kotlinscriptingjsr223jvm-languagesjavax.script

How do I retrieve Kotlin jsr223 script engine from ScriptEngineManager?


I'm trying to write an interface (GUI) to run some Kotlin scripts. I'm starting with just a Junit test to ensure that I can execute a script. I can't even load the kotlin engine. It seems like my dependencies (gradle) are in order, but you can double check that.

compile name: 'kotlin-script-runtime',
    group: 'org.jetbrains.kotlin', version: kotlin_version
compile name: 'kotlin-script-util',
    group: 'org.jetbrains.kotlin', version: kotlin_version
compile name: 'kotlin-compiler-embeddable',
    group: 'org.jetbrains.kotlin', version: kotlin_version

above: ext.kotlin_version = '1.3.0'

Here is the failing test:

@Test
fun testEngine() {
  // attempt to load script engine class before looking for it
  val ktsEngineClassName = "org.jetbrains.kotlin.script.jsr223.KotlinJsr223JvmLocalScriptEngine";
  var clazz: Class<*>? = null;
  // assign the clazz variable inside a closure to catch ClassNotFound
  assertDoesNotThrow({clazz = Class.forName(ktsEngineClassName)}, "could not find $ktsEngineClassName")
  // just to make sure it's there
  assertNotNull(clazz, "could not find $ktsEngineClassName")

  // so far so good, but here's the problem:
  val engineName = "kotlin"
  // load the factory
  val factory = ScriptEngineManager().getEngineByName(engineName)?.factory
  // and test that we got it
  assertNotNull(factory, "didn't find factory for '$engineName'." +
      "\navailable: ${ScriptEngineManager().engineFactories}")
}

That last assertNotNull fails with the following output:

org.opentest4j.AssertionFailedError: didn't find factory for 'kotlin'.
available: [jdk.nashorn.api.scripting.NashornScriptEngineFactory@1a41b1fc] ==> expected: not <null>

You can see that, although the script engine class seems to load via Class.forName successfully, the name kotlin is not registered, and the list of available engines contains Nashorn only. How do I ensure that the engine is registered?


Solution

  • It looks like the kotlin script engine is not in the default classloader, so you need to specify a classloader when instantiating the ScriptEngineManager:

    ScriptEngineManager(clazz.getClassLoader()) getEngineByName(engineName)?.factory

    UPDATE

    In Java 8, if you look at $JAVA_HOME/jre/lib/ext/nashorn.jar in the path META_INF/services there is a file called javax.script.ScriptEngineFactory. The contents are:

    jdk.nashorn.api.scripting.NashornScriptEngineFactory
    

    So when the ScriptEngineManager looks for ScriptEngine implementations, it scans the classpath for entries named /META-INF/services/javax.script.ScriptEngineFactory and loads the class names from each one found.

    Normally, the jar containing your targeted script engine would contain its own services file, but, if your kotlin script engine is already in the class path, I think you could just create a classpath resource called /META-INF/services/javax.script.ScriptEngineFactory with the plain text context of

    org.jetbrains.kotlin.script.jsr223.KotlinJsr223JvmLocalScriptEngineFactory
    

    and you should be good to go. Also, you would not need any of the code you provided in your example above the comment // so far so good, but here's the problem:.