Move invokable parts to JSR223 only, remove unneeded code

This commit is contained in:
Ilya Chernikov
2016-12-09 17:14:09 +01:00
parent aab1759dc5
commit fc416476a8
9 changed files with 64 additions and 135 deletions
@@ -16,20 +16,18 @@
package org.jetbrains.kotlin.cli.common.repl
import org.jetbrains.kotlin.utils.tryCreateCallableMapping
import org.jetbrains.kotlin.resolve.jvm.JvmClassName
import java.io.File
import java.net.URLClassLoader
import java.util.concurrent.locks.ReentrantReadWriteLock
import kotlin.concurrent.read
import kotlin.concurrent.write
import kotlin.reflect.*
open class GenericReplCompiledEvaluator(baseClasspath: Iterable<File>,
baseClassloader: ClassLoader?,
val scriptArgs: Array<Any?>? = null,
val scriptArgsTypes: Array<Class<*>>? = null
) : ReplCompiledEvaluator, ReplScriptInvoker {
) : ReplCompiledEvaluator {
private var classLoader: org.jetbrains.kotlin.cli.common.repl.ReplClassLoader = makeReplClassLoader(baseClassloader, baseClasspath)
private val classLoaderLock = ReentrantReadWriteLock()
@@ -37,8 +35,6 @@ open class GenericReplCompiledEvaluator(baseClasspath: Iterable<File>,
// TODO: consider to expose it as a part of (evaluator, invoker) interface
private val evalStateLock = ReentrantReadWriteLock()
private data class ClassWithInstance(val klass: Class<*>, val instance: Any)
private val compiledLoadedClassesHistory = ReplHistory<ClassWithInstance>()
override fun eval(codeLine: ReplCodeLine,
@@ -83,7 +79,7 @@ open class GenericReplCompiledEvaluator(baseClasspath: Iterable<File>,
}
val constructorParams: Array<Class<*>> =
(compiledLoadedClassesHistory.values.map { it.klass } +
(compiledLoadedClassesHistory.values.map { it.klass.java } +
(scriptArgs?.mapIndexed { i, it -> scriptArgsTypes?.getOrNull(i) ?: it?.javaClass ?: Any::class.java } ?: emptyList())
).toTypedArray()
val constructorArgs: Array<Any?> = (compiledLoadedClassesHistory.values.map { it.instance } + scriptArgs.orEmpty()).toTypedArray()
@@ -98,7 +94,7 @@ open class GenericReplCompiledEvaluator(baseClasspath: Iterable<File>,
return ReplEvalResult.Error.Runtime(compiledLoadedClassesHistory.lines, renderReplStackTrace(e.cause!!, startFromMethodName = "${scriptClass.name}.<init>"), e as? Exception)
}
compiledLoadedClassesHistory.add(codeLine, ClassWithInstance(scriptClass, scriptInstance))
compiledLoadedClassesHistory.add(codeLine, ClassWithInstance(scriptClass.kotlin, scriptInstance))
val rvField = scriptClass.getDeclaredField(SCRIPT_RESULT_FIELD_NAME).apply { isAccessible = true }
val rv: Any? = rvField.get(scriptInstance)
@@ -106,40 +102,7 @@ open class GenericReplCompiledEvaluator(baseClasspath: Iterable<File>,
return if (hasResult) ReplEvalResult.ValueResult(compiledLoadedClassesHistory.lines, rv) else ReplEvalResult.UnitResult(compiledLoadedClassesHistory.lines)
}
override fun <T: Any> getInterface(klass: KClass<T>): ReplScriptInvokeResult = evalStateLock.read {
val (_, instance) = compiledLoadedClassesHistory.values.lastOrNull() ?: return ReplScriptInvokeResult.Error.NoSuchEntity("no script ")
return getInterface(instance, klass)
}
override fun <T: Any> getInterface(receiver: Any, klass: KClass<T>): ReplScriptInvokeResult = evalStateLock.read {
return ReplScriptInvokeResult.ValueResult(klass.safeCast(receiver))
}
override fun invokeMethod(receiver: Any, name: String, vararg args: Any?, invokeWrapper: InvokeWrapper?): ReplScriptInvokeResult = evalStateLock.read {
return invokeImpl(receiver.javaClass.kotlin, receiver, name, args, invokeWrapper)
}
override fun invokeFunction(name: String, vararg args: Any?, invokeWrapper: InvokeWrapper?): ReplScriptInvokeResult = evalStateLock.read {
val (klass, instance) = compiledLoadedClassesHistory.values.lastOrNull() ?: return ReplScriptInvokeResult.Error.NoSuchEntity("no script ")
return invokeImpl(klass.kotlin, instance, name, args, invokeWrapper)
}
private fun invokeImpl(receiverClass: KClass<*>, receiverInstance: Any, name: String, args: Array<out Any?>, invokeWrapper: InvokeWrapper?): ReplScriptInvokeResult {
val candidates = receiverClass.memberFunctions.filter { it.name == name } +
receiverClass.memberExtensionFunctions.filter { it.name == name }
val (fn, mapping) = candidates.findMapping(args.toList()) ?:
candidates.findMapping(listOf<Any?>(receiverInstance) + args) ?:
return ReplScriptInvokeResult.Error.NoSuchEntity("no suitable function '$name' found")
val res = try {
invokeWrapper?.invoke { fn.callBy(mapping) } ?: fn.callBy(mapping)
}
catch (e: Throwable) {
// ignore everything in the stack trace until this constructor call
return ReplScriptInvokeResult.Error.Runtime(renderReplStackTrace(e.cause!!, startFromMethodName = "${fn.name}"), e as? Exception)
}
return if (fn.returnType.classifier == Unit::class) ReplScriptInvokeResult.UnitResult else ReplScriptInvokeResult.ValueResult(res)
}
override val lastEvaluatedScript: ClassWithInstance? get() = evalStateLock.read { compiledLoadedClassesHistory.values.lastOrNull() }
companion object {
private val SCRIPT_RESULT_FIELD_NAME = "\$\$result"
@@ -149,10 +112,3 @@ open class GenericReplCompiledEvaluator(baseClasspath: Iterable<File>,
private fun makeReplClassLoader(baseClassloader: ClassLoader?, baseClasspath: Iterable<File>) =
ReplClassLoader(URLClassLoader(baseClasspath.map { it.toURI().toURL() }.toTypedArray(), baseClassloader))
private fun Iterable<KFunction<*>>.findMapping(args: List<Any?>): Pair<KFunction<*>, Map<KParameter, Any?>>? {
for (fn in this) {
val mapping = tryCreateCallableMapping(fn, args)
if (mapping != null) return fn to mapping
}
return null
}
@@ -16,45 +16,71 @@
package org.jetbrains.kotlin.cli.common.repl
import org.jetbrains.kotlin.utils.tryCreateCallableMapping
import javax.script.Invocable
import javax.script.ScriptException
import kotlin.reflect.*
@Suppress("unused") // used externally (kotlin.script.utils)
interface KotlinJsr223JvmInvocableScriptEngine : Invocable {
val replScriptInvoker: ReplScriptInvoker
val replScriptEvaluator: ReplEvaluatorBase
fun <T: Any> getInterface(klass: KClass<T>): Any? {
val (_, instance) = replScriptEvaluator.lastEvaluatedScript ?: throw IllegalArgumentException("no script ")
return getInterface(instance, klass)
}
fun <T: Any> getInterface(receiver: Any, klass: KClass<T>): Any? {
return klass.safeCast(receiver)
}
override fun invokeFunction(name: String?, vararg args: Any?): Any? {
if (name == null) throw java.lang.NullPointerException("function name cannot be null")
return processInvokeResult(replScriptInvoker.invokeFunction(name, *args), isMethod = true)
val (klass, instance) = replScriptEvaluator.lastEvaluatedScript ?: throw IllegalArgumentException("no script ")
return invokeImpl(klass, instance, name, args, invokeWrapper = null)
}
override fun invokeMethod(thiz: Any?, name: String?, vararg args: Any?): Any? {
if (name == null) throw java.lang.NullPointerException("method name cannot be null")
if (thiz == null) throw IllegalArgumentException("cannot invoke method on the null object")
return processInvokeResult(replScriptInvoker.invokeMethod(thiz, name, *args), isMethod = true)
return invokeImpl(thiz.javaClass.kotlin, thiz, name, args, invokeWrapper = null)
}
override fun <T : Any> getInterface(clasz: Class<T>?): T? {
if (clasz == null) throw IllegalArgumentException("class object cannot be null")
if (!clasz.isInterface) throw IllegalArgumentException("expecting interface")
return processInvokeResult(replScriptInvoker.getInterface(clasz.kotlin), isMethod = false) as? T
val (_, instance) = replScriptEvaluator.lastEvaluatedScript ?: throw IllegalArgumentException("no script ")
return getInterface(instance, clasz)
}
override fun <T : Any> getInterface(thiz: Any?, clasz: Class<T>?): T? {
if (thiz == null) throw IllegalArgumentException("object cannot be null")
if (clasz == null) throw IllegalArgumentException("class object cannot be null")
if (!clasz.isInterface) throw IllegalArgumentException("expecting interface")
return processInvokeResult(replScriptInvoker.getInterface(thiz, clasz.kotlin), isMethod = false) as? T
return clasz.kotlin.safeCast(thiz)
}
}
private fun processInvokeResult(res: ReplScriptInvokeResult, isMethod: Boolean): Any? =
when (res) {
is ReplScriptInvokeResult.Error.NoSuchEntity -> throw if (isMethod) NoSuchMethodException(res.message) else IllegalArgumentException(res.message)
is ReplScriptInvokeResult.Error.CompileTime -> throw IllegalArgumentException(res.message) // should not happen in the current code, so leaving it here despite the contradiction with Invocable's specs
is ReplScriptInvokeResult.Error.Runtime -> throw ScriptException(res.message)
is ReplScriptInvokeResult.Error -> throw ScriptException(res.message)
is ReplScriptInvokeResult.UnitResult -> Unit // TODO: check if it is suitable replacement for java's Void
is ReplScriptInvokeResult.ValueResult -> res.value
}
}
private fun invokeImpl(receiverClass: KClass<*>, receiverInstance: Any, name: String, args: Array<out Any?>, invokeWrapper: InvokeWrapper?): Any? {
val candidates = receiverClass.functions.filter { it.name == name }
val (fn, mapping) = candidates.findMapping(listOf<Any?>(receiverInstance) + args) ?:
throw NoSuchMethodException("no suitable function '$name' found")
val res = try {
invokeWrapper?.invoke {
fn.callBy(mapping)
} ?: fn.callBy(mapping)
}
catch (e: Throwable) {
// ignore everything in the stack trace until this constructor call
throw ScriptException(renderReplStackTrace(e.cause!!, startFromMethodName = fn.name))
}
return if (fn.returnType.classifier == Unit::class) Unit else res
}
private fun Iterable<KFunction<*>>.findMapping(args: List<Any?>): Pair<KFunction<*>, Map<KParameter, Any?>>? {
for (fn in this) {
val mapping = tryCreateCallableMapping(fn, args)
if (mapping != null) return fn to mapping
}
return null
}
@@ -28,6 +28,8 @@ data class ReplCodeLine(val no: Int, val code: String) : Serializable {
}
}
data class ClassWithInstance(val klass: KClass<*>, val instance: Any)
// TODO: consider storing code hash where source is not needed
data class CompiledClassData(val path: String, val bytes: ByteArray) : Serializable {
@@ -73,22 +75,6 @@ sealed class ReplCompileResult(val updatedHistory: List<ReplCodeLine>) : Seriali
}
}
sealed class ReplScriptInvokeResult : Serializable {
class ValueResult(val value: Any?) : ReplScriptInvokeResult() {
override fun toString(): String = "Result: $value"
}
object UnitResult : ReplScriptInvokeResult()
sealed class Error(val message: String) : ReplScriptInvokeResult() {
class Runtime(message: String, val cause: Exception? = null) : Error(message)
class NoSuchEntity(message: String) : Error(message)
class CompileTime(message: String, val location: CompilerMessageLocation = CompilerMessageLocation.NO_LOCATION) : Error(message)
override fun toString(): String = "${this::class.simpleName}Error(message = \"$message\""
}
companion object {
private val serialVersionUID: Long = 8228357578L
}
}
sealed class ReplEvalResult(val updatedHistory: List<ReplCodeLine>) : Serializable {
class ValueResult(updatedHistory: List<ReplCodeLine>, val value: Any?) : ReplEvalResult(updatedHistory) {
override fun toString(): String = "Result: $value"
@@ -117,19 +103,11 @@ interface ReplCompiler : ReplChecker {
fun compile(codeLine: ReplCodeLine, history: List<ReplCodeLine>): ReplCompileResult
}
interface ReplScriptInvoker {
fun <T: Any> getInterface(clasz: KClass<T>): ReplScriptInvokeResult
fun <T: Any> getInterface(receiver: Any, clasz: KClass<T>): ReplScriptInvokeResult
fun invokeMethod(receiver: Any, name: String, vararg args: Any?, invokeWrapper: InvokeWrapper? = null): ReplScriptInvokeResult
fun invokeFunction(name: String, vararg args: Any?, invokeWrapper: InvokeWrapper? = null): ReplScriptInvokeResult
interface ReplEvaluatorBase {
val lastEvaluatedScript: ClassWithInstance?
}
// TODO this is a bit cumbersome, consider some other ways to access an invoker
interface ReplScriptInvokerProxy {
val scriptInvoker: ReplScriptInvoker
}
interface ReplCompiledEvaluator {
interface ReplCompiledEvaluator : ReplEvaluatorBase {
fun eval(codeLine: ReplCodeLine,
history: List<ReplCodeLine>,
@@ -141,7 +119,7 @@ interface ReplCompiledEvaluator {
}
interface ReplEvaluator : ReplChecker {
interface ReplEvaluator : ReplChecker, ReplEvaluatorBase {
fun eval(codeLine: ReplCodeLine,
history: List<ReplCodeLine>,