Search code examples
javascalasingletonjsr223

Scala Script Engine creating another instance of a singleton


So, I'm creating a scripting system using JSR 223 for Scala, but I came across this problem I can't find any cause for.

There is a singleton-like class, which has methods to add event listeners (from the scripts) and dispatch events (from the core). Everything worked fine, but for some reason the added listeners had disappeared when I got to dispatch an event.

After reproducing the problem, I found out that the script engine creates another instance of the singleton:

Here is my Singleton class:

package test;

import java.util.Arrays;

public final class Singleton {

    private static final Singleton instance = new Singleton();

    private Singleton() {
        Arrays.stream(Thread.currentThread().getStackTrace()).forEach(System.out::println);
        System.out.println();
    }

    public static Singleton instance() {
        return instance;
    }
}

And here is my Main class:

package test;

import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;

public final class Main {

    public static void main(String[] args) throws ScriptException {

        ScriptEngine engine = new ScriptEngineManager().getEngineByName("scala");

        // this is a trick I found to access the classpath,
        // might be the problem
        @SuppressWarnings("rawtypes")
        scala.collection.immutable.List nil = scala.collection.immutable.Nil$.MODULE$;
        @SuppressWarnings("unchecked")
        scala.collection.immutable.$colon$colon<String> vals = scala.collection.immutable.$colon$colon$.MODULE$.apply("true", nil);
        ((scala.tools.nsc.interpreter.IMain) engine).settings().usejavacp().tryToSet(vals);

        engine.eval("test.Singleton.instance");
        Singleton.instance();
    }
}

And here is the output:

java.lang.Thread.getStackTrace(Unknown Source)
test.Singleton.<init>(Singleton.java:10)
test.Singleton.<clinit>(Singleton.java:7)
$line3.$read$$iw$$iw$.<init>(<console>:8)
$line3.$read$$iw$$iw$.<clinit>(<console>)
$line3.$eval$.$result$lzycompute(<console>:5)
$line3.$eval$.$result(<console>:5)
$line3.$eval.$result(<console>)
sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
java.lang.reflect.Method.invoke(Unknown Source)
scala.tools.nsc.interpreter.IMain$ReadEvalPrint.call(IMain.scala:773)
scala.tools.nsc.interpreter.IMain$ReadEvalPrint.callEither(IMain.scala:777)
scala.tools.nsc.interpreter.IMain$ReadEvalPrint.evalEither(IMain.scala:792)
scala.tools.nsc.interpreter.IMain$WrappedRequest.eval(IMain.scala:613)
scala.tools.nsc.interpreter.IMain.eval(IMain.scala:1047)
javax.script.AbstractScriptEngine.eval(Unknown Source)
test.Main.main(Main.java:19)

java.lang.Thread.getStackTrace(Unknown Source)
test.Singleton.<init>(Singleton.java:10)
test.Singleton.<clinit>(Singleton.java:7)
test.Main.main(Main.java:20)

The stack trace shows that the script engine ends up creating a new instance of Singleton, but I got no idea why.

Thank you.


Solution

  • Found out the fix after browsing the source code:

    ((scala.tools.nsc.interpreter.IMain) engine).settings().embeddedDefaults(Main.class.getClassLoader());
    

    This changes the ClassLoader for the ScriptEngine to the same one.