mirror of
https://github.com/2006-Scape/apollo.git
synced 2026-07-07 16:49:12 +00:00
First draft of KotlinPluginEnvironment
This commit is contained in:
@@ -1,7 +1,32 @@
|
|||||||
description = 'Apollo Game'
|
description = 'Apollo Game'
|
||||||
|
|
||||||
|
buildscript {
|
||||||
|
ext.kotlinVersion = '1.1.2-4'
|
||||||
|
|
||||||
|
repositories {
|
||||||
|
mavenCentral()
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
apply plugin: 'kotlin'
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
compile project(':cache')
|
compile project(':cache')
|
||||||
compile project(':net')
|
compile project(':net')
|
||||||
compile project(':util')
|
compile project(':util')
|
||||||
|
|
||||||
|
compile group: 'org.jetbrains.kotlin', name: 'kotlin-compiler', version: $kotlinVersion
|
||||||
|
compile group: 'org.jetbrains.kotlin', name: 'kotlin-stdlib-jre8', version: $kotlinVersion
|
||||||
|
}
|
||||||
|
|
||||||
|
sourceSets {
|
||||||
|
main.kotlin.srcDirs += 'src/kotlin'
|
||||||
|
}
|
||||||
|
|
||||||
|
repositories {
|
||||||
|
mavenCentral()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,64 @@
|
|||||||
|
package org.apollo.game.plugin.kotlin
|
||||||
|
|
||||||
|
import com.intellij.openapi.util.Disposer
|
||||||
|
import org.apollo.Server
|
||||||
|
import org.apollo.game.model.World
|
||||||
|
import org.apollo.game.plugin.PluginContext
|
||||||
|
import org.jetbrains.kotlin.cli.common.CLIConfigurationKeys
|
||||||
|
import org.jetbrains.kotlin.cli.common.messages.MessageCollector
|
||||||
|
import org.jetbrains.kotlin.cli.jvm.compiler.EnvironmentConfigFiles
|
||||||
|
import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment
|
||||||
|
import org.jetbrains.kotlin.cli.jvm.compiler.KotlinToJVMBytecodeCompiler
|
||||||
|
import org.jetbrains.kotlin.cli.jvm.config.JvmClasspathRoot
|
||||||
|
import org.jetbrains.kotlin.codegen.CompilationException
|
||||||
|
import org.jetbrains.kotlin.config.CommonConfigurationKeys
|
||||||
|
import org.jetbrains.kotlin.config.CompilerConfiguration
|
||||||
|
import org.jetbrains.kotlin.config.JVMConfigurationKeys
|
||||||
|
import org.jetbrains.kotlin.config.addKotlinSourceRoot
|
||||||
|
import org.jetbrains.kotlin.script.KotlinScriptDefinitionFromAnnotatedTemplate
|
||||||
|
import java.io.File
|
||||||
|
|
||||||
|
class KotlinPluginCompiler(val classpath: List<File>, val messageCollector: MessageCollector) {
|
||||||
|
|
||||||
|
private fun createCompilerConfiguration(inputPath: String): CompilerConfiguration {
|
||||||
|
val configuration = CompilerConfiguration()
|
||||||
|
val scriptDefinition = KotlinScriptDefinitionFromAnnotatedTemplate(KotlinPluginScript::class)
|
||||||
|
|
||||||
|
configuration.add(JVMConfigurationKeys.SCRIPT_DEFINITIONS, scriptDefinition)
|
||||||
|
configuration.put(JVMConfigurationKeys.CONTENT_ROOTS, classpath.map { JvmClasspathRoot(it) })
|
||||||
|
configuration.put(JVMConfigurationKeys.RETAIN_OUTPUT_IN_MEMORY, true)
|
||||||
|
configuration.put(CLIConfigurationKeys.MESSAGE_COLLECTOR_KEY, messageCollector)
|
||||||
|
configuration.put(CommonConfigurationKeys.MODULE_NAME, inputPath)
|
||||||
|
configuration.addKotlinSourceRoot(inputPath)
|
||||||
|
|
||||||
|
return configuration
|
||||||
|
}
|
||||||
|
|
||||||
|
@Throws(KotlinPluginCompilerException::class)
|
||||||
|
fun compile(inputPath: String): Class<out KotlinPluginScript> {
|
||||||
|
val rootDisposable = Disposer.newDisposable()
|
||||||
|
val configuration = createCompilerConfiguration(inputPath)
|
||||||
|
|
||||||
|
val configFiles = EnvironmentConfigFiles.JVM_CONFIG_FILES
|
||||||
|
val environment = KotlinCoreEnvironment.createForProduction(rootDisposable, configuration, configFiles)
|
||||||
|
|
||||||
|
try {
|
||||||
|
val clazz = KotlinToJVMBytecodeCompiler.compileScript(environment, Server::class.java.classLoader)
|
||||||
|
|
||||||
|
if (clazz?.getConstructor(World::class.java, PluginContext::class.java) == null) {
|
||||||
|
throw KotlinPluginCompilerException("Unable to compile $inputPath, no plugin constructor found")
|
||||||
|
}
|
||||||
|
|
||||||
|
return clazz as Class<out KotlinPluginScript>
|
||||||
|
} catch (e: CompilationException) {
|
||||||
|
throw KotlinPluginCompilerException("Compilation failed", e)
|
||||||
|
} finally {
|
||||||
|
Disposer.dispose(rootDisposable)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
class KotlinPluginCompilerException(message: String, cause: Throwable? = null) : Exception(message, cause) {
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
package org.apollo.game.plugin.kotlin
|
||||||
|
|
||||||
|
import org.apollo.game.message.handler.MessageHandler
|
||||||
|
import org.apollo.game.model.World
|
||||||
|
import org.apollo.game.model.entity.Player
|
||||||
|
import org.apollo.game.plugin.PluginContext
|
||||||
|
import org.apollo.net.message.Message
|
||||||
|
import kotlin.reflect.KClass
|
||||||
|
import kotlin.script.templates.ScriptTemplateDefinition
|
||||||
|
|
||||||
|
@ScriptTemplateDefinition(
|
||||||
|
scriptFilePattern = ".*\\.plugin\\.kts"
|
||||||
|
)
|
||||||
|
abstract class KotlinPluginScript(val world: World, val context: PluginContext) {
|
||||||
|
|
||||||
|
protected fun <T : Message> on(type: () -> KClass<T>): KotlinMessageHandler<T> {
|
||||||
|
return KotlinMessageHandler(world, context, type.invoke())
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class KotlinMessageHandler<T : Message>(val world: World, val context: PluginContext, val type: KClass<T>) : MessageHandler<T>(world) {
|
||||||
|
|
||||||
|
override fun handle(player: Player, message: T) {
|
||||||
|
if (message.predicate()) {
|
||||||
|
message.function(player)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var function: T.(Player) -> Unit = { _ -> }
|
||||||
|
|
||||||
|
var predicate: T.() -> Boolean = { true }
|
||||||
|
|
||||||
|
fun where(predicate: T.() -> Boolean): KotlinMessageHandler<T> {
|
||||||
|
this.predicate = predicate
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
fun then(function: T.(Player) -> Unit) {
|
||||||
|
this.function = function
|
||||||
|
this.context.addMessageHandler(type.java, this)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,101 @@
|
|||||||
|
package org.apollo.game.plugin;
|
||||||
|
|
||||||
|
import org.apollo.game.model.World;
|
||||||
|
import org.apollo.game.plugin.kotlin.KotlinPluginCompiler;
|
||||||
|
import org.apollo.game.plugin.kotlin.KotlinPluginScript;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
import org.jetbrains.kotlin.cli.common.messages.CompilerMessageLocation;
|
||||||
|
import org.jetbrains.kotlin.cli.common.messages.CompilerMessageSeverity;
|
||||||
|
import org.jetbrains.kotlin.cli.common.messages.MessageCollector;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.lang.reflect.Constructor;
|
||||||
|
import java.net.URISyntaxException;
|
||||||
|
import java.net.URL;
|
||||||
|
import java.net.URLClassLoader;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.logging.Level;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
|
public class KotlinPluginEnvironment implements PluginEnvironment, MessageCollector {
|
||||||
|
|
||||||
|
private static final Logger logger = Logger.getLogger(KotlinPluginEnvironment.class.getName());
|
||||||
|
|
||||||
|
private final World world;
|
||||||
|
private final KotlinPluginCompiler pluginCompiler;
|
||||||
|
|
||||||
|
private PluginContext context;
|
||||||
|
|
||||||
|
public KotlinPluginEnvironment(World world) {
|
||||||
|
this.world = world;
|
||||||
|
this.pluginCompiler = new KotlinPluginCompiler(resolveClasspath(), this);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resolve the classpath of the current running {@link Thread}.
|
||||||
|
*
|
||||||
|
* @return A {@link List} of {@link File}s pointing to classpath entries.
|
||||||
|
*/
|
||||||
|
private static List<File> resolveClasspath() {
|
||||||
|
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
|
||||||
|
|
||||||
|
if (!(classLoader instanceof URLClassLoader)) {
|
||||||
|
throw new RuntimeException("Unable to resolve classpath for current ClassLoader");
|
||||||
|
}
|
||||||
|
|
||||||
|
URLClassLoader urlClassLoader = (URLClassLoader) classLoader;
|
||||||
|
URL[] classpathUrls = urlClassLoader.getURLs();
|
||||||
|
List<File> classpath = new ArrayList<>();
|
||||||
|
|
||||||
|
for (URL classpathUrl : classpathUrls) {
|
||||||
|
try {
|
||||||
|
classpath.add(new File(classpathUrl.toURI()));
|
||||||
|
} catch (URISyntaxException e) {
|
||||||
|
throw new RuntimeException("URL returned by ClassLoader is invalid");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return classpath;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void parse(InputStream is, String name) {
|
||||||
|
//@todo - wait until all plugin classes are loading until running constructors?
|
||||||
|
try {
|
||||||
|
Class<? extends KotlinPluginScript> pluginClass = pluginCompiler.compile(name);
|
||||||
|
Constructor<? extends KotlinPluginScript> pluginConstructor = pluginClass.getConstructor(World.class,
|
||||||
|
PluginContext.class);
|
||||||
|
|
||||||
|
pluginConstructor.newInstance(world, context);
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setContext(PluginContext context) {
|
||||||
|
this.context = context;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void clear() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void report(@NotNull CompilerMessageSeverity severity, @NotNull String message,
|
||||||
|
@NotNull CompilerMessageLocation location) {
|
||||||
|
if (severity.isError()) {
|
||||||
|
logger.log(Level.SEVERE, String.format("%s:%s-%s: %s", location.getPath(), location.getLine(),
|
||||||
|
location.getColumn(), message));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean hasErrors() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -146,7 +146,7 @@ public final class PluginManager {
|
|||||||
Map<String, PluginMetaData> plugins = createMap(findPlugins());
|
Map<String, PluginMetaData> plugins = createMap(findPlugins());
|
||||||
Set<PluginMetaData> started = new HashSet<>();
|
Set<PluginMetaData> started = new HashSet<>();
|
||||||
|
|
||||||
PluginEnvironment env = new RubyPluginEnvironment(world); // TODO isolate plugins if possible in the future!
|
PluginEnvironment env = new KotlinPluginEnvironment(world); // TODO isolate plugins if possible in the future!
|
||||||
env.setContext(context);
|
env.setContext(context);
|
||||||
|
|
||||||
for (PluginMetaData plugin : plugins.values()) {
|
for (PluginMetaData plugin : plugins.values()) {
|
||||||
|
|||||||
Reference in New Issue
Block a user