Skip to content

Instantly share code, notes, and snippets.

@vivin
Created December 4, 2015 23:55
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save vivin/5d1fb904ef85692ff4db to your computer and use it in GitHub Desktop.
Save vivin/5d1fb904ef85692ff4db to your computer and use it in GitHub Desktop.
Nashorn: Sharing a module that maintains state amongst custom scripts that share a single script-engine instance

Probably ill-conceived and I probably won't ever need this. But just noting this down somewhere if I ever do...

Let's say you have a module or script or something that maintains state, and you have a single script-engine instance. You want each custom script to get its own instance of this module (i.e., multiple scripts executing on the engine must not step over each other's state). How do you do this? You precompile then when it's time to execute your custom script, you first execute the precompiled script. Then you copy everything it exposes into your custom script's context. Snippet:

//Precompile the modules and put them in a map
private void compileModule(String name, String source) {
    try {
        this.engine.put(ScriptEngine.FILENAME, String.format("<%s>", name));
        this.compiledModules.put(name, ((Compilable) this.engine).compile(source));
    } catch(RuntimeException | ScriptException e) {
        throw new RuntimeException(String.format("Unexpected error while pre-compiling global module %s: %s", name, e.getMessage()), e);
    }
}

private void execute() {
    this.compiledModules.forEach((moduleName, compiledModule) -> {
        //create a new context for the module
        SimpleScriptContext compiledModuleContext = new SimpleScriptContext();

        //create new bindings that inherits nashorn global objects
        compiledModuleContext.setBindings(getEngine().createBindings(), ScriptContext.ENGINE_SCOPE);
        
        //copy over any custom stuff you shoved into the parent context (e.g., your own custom Java objects or other 
        //eval'd stuff etc.)
        compiledModuleContext.getBindings(ScriptContext.ENGINE_SCOPE).putAll(getEngine().getBindings(ScriptContext.ENGINE_SCOPE));

        try {
            compiledModule.eval(compiledModuleContext);
            
            //Copy all the stuff that the compiled module created and all the other stuff inherited from the parent context
            //into your custom script's context
            this.myContext.getBindings(ScriptContext.ENGINE_SCOPE).putAll(compiledModuleContext.getBindings(ScriptContext.ENGINE_SCOPE));
        } catch(ScriptException e) {
            throw new RuntimeException(String.format("Unexpected error while evaluting pre-compiled module %s: %s", moduleName, e.getMessage()), e);
        }
    });
}

This is probably unnecessary... you can always design the module so that it always returns a new instance.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment