Introduction
Java is not Javascript and most of the times when questions end up in the wrong
forum, they're moved to the other forum as soon as possible. Accidentally this
article talks about Javascript a bit but it belongs in the 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
Expand|Select|Wrap|Line Numbers
- ScriptEngineManager manager= new ScriptEngineManager();
- ScriptEngine engine= manager.getEngineByName("JavaScript");
- try {
- engine.eval("print('Hello, world!')");
- } catch (ScriptException ex) {
- ex.printStackTrace();
- }
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:
Expand|Select|Wrap|Line Numbers
- 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);
- }
of available ScriptEngineFactories. For every factory it's information is
retrieved and printed. Note that a language doesn't just have a 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:
Expand|Select|Wrap|Line Numbers
- Mozilla Rhino(1.6 release 2): ECMAScript
- js
- rhino
- JavaScript
- javascript
- ECMAScript
- ecmascript
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:
Expand|Select|Wrap|Line Numbers
- 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();
- }
a=41 and b=1. When I run this code snippet on my laptop I see this:
Expand|Select|Wrap|Line Numbers
- a+b= 42.0
- type: java.lang.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:
Expand|Select|Wrap|Line Numbers
- 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();
- }
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:
Expand|Select|Wrap|Line Numbers
- 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, 41)= "+invocable.invokeFunction("add", 1, 41));
- 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();
- }
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 run this little code snippet, this is the output:
Expand|Select|Wrap|Line Numbers
- add(1, 41)= 42.0
- add(a, b)= ab
- add(a, b)= 42.0
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. I hope to see you all again
next week and
kind regards,
Jos