View RSS Feed

JosAH's blog

Script Engines

Rate this Entry
by , 06-12-2011 at 10:07 AM (3043 Views)
Greetings,

Introduction

Java is not Javascript and most of the times when questions end up in the wrong forum, they're moved to another forum as soon as possible. Accidentally this article talks about Javascript a bit but it belongs in this Java section.

Since version 1.6 of Java a script engine comes packaged with the core classes. A script engine is an abstract little framework that offers support for other languages, scripting languages to be exact, a.k.a. interpreters.

Java version 1.6. comes bundled with one scripting language: Javascript. The next paragraphs decribe what you can do with this little framework and how you can do it.

ScriptEngineManager

A ScriptEngineManager manages (sic) ScriptEngines. A ScriptEngine is Java's interface to a scripting language. The manager doesn't know how to install, or instantiate, an engine itself: it uses so called ScriptEngineFactories for that purpose. So if you want to obtain a ScriptEngine you ask the manager for it; the manager checks if one of its factories can instantiate the appropriate engine; if so, it asks the factory to do so and the manager returns the new ScriptEngine to the caller.

Note that in this simple case we, the programmers, didn't need to deal with the factories ourself. Here's a simple example

Java Code:
ScriptEngineManager manager= new ScriptEngineManager();
ScriptEngine engine= manager.getEngineByName("JavaScript");
try {
	engine.eval("print('Hello, world!')");
} catch (ScriptException ex) {
	ex.printStackTrace();
}
The first line instantiates the new manager. Line two indirectly instantiates a ScriptEngine for the Javascript language. The next lines put that ScriptEngine to work a bit; it's the obligatory "hello world!" program written in Javascript and executed (or "interpreted") from the Java environment.

ScriptEngineFactory

The factories are implementations of this interface. Factories can produce the ScriptEngines (see previous paragraph). Let's see what those factories can tell us; here's an example:

Java Code:
ScriptEngineManager manager= new ScriptEngineManager();
List<ScriptEngineFactory> factories= manager.getEngineFactories();

for (ScriptEngineFactory factory: factories) {

	String name = factory.getEngineName();
	String version = factory.getEngineVersion();
	String language = factory.getLanguageName();

	System.out.println(name+"("+version+"): "+language);

	for(String n: factory.getNames()) 
      		System.out.println(n);
}
This example instantiates a ScriptEngineManager again and asks it for a List of available ScriptEngineFactories. For every factory its information is retrieved and printed. Note that a language doesn't just have one name, the language is also known by zero or more aliases; the inner loop shows the alias names too, if available.

Read the API documentation to see what more a ScriptEngineFactory can do for you. When I run that little snippet on my laptop this is the output:

Java Code:
Mozilla Rhino(1.6 release 2): ECMAScript
js
rhino
JavaScript
javascript
ECMAScript
ecmascript
There's only one factory installed for the 'ECMAScript' language; that's the old name of Javascript; the ScriptEngine implementation is Mozilla Rhino. The language is also available under the alias names, "js", "rhino", "Javascript", and a few other variations.

How does the ScriptEngineManager know which ScriptEngineFactories are available? It uses quite a new naming convention for that: the jars that contain factories (and the engines) must be stored in a special directory and the manager checks that directory for the jars and dynamically figures out which factories are in those jars. A detailed discussion of this mechanism is beyond the scope of this article. Maybe in the near future I'll build a factory and an engine for the little expression language presented in a previous article series.

ScriptEngine

A ScriptEngine is Java's gateway to a particular script interpreter. But where does that script come from? There are two ways to feed a script to the engine: pass it a Reader or pass it a String. When reading from the Reader the script is supposed to be read. The Reader can be any Reader: wrapped around a socket InputStream, or maybe just a FileReader. The script can come from anywhere. The alternative is just to pass the entire script text as a String.

A script maintains 'bindings'. A binding is nothing more than a Map<String, Object>, i.e. it associated Strings with objects. The engine uses those bindings for its scripts. Here's a small example:

