Scripting for the Java platform

Despite many developers do not know it, since Java SE 6 it is really easy to integrate Java and some scripting languages through a standard Java Scripting API (JSR-223 implementation)

Currently, both AppleScript (2.2.1) and ECMAScript (1.6) script engines are supported by default, at least on my machine. In the following example I invoke a Javascript function from Java, using Mozilla Rhino, the JavaScript implementation that ships with Java SE 6:

import javax.script.Invocable;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;

public class TestScript {
  public static void main(String[] args) throws Exception {
    ScriptEngineManager mgr = new ScriptEngineManager();
    ScriptEngine engine = mgr.getEngineByName("js");

    String script = "function sum(a, b) {" +
                    "   var result = a + b;" +
                    "   return result;" +
                    "}";
    engine.eval(script);

    Invocable invocableEngine = (Invocable) engine;
    Number result = 
            (Number) invocableEngine.invokeFunction("sum", 2, 3);
    System.out.println(result); // output = 5.0
  }
}

Reuse existing code

I find this “trick” useful in cases where you want to reuse existing scripting code. In the following example I use the great datejs library to parse dates written in natural language:

import java.io.InputStreamReader;
import java.io.Reader;

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

public class DateParser {
    private final ScriptEngine engine;

    public DateParser() throws ScriptException {
        ScriptEngineManager mgr = new ScriptEngineManager();
        engine = mgr.getEngineByName("js");

        Reader reader = new InputStreamReader(
                this.getClass().getResourceAsStream("/scripts/date.js"));
        engine.eval(reader);
        engine.eval(
            "function parse(value) { " +
            "    return Date.parse(value).toDateString();" +
            "};"
        );
    }

    public String parse(String naturalLanguageDate)
            throws ScriptException, NoSuchMethodException {
        Invocable invocableEngine = (Invocable) engine;
        return (String) invocableEngine.invokeFunction("parse", naturalLanguageDate);
    }

    public static void main(String[] args) throws Exception {
        DateParser parser = new DateParser();

        // output = Thu Nov 03 2011
        System.out.println(parser.parse("next thursday")); 

        // output  = Wed Nov 09 2011
        System.out.println(parser.parse("tomorrow + 10 days"));
    }
}

Well, that is all for the proof of concept I was looking for. If you want to read a more complete and deep article about this subject, I recommend Java Scripting Programmer’s Guide at Oracle.

Problems found

So far, the most problematic point I have found is the conversion from the Object returned by invokeFunction to the appropriate Java types. It is not hard when it is a simple type such as a Number or a String, but it is not so easy when it comes to a more complex type (Array, Date). This is why the parse function in the second example returns a String instead of a Date.

In my first attempt I tried to create a neural network with brain, a javascript supervised machine learning library. It would have been an interesting and even useful piece of code, but I failed miserably, probably because they use code not supported by ECMAScript 1.6.

A little benchmark would also be great, to determine the overhead introduced (if any) when you execute scripting code from Java, and compare it with a external/standalone script engine.


Hello. Call me Guido · Twitter · My bot (alpha) ·