analytics

Tuesday, March 29, 2011

Scripting Language Support in Java SE 6 (Mustang)

You can now mix in JavaScript technology source code, useful for prototyping. Also useful when you have teams with a variety of skill sets. More advanced developers can plug in their own scripting engines and mix their favourite scripting language in with Java code as they see fit.


J2SE 1.6 includes a modified version of the Rhino JavaScript engine integrated via JSR 223 and a command-line scripting shell, jrunscript, which leverages this support. The shell is of limited use in most applications, but can be a valuable tool in learning and prototyping in Java and arbitrary scripting languages. The jrunscript syntax is:

Usage: jrunscript [options] [arguments...]


Accessing the Scripting Engine

The fundamental interface for all scripting engines is javax.script.ScriptEngine. This interface defines a set of methods expected to be supported by all implementations. An implementation is obtained from the ScriptEngineManager. ScriptEngine implementations are deployed as Jar files (typically in lib/ext). Each such jar contains a text file resource META-INF/services/javax.script.ScriptEngineFactory defining one or more classes implementing ScriptEngineFactory. The ScriptEngineManager utilizes these factories to create specific implementations. The ScriptEngineManager also provides metadata defining the supported script type and version information which is used to resolve requests for a scripting implementation. Obtaining an engine is straightforward:

ScriptEngineManager manager = new ScriptEngineManager();
ScriptEngine jsEngine = manager.getEngineByName("js");

The primary method of the ScriptEngine, eval(), executes a script directly passed as a String or accessed through a Reader. These two variations may be additionally parameterized with either a Bindings or ScriptContext object.

Object eval(String script)
Object eval(Reader reader)
Object eval(String script, Bindings n)
Object eval(Reader reader, Bindings n)
Object eval(String script, ScriptContext context)
Object eval(Reader reader, ScriptContext context)


A ScriptContext associates a ScriptEngine with objects in the Java application. Every engine has a default ScriptContext (which can be altered through the API). It groups objects by scope. Each scope has a related Bindings instance containing these objects. Bindings is simply a Map which associates scripting variable names with Java instances.

Two scopes are predefined, ScriptContext.GLOBAL_SCOPE and ScriptContext.ENGINE_SCOPE. The first contains attributes shared by all Engines created via a given factory, while the second is specific to the engine implementation. The default context and scope is used by the single argument eval() methods. The Scripting API includes many instances of such convenience methods. A binding or context passed to an eval effectively replaces the default bindings or context for the scope of the method call.

Additional scopes may be defined by contexts specific to an Engine. For example, the HttpScriptContext used in web-based applications defines request, session, and applications scopes corresponding to Servlet scopes.

The below example summarizes basic usage of the Scripting API:

// simple script execution
jsEngine.eval("print('Hello')");

// bind scripting variables and use in a script
jsEngine.put("a", 3);
Integer b = 2;
jsEngine.put("b", b);
Object retval = jsEngine.eval("c = a + b; print('a + b = ' + c);");

// retrieve the bindings we previously added
Bindings bindings = jsEngine.getBindings(ScriptContext.ENGINE_SCOPE);
boolean containsKey = bindings.containsKey("a");
System.out.println(containsKey);

Results in:

Hello
a + b = 5
true



The checked ScriptException is thrown by methods of the Scripting API. Depending on the Engine, the errant line and column number of the script will be included in the exception.

Compilable and Invocable

Beyond the based ScriptEngine capability, certain implementations may advertise additional capability by implementing the Compilable or Invocable interfaces.

An engine implementing Compilable supports the re-execution of intermediate (e.g. compiled) code from previous compilations. The compile() method returns a CompiledScript based on either a String or Reader. The CompiledScript may be executed repeatedly via its eval() method.

Compilable eng = (Compilable)jsEngine;
CompiledScript script = eng.compile("function test() { return 10; }; test();");
Object compiledOutput = script.eval();
System.out.println(compiledOutput); // prints 10.0

An engine implementing Invocable supports the calling of individual functions scripted in the engine via the invoke() method. Arguments and return values are converted between script and Java types based on the engine implementation. Below is a simple example:

jsEngine.eval("function test(val) { return val; }");
Invocable jsInvoke = (Invocable)jsEngine;
Object invoke = jsInvoke.invoke("test", new Object[] {111.0});
System.out.println(invoke); // prints 111.0

Scripts may be used to implement arbitrary interfaces without direct dependence on the given interface. The following example illustrates definition of a run() method which is bound as the implementation of a Runnable interface at runtime based only on method signature. Below, the invoke() and run() calls invoke the same underlying scripted method.

jsEngine.eval("function run() { print ('running..'); }");
Object invoke2 = jsInvoke.invoke("run",null); // prints running...

Runnable r = jsInvoke.getInterface(Runnable.class);
r.run(); // prints running...

No comments:

Post a Comment