Java Code:
ScriptEngineManager manager = new ScriptEngineManager();
ScriptEngine engine = manager.getEngineByName("JavaScript");
        
try {
	String expression = "a+b";
	engine.put("a", 41);
	engine.put("b", 1);
	Object result = engine.eval(expression);
	System.out.println(expression+"= "+result);
	System.out.println("type: "+result.getClass().getCanonicalName());
} catch(ScriptException se) {
	se.printStackTrace();
}
Both 'engine.put()' method calls put a new binding in the engine's bindings: a=41 and b=1. When I run this code snippet on my laptop I see this:

Java Code:
a+b= 42.0
type: java.lang.Double
That ScriptEngine was smart enough to convert a Javascript '42' to a Java Double object. It was also smart enough to convert Java's ints '41' and '1' to the correct objects for Javascript so that it can evaluate 'a+b'. That's cute.

As a matter of fact, a ScriptEngine can convert all sorts of Java objects to the script's representation thereof and back again to Java's representation.

The Javascript engine can even directly use Java objects; I bluntly 'borrowed' the following example from a piece of Sun's text on the same topic:

Java Code:
ScriptEngineManager manager = new ScriptEngineManager();
ScriptEngine engine = manager.getEngineByName("JavaScript");
        
try {
	engine.eval(
		"importPackage(javax.swing);" +
		"var pane = " +
		"JOptionPane.showMessageDialog(null, 'Hello, world!');");
} catch (ScriptException ex) {
	ex.printStackTrace();
}
Invocable

Script engines don't just read, parse and interpret source text; they compile
the script text to their internal form. Such engines implement another interface,
the Invocable interface.

Here's an example showing how this interface can be used:

Java Code:
ScriptEngineManager manager = new ScriptEngineManager();
ScriptEngine engine = manager.getEngineByName("JavaScript");
        
try {
	String expression = "function add(x, y) { return x+y; }";
	engine.eval(expression);
           
	engine.put("a", 41);
	engine.put("b", 1);

	Invocable invocable= (Invocable)engine;
            
	System.out.println("add(1, 2)= "+invocable.invokeFunction("add", 1, 2));
	System.out.println("add(a, b)= "+invocable.invokeFunction("add", "a", "b"));
	System.out.println("add(a, b)= "+engine.eval("add(a, b)"));

} catch(ScriptException se) {
	se.printStackTrace();
} catch (NoSuchMethodException nsme) {
	nsme.printStackTrace();
}
First the small script "function add(x, y) { return x+y; }' is compiled and two bindings are added. Next the ScriptEngine is cast to an Invocable. If the cast fails (it doesn't in this example), an exception is thrown indicating that the particular script cannot compile the script and keep it for later use.

When I ran this little code snippet, this was the output:

Java Code:
add(1, 2)= 3.0
add(a, b)= ab
add(a, b)= 42.0
Note that the second invocation tried to add the String values "a" and "b", not the values present in the bindings. The result is the String "ab" showing that Javascript concatenates strings when the '+' operator is applied to them.

The third call properly uses the bound values for a and b again.

Concluding remarks

There's much more that can be done with this quite new little framework. The framework is the result of all the work done by JSR 223 and at this very moment they're working on implementations for the Ruby language, the BeanShell interpreter and other languages as well. This article showed the basic usage and the structure of the ScriptEngine framework. Have fun with it.

kind regards,

Jos

Submit "Script Engines" to Facebook Submit "Script Engines" to Digg Submit "Script Engines" to del.icio.us Submit "Script Engines" to StumbleUpon Submit "Script Engines" to Google

Updated 06-12-2011 at 10:10 AM by JosAH

Tags: None Add / Edit Tags
Categories
Uncategorized

Comments

  1. sunde887's Avatar
    • |
    • permalink
    Interesting read. Is it possible to make any interpreted language usable via script engines?
  2. JosAH's Avatar
    • |
    • permalink
    Yep (sort of); e.g. see the RPL sources: there's a ScriptEngine available for that language.

    kind regards,

    Jos