Java ScriptEngine: using value on Java side?

12,933

Solution 1

Except perhaps for simple values, I would rather let the scripting engine coerce its values to Java types.

public class ScriptDemo {

  static class Result {
    private String[] words;

    public void setWords(String[] words) {
      this.words = words;
    }
  }

  static final String SCRIPT = "var foo = 'Hello World!';\n"
      + "result.setWords(foo.split(' '));";

  public static void main(String[] args)
      throws ScriptException {
    Result result = new Result();
    javax.script.ScriptEngineManager mgr = new ScriptEngineManager();
    javax.script.ScriptEngine scripEngine = mgr
        .getEngineByExtension("js");
    scripEngine.getContext().setAttribute("result", result,
        ScriptContext.ENGINE_SCOPE);
    scripEngine.eval(SCRIPT);
    System.out.println(Arrays.toString(result.words));
  }

}

Even if you can't edit the script, you could take the return value and pass it through your own generated script to do the coercion. This assumes you know something about the value being returned.


EDIT: since nothing is known about the return value, I would first test it using Java (getClass()) to see if it was one of the java.lang types. If the returned object is from some API private to the library, I would introspect it using the scripting language (in this case JavaScript), possibly coercing it to a Java type or pushing its properties into some Java data structure during the process.

My JavaScript is rusty, but John Leach's tutorial looks quite good: JavaScript Introspection.

(You may be able to use Java reflection, but since the engine implementation could vary between Java versions/JREs/JavaScript engines, I wouldn't rely on it.)

Solution 2

This question was asked ages ago, but the answers still seemed correct. In case it's of interest to anyone else trying to pass complex objects between Java and Javascript, I wanted to present my solution.

I wrote a script that converts the NativeObject to (in-memory) JSON objects (actually I use MongoDB's BSON-based objects, but you should just be able to substitute 1-1 for JSONArray and JSONObject in the sample code below).

So for example, say I have a (user) script "create_object_script", which "returns" some object or array of interest, then I can convert it into JSON (ie a list of hashmaps) as follows:

Object returnVal = engine.eval(create_object_script);
engine.put("output", returnVal);
BasicDBObject objFactory = new BasicDBObject(); // (or JSON equivalent)
BasicDBList listFactory = new BasicDBList(); // (or JSON equivalent)
BasicDBList outList = new BasicDBList(); // (or JSON equivalent)
engine.put("objFactory", objFactory);
engine.put("listFactory", listFactory);
engine.put("outList", outList);
engine.eval(parsing_script); // (described below)
// "outList" is now populated with (in-memory) JSON representations of "returnVal"

Obviously if you have control over the "create_object_script" script you can do this in a single step; my scripts are user-generated so hiding away this complexity is necessary - the users just write scripts where the "return value" is the final line.

I gisted the "parsing_script" here to keep the length of this post down.

Functionally this works very nicely; I haven't developed in JS much so it's possible there are more efficient ways of doing this. (Note I always need my results in a list, if you don't then you could pass in a BasicDBObject "outObj" and write to that instead in the singleton case).

Hope this helps someone who finds themselves in my situation at 1am last night!

Share:
12,933
LabRat01010
Author by

LabRat01010

Bioinformatician Virology Genetics Biology Science Science2.0 Web2.0 Bioinformatics Genotyping Wikipedia

Updated on June 23, 2022

Comments

  • LabRat01010
    LabRat01010 almost 2 years

    In a Java program I'm invoking a user-defined JavaScript program:

    File userJSFile=...;
    javax.script.ScriptEngineManager mgr=new  ScriptEngineManager();
    javax.script.ScriptEngine scripEngine= mgr.getEngineByExtension("js");
    Object result=scripEngine.eval(new java.io.FileReader(userJSFile));
    

    Now I would like to use 'result': how can I have an access to it? Can I identify it as an array (Can I iterate threw its members), a String, an Integer, etc... ?

    Thanks

    EDITED: I just know that my user gave me a script returning the last value. I don't know anything about this value. Is it a String, an array, etc.. ? I don't known but I want to use it.