This article is more than 1 year old

Writing the Script: Java SE 6

A standard Java scripting engine API to play with

Over the years there have been several times where I've had to implement a mini-language and language processor for some specific requirement or another.

In one case, it was to provide a simple scripting language to allow for customisation of a Management Information System, in another to provide support for a Decision Tree based Expert System style application.

In both cases, a simple language was defined and a simple interpreter written. Both systems worked well within their respective applications; but neither was really specific to that application and both were, in fact, simple scripting engines.

In an ideal world, rather than invent our own scripting engine we could have used an existing engine, one that supported a well-known or standardised language. Such engines are now available and indeed implementations for Java can be obtained both commercially and as open source projects. However, such engines each tend to have their own interface and standards.

Java Standard Edition 6.0 introduces a new API that supports a standard interface to any compliant scripting engine. This feature allows different scripting engines to be plugged in, but all are accessed in the same way, through a standard API.

Scripting in Java

Java Standard Edition 6.0 Platform introduces this new API in the javax.script package. This package provides a standard API that allows access to, and execution of, JSR 223-compliant scripting engines. JSR 223 is the Java Specification Request for a scripting API within Java; it describes "mechanisms allowing scripting language programs to access information developed in the Java Platform and allowing scripting language pages to be used in Java Server-side Applications".

Java SE 6.0 doesn't specify that any particular Scripting Engine must be made available; instead it provides a default JavaScript engine (Mozilla Rhino) and allows other engines to be deployed as long as they meet the requirements of the JSR.

The Scripting API

The Scripting API in javax.script consists of 6 interfaces and 6 classes (one of which is abstract), which combine to provide its standard script engine access, instantiation and execution features.

Table 1 The classes and interfaces in the javax.script package.

Interface Class
Bindings AbstractScriptEngine
Compilable CompiledScript
Invocable ScriptEngineManager
ScriptContext SimpleBindings
ScriptEngine SimpleScriptContext
ScriptEngineFactory ScriptException

In the following, we will look at how to use some of these classes and interfaces within a simple Java application.

Working with the ScriptingEngineManager

In the class presented in Listing 1, the first thing we do is to obtain a reference to the ScriptEngineManager object. A ScriptEngineManager is responsible both for the discovery of available script engines and also for access to instances of those script engines. This class is used to list all of the implementations of the ScriptEngineFactory currently installed within the JVM. Each script engine available should have a script engine factory that can be used to create instances of it. Note that as the scripting features are only available within Java SE 6 we are using generic types to ensure type safety within this example.

A ScriptEngineFactory, in turn, is used to provide information on a ScriptEngine (such as the version of the script engine and the language it supports) as well as to provide access to an instance of that scripting engine. This is illustrated in the for loop within the example in Listing 1. Here, we retrieve the engine name and version number as well as the supported language and language version, and print this out.

Listing 1: List of ScriptTest1.java

package com.reg.dev.script;

import java.util.List;

import javax.script.ScriptEngineFactory;
import javax.script.ScriptEngineManager;

public class ScriptTest1 {

    public static void main(String[] args) {
        // Obtain the manager object
        ScriptEngineManager mgr = new ScriptEngineManager();
          // Obtain a list of factories
          List<ScriptEngineFactory> list = mgr.getEngineFactories();
          System.out.println("Supported Script Engines");
        for (ScriptEngineFactory factory: list) {
            // Obtains the full name of the ScriptEngine.
                String name = factory.getEngineName();
                String version = factory.getEngineVersion();
                // Returns the name of the scripting language 
            // supported by this ScriptEngine
                String language = factory.getLanguageName();
                String languageVersion = factory.getLanguageVersion();
                System.out.printf(
                 "Name: %s (%s) : Language: %s v. %s \n", 
                    name, version, language, languageVersion);
           // Get a list of aliases
             List<String> engNames = factory.getNames();
                 for(String e: engNames) {
                       System.out.printf("\tEngine Alias: %s\n", e);
                   }
             }
        }

}

The final part of Listing 1 illustrates that a particular scripting engine may have a number of short names used to represent the language it supports. For example, "js", javascript", "JavaScript" or "ECMAScript" may all be used as short names (or aliases) for the same scripting language. As a result, the same scripting engine may be accessed (as illustrated in listing 2) using various different names. The output from ScriptTest1, below, illustrates the range of names used for the default Mozilla Rhino scripting engine included with the Java SE 6.0 release.

Output from ScriptTest1:

Supported Script Engines
Name: Mozilla Rhino (1.6 release 2) : Language: ECMAScript v. 1.6 
        Engine Alias: js
        Engine Alias: rhino
        Engine Alias: JavaScript
        Engine Alias: javascript
        Engine Alias: ECMAScript
        Engine Alias: ecmascript

An important point to note is that in the example used in this article, there is only a single Script Engine available - the Mozilla Rhino (1.6 release 2) script engine - because this is the only scripting engine included in the Java SE 6 platform libraries. However, as I've pointed out earlier, this doesn't mean that it's the only possible scripting engine and others can be deployed within a JVM as required.

