fusionlightcat
fusionlightcat

Reputation: 385

Nashorn: How to pre-set Java.type()-vars inside of Java before JavaScript execution?

I am currently executing my JavaScript-scripts with this java code:

ScriptEngine engine = new ScriptEngineManager().getEngineByName("nashorn");
engine.eval(new FileReader("awesome_script.js"));

I need to call Java functions from JavaScript, so I defined this at the top of my awesome_script.js file:

var first = Java.type('io.github.awesomeprogram.FirstClass');
var second = Java.type('io.github.awesomeprogram.SecondClass');
var extra = Java.type('io.github.awesomeprogram.ExtraClass');

I can then call some methods from these classes, e.g.:

second.coolmethod("arg1",2);

My problem is now that I need to use many java classes inside of my scripts. I also have a lot of scripts and I think it is very inefficient to define every single one of this classes in every script.

So I am looking for a solution to create the objects created inside of JavaScript with Java.type() inside of Java and then pass them to the script I want to execute.

How can I do this?

Thanks in advance!

Upvotes: 0

Views: 1925

Answers (2)

A. Sundararajan
A. Sundararajan

Reputation: 4405

You may want to avoid using the "internal" classes in packages like "jdk.internal.", "jdk.nashorn.internal.". In jdk9, dynalink is an API ("jdk.dynalink" has exported packages). In jdk9, you can call jdk.dyanlink.beans.StaticClass.forClass(Class) [ http://download.java.net/java/jdk9/docs/jdk/api/dynalink/jdk/dynalink/beans/StaticClass.html#forClass-java.lang.Class- ] to construct "type" objects and expose those as global variables to the script engine. For jdk8, you could pre-eval a script that uses Java.type(String) calls before evaluating "user" scripts. You can also call "Java.type" function from Java code.

Solution for jdk9:

import jdk.dynalink.beans.StaticClass;
import javax.script.*;

public class Main {
   public static void main(String[] args) throws Exception {
     ScriptEngineManager m = new ScriptEngineManager();
     ScriptEngine e = m.getEngineByName("nashorn");
     e.put("AList", StaticClass.forClass(java.util.ArrayList.class));
     e.eval("var al = new AList(); al.add('hello'), al.add('world')");
     e.eval("print(al)");
   }
}

Solution for jdk8:

import javax.script.*;

public class Main {
   public static void main(String[] args) throws Exception {
     ScriptEngineManager m = new ScriptEngineManager();
     ScriptEngine e = m.getEngineByName("nashorn");
     // eval a "boot script" before evaluating user script
     // Note that this script could come from your app resource URL
     e.eval("var AList = Java.type('java.util.ArrayList')");
     // now evaluate user script!
     e.eval("var al = new AList(); al.add('hello'), al.add('world')");
     e.eval("print(al)");
   }
}

Alternative solution for jdk8:

import javax.script.*;
import jdk.nashorn.api.scripting.*;

public class Main {
   public static void main(String[] args) throws Exception {
     ScriptEngineManager m = new ScriptEngineManager();
     ScriptEngine e = m.getEngineByName("nashorn");

     // get Java.type function as object
     JSObject javaTypeFunc = (JSObject) e.eval("Java.type");
     // you can javaTypeFunc from java code many times
     Object alType = javaTypeFunc.call(null, "java.util.ArrayList");
     // expose that as global
     e.put("AList", alType);

     // now evaluate user script!
     e.eval("var al = new AList(); al.add('hello'), al.add('world')");
     e.eval("print(al)");
   }
}

Upvotes: 1

fusionlightcat
fusionlightcat

Reputation: 385

After quite a bit of research I found a way to put global variables in the ScriptEngine before executing: The Java Scripting API (Oracle Docs)

This enabled me to put any object I want into a global variable. However, I still needed a way to get the Object that Java.type() creates inside of Java. So I wrote a test script which returns one of this objects and I found out it is an object of the type jdk.internal.dynalink.beans.StaticClass. This class has an constructor which takes a ordinary Class as an argument. Sadly, this constructor is not usable in my code because it is not visible. To bypass this I used reflection and made this method:

public StaticClass toNashornClass(Class<?> c) throws ClassNotFoundException, NoSuchMethodException, SecurityException, InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException{

    Class<?> cl = Class.forName("jdk.internal.dynalink.beans.StaticClass");

    Constructor<?> constructor = cl.getDeclaredConstructor(Class.class);

    constructor.setAccessible(true);
    StaticClass o = (StaticClass) constructor.newInstance(c);

    return o;
}

If I pass the Class of the object I want as a global variable I just need to call toNashornClass(Example.class); and put the resulting object into a global var with engine.put("example",object);

It works fine. I can use the example var completely like a var created by Java.type().

Upvotes: 0

Related Questions