I am trying to use a Kotlin JSR 223 engine to expose Kotlin as a scripting language in a Java GUI. The GUI has support for multiple languages already and injects variables through the engine scope bindings. This works for all languages but Kotlin does not see any of the bindings. Conversely, any variables that are defined in Kotlin, are not available to other languages. I assume that these issues are related.
I created a minimum working example. I used Java because the code base is Java but I also translated to Kotlin to confirm that the issue also occurs there.
- Java MWE
import kotlin.jvm.JvmClassMappingKt; import kotlin.reflect.KClass; import org.jetbrains.kotlin.cli.common.repl.KotlinJsr223JvmScriptEngineFactoryBase; import org.jetbrains.kotlin.cli.common.repl.ScriptArgsWithTypes; import org.jetbrains.kotlin.script.jsr223.KotlinJsr223JvmLocalScriptEngine; import org.jetbrains.kotlin.script.jsr223.KotlinStandardJsr223ScriptTemplate; import javax.script.Bindings; import javax.script.ScriptContext; import javax.script.ScriptEngine; import javax.script.ScriptException; import java.io.File; import java.net.URL; import java.net.URLClassLoader; import java.util.Arrays; import java.util.stream.Collectors; public class MWE { private static class Factory extends KotlinJsr223JvmScriptEngineFactoryBase { @Override public ScriptEngine getScriptEngine() { return new KotlinJsr223JvmLocalScriptEngine( this, Arrays.stream(((URLClassLoader)ClassLoader.getSystemClassLoader()).getURLs()).map(URL::getFile).map(File::new).collect(Collectors.toList()), KotlinStandardJsr223ScriptTemplate.class.getCanonicalName(), (ctx, types) -> new ScriptArgsWithTypes(new Object[]{ctx.getBindings(ScriptContext.ENGINE_SCOPE)}, types == null ? new KClass[0] : types), new KClass[] {JvmClassMappingKt.getKotlinClass(Bindings.class) } ); } } public static void main(String... args) throws ScriptException { final ScriptEngine engine = new Factory().getScriptEngine(); engine.getBindings(ScriptContext.ENGINE_SCOPE).put("abc", 1); System.out.println(engine.eval("1")); System.out.println(engine.eval("abc")); } }
- Kotlin MWE
import org.jetbrains.kotlin.cli.common.repl.KotlinJsr223JvmScriptEngineFactoryBase import org.jetbrains.kotlin.cli.common.repl.ScriptArgsWithTypes import org.jetbrains.kotlin.script.jsr223.KotlinJsr223JvmLocalScriptEngine import org.jetbrains.kotlin.script.jsr223.KotlinStandardJsr223ScriptTemplate import java.io.File import java.net.URL import java.net.URLClassLoader import java.util.* import java.util.stream.Collectors import javax.script.Bindings import javax.script.ScriptContext import javax.script.ScriptEngine import kotlin.reflect.KClass private class Factory : KotlinJsr223JvmScriptEngineFactoryBase() { override fun getScriptEngine(): ScriptEngine { return KotlinJsr223JvmLocalScriptEngine( this, Arrays.stream((ClassLoader.getSystemClassLoader() as URLClassLoader).urLs).map { obj: URL -> obj.file }.map { File(it) }.collect(Collectors.toList()), KotlinStandardJsr223ScriptTemplate::class.java.canonicalName, { ctx: ScriptContext, types: Array<out KClass<out Any>>? -> ScriptArgsWithTypes(arrayOf<Any?>(ctx.getBindings(ScriptContext.ENGINE_SCOPE)), types ?: emptyArray()) }, arrayOf(Bindings::class)) } } fun main() { val engine = Factory().scriptEngine engine.getBindings(ScriptContext.ENGINE_SCOPE)["abc"] = 1 println(engine.eval("1")) println(engine.eval("abc")) }
- relevant output (equivalent in both cases, just posting the Kotlin MWE output):
1 Exception in thread "main" javax.script.ScriptException: error: unresolved reference: abc abc ^ at org.jetbrains.kotlin.cli.common.repl.KotlinJsr223JvmScriptEngineBase.asJsr223EvalResult(KotlinJsr223JvmScriptEngineBase.kt:104) at org.jetbrains.kotlin.cli.common.repl.KotlinJsr223JvmScriptEngineBase.compileAndEval(KotlinJsr223JvmScriptEngineBase.kt:63) at org.jetbrains.kotlin.cli.common.repl.KotlinJsr223JvmScriptEngineBase.eval(KotlinJsr223JvmScriptEngineBase.kt:31) at javax.script.AbstractScriptEngine.eval(AbstractScriptEngine.java:264) at MWEKt.main(MWE.kt:33) at MWEKt.main(MWE.kt)
The engine can clearly evaluate a statement that does not rely on information in the bindings: It prints 1
for the first call engine.eval("1")
. For the second call, engine.eval("abc")
, it does not find/use abc
in the bindings.
Do I use the engine incorrectly?
There is also another exception
java.lang.NoClassDefFoundError: com/sun/jna/Native
but it does not crash the application so I don’t know if it is relevant. I can share the entire stack trace if this seems relevant.