Creating a Java Script Engine

Now that we know that the ScriptEngineManager can be used to find out meta data about the scripting engines available, how do we go about obtaining one of these? This can be done in one of two ways.

Firstly, we can use one of the ScriptEngineFactory objects obtained form the ScriptEngineManager (as illustrated in listing 1) and use this getScriptEngine() method to return an instance of a ScriptEngine.

Alternatively, we can use the ScriptEngineManager and request that it provide a ScriptEngine (of those currently installed) that supports a particular language extension, or has a particular name. For example: ScriptEngine jsEngine = mgr.getEngineByName("JavaScript");. This latter approach is somewhat similar to that used with JDBC; where the DriverManager can be used to obtain a driver that supports a particular protocol (such as ODBC).

The approach is also used within the following listing; as it is more generic and does not rely on the presence of a particular scripting engine (merely on the availability of one that supports JavaScript).

Listing 2: Processing some JavaScript

package com.reg.dev.script;

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

public class ScriptTest2 {
    public static void main(String[] args) {
        ScriptEngineManager mgr = new ScriptEngineManager();
          // Now we can go and get a script engine we want. 
          // This can be done either by finding a factory that supports 
          // our required scripting language 
        // (engine = factory.getScriptEngine();)
          // or by requesting a script engine that supports a 
        // given language by name from the script engine manager.
          ScriptEngine engine = mgr.getEngineByName("JavaScript");
        
        // Now we have a script engine instance that 
        // can execute some JavaScript
        try {
            engine.eval("print('Hello, world!')");
          } catch (ScriptException ex) {
            ex.printStackTrace();
        }    
    }
}

Now that we have an instance of the script engine (i.e. a ScriptEngine that supports JavaScript) we are in a position to execute some JavaScript. This is discussed below.

Running a piece of JavaScript

To execute some JavaScript, we can use the eval method defined on the ScriptEngine interface. The eval method is overloaded to take both a string and a reader and to provide additional data (such as bindings for values passed into the JavaScript from the encompassing environment). This method also returns an object that can be used to obtain information about the result of processing the JavaScript.

A simple example of executing JavaScript can be illustrated using a simple string containing some JavaScript - see this in context in Listing 2; and as a code snippet below:

Code snippet: Executing a JavaScript string

                try {
                        engine.eval("print('Hello, world!')");
                } catch (ScriptException ex) {
                        ex.printStackTrace();
                }    

The output from running the class ScriptTest2 is presented below. Note that the output goes to the standard output, in this case:

Hello, world!

Executing a JavaScript file

Of course, executing a hardcoded string is one thing, but in general, we want to be more flexible than that. For example, we may wish to allow system customizers to write JavaScript files that are loaded into an application, and modify its behaviour at runtime. This can be done very simply with the eval method. As well as taking a String, the eval method can take a Stream Reader.

Although, on its own, this may not seem particularly useful, a Reader is an abstract super class for a number of more useful classes that allow you to read form any appropriate data source (including streams and files). Thus to read in a file containing JavaScript and then execute it all you need to do is to create a FileInputStream and wrap this inside an InputStreamReader. This can then be passed to the eval method and used to read in and evaluate the JavaScript file. This is illustrated in Listing 3.

Listing 3: Processing a JavaScript file

package com.reg.dev.script;

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.InputStreamReader;
import java.io.Reader;

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

public class ScriptTest3 {
    public static void main(String[] args) {
        ScriptEngineManager mgr = new ScriptEngineManager();
        ScriptEngine engine = mgr.getEngineByName("JavaScript");

        // Now we will read in some Javascript from a file
        try {
            FileInputStream fis = new FileInputStream("Primes.js");
            Reader reader = new InputStreamReader(fis);
                engine.eval(reader);
        } catch (ScriptException ex) {
            ex.printStackTrace();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }
    }
}

The class in Listing 3 loads the following JavaScript file (which calculates prime numbers):

Listing 4: Primes.js JavaScript file

function calcPrimeNumbers(limit) {
        for (var i = 2; i < limit; i+=1) {
                for (var j = 2; j*j <= i; j+=1) {
                        rem = i%j;
                        if (rem == 0.0)
                                break;
                }
                if (j*j > i) {
                        print(i + ", ");
                }
        }
}
calcPrimeNumbers(100);

The result from running ScriptTest3 in Listing 3 is that the JavaScript in Primes.js is executed and produces the following output:

2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97,

Summary

As can be seem from the simple examples in this column, it is very easy to integrate JavaScript programs into your Java applications using the ScriptEngine facilities in Java SE 6.0. In fact, the approach works for other script engines, as any ScriptEngine that complies with JSR 223 can be integrated with Java in this way and thus any scripting language with a compliant Script Engine can now be integrated into Java applications. ®

More about

TIP US OFF

Send us news


Other stories you might like