ONJava.com    
 Published on ONJava.com (http://www.onjava.com/)
 See this if you're having trouble printing code examples


The Mustang Meets the Rhino: Scripting in Java 6

by John Ferguson Smart
04/26/2006

The latest major Java release (Java SE 6, aka Mustang), is now in its beta version. Although this new version is not as major an update as Java 5, it does come with a few interesting new features. Undoubtedly, one of these is its support for scripting languages.

Scripting languages such as PHP, Ruby, JavaScript, Python (or Jython), and the like are widely used in many domains, and are popular because of their flexibility and simplicity. Scripts are interpreted, not compiled, so they can be easily run and tested from the command line. This tightens the coding/testing cycle, and increases developer productivity. They are generally dynamically typed, and have expressive syntaxes that allow an equivalent algorithm to be written much more concisely than in Java. They are also often fun to work with.

Using scripting languages from Java can be useful in many situations, such as providing extensions to your Java application so that users can write their own scripts to extend or customize the core functionalities. Scripting languages are both simpler to understand and easier to write, so they can be ideal to give (technical) end users the possibility to tailor your product to their needs.

Many independent scripting packages have been available for Java for some time, including Rhino, Jacl, Jython, BeanShell, JRuby, and others. The novelty is that Java 6 provides built-in support for scripting languages via a standard interface.

Java 6 provides integrated support of the JSR-223 specification. This spec provides a convenient, standard way to execute scripting languages from within Java and provides access to Java resources and classes from within scripts. Java 6 comes with a built-in integration of Mozilla's Rhino implementation of JavaScript. And based on this spec, support for other scripting languages such as PHP, Groovy, and BeanShell, is in the pipelines. Rhino implementation is the focus for this article, but other languages should be essentially the same.

Where do scripting language names come from? Well, since most scripting languages are generated from open source projects, the names generally are the product of the imagination of their respective authors. Rhino gets its name from the animal on the cover of the O'Reilly book about JavaScript. PHP, in the self-referential Unix tradition, is short for PHP: Hypertext Preprocessor. Jython is a Java implementation of the Python scripting language. And Groovy just seemed like a cool name.

Using the Scripting Engine

The JSR 223 specifications are convenient and easy to use. To work with scripts, you only need to know a couple of key classes. The main one is the ScriptEngine class, which handles script interpreting and evaluation. To instantiate a script engine, you use the ScriptEngineManager class to retrieve a ScriptEngine object for the scripting language you're interested in. Each scripting language has a name. The Mozilla Rhino ECMAScript scripting language (commonly known as JavaScript) is identified by "js".



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

Embedded JavaScript can be used for a variety of purposes. Since it is more flexible and easier to configure than hard-coded Java, it can often be used to code business rules that may change often. Scripting expressions are evaluated using the eval() method. Any variables used in the scripting environment can be assigned from within your Java code using the put() method.



        ScriptEngineManager manager
            = new ScriptEngineManager();
        ScriptEngine engine
            = manager.getEngineByName("js");
        engine.put("age", 21);
        engine.eval(
            "if (age >= 18){ " +
                        "    print('Old enough to vote!'); " +
                        "} else {" +
                        "    print ('Back to school!');" +
                        "}");

        > Old enough to vote!

The eval() method also accepts a Reader object, which makes it easy to store scripts in files or other external sources, as in the following example:


        ScriptEngineManager manager
            = new ScriptEngineManager();
        ScriptEngine engine
            = manager.getEngineByName("js");
        engine.put("age", 21);
        engine.eval(new FileReader("c:/voting.js"));
JavaScript: The Definitive Guide

Related Reading

JavaScript: The Definitive Guide
By David Flanagan

Retreiving Results

So now you can run a script, what next? You will generally want to fetch calculated values or expressions from the scripting environment for use in your Java code. There are two ways to do this. The first one is that the eval() function returns the value returned by the execution of the script. By default, this is the value of the last executed expression.

The following example illustrates the premium calculations for an imaginary insurance company. Any driver less than 25 years of age will pay 50% extra. If a driver over 25 has a no claims bonus, the company offers a 25% discount. Otherwise, the standard premiums will apply. This rule could be implemented using a JavaScript expression as follows:


        ScriptEngineManager manager
                   = new ScriptEngineManager();
        ScriptEngine engine
                   = manager.getEngineByName("js");
        engine.put("age", 26);
        engine.put("noClaims", Boolean.TRUE);
        Object result = engine.eval(
            "if (age < 25){ " +
                        "    riskFactor = 1.5;" +
                        "} else if (noClaims) {" +
                        "    riskFactor = 0.75;" +
                        "} else {" +
                        "    riskFactor = 1.0;" +
                        "}");
        assertEquals(result,0.75);
    }

The returned value is the value of the last executed instruction, so in this case it will be the value assigned to riskFactor. Note that the name of the JavaScript variable that contains the result (in this case, riskFactor) is irrelevant: only the value is returned.

The second way to interact with the script is to use a Bindings object. A Bindings object is essentially a map of key/value pairs that can be used to exchange information between your Java application and the JavaScript script.


    public void testEvalWithBindings()
                          throws ScriptException {
        ScriptEngineManager manager
                 = new ScriptEngineManager();
        ScriptEngine engine
                 = manager.getEngineByName("js");
        Bindings bindings
                 = engine.createBindings();
        bindings.put("age", 26);
        bindings.put("noClaims", Boolean.TRUE);
        bindings.put("riskFactor", 1);

        engine.eval(
            "if (age < 25){ " +
            "    riskFactor = 1.5;" +
            "} else if (noClaims) {" +
            "    riskFactor = 0.75;" +
            "} else {" +
            "    riskFactor = 1.0;" +
            "}");

        double risk = bindings.get("riskFactor");
        assertEquals(risk,0.75);
    }

Accessing Java Resources

You can also access Java classes and resources from within scripts. The Rhino JavaScript engine supports the importPackage() function, which allows you to import Java packages. Once imported, you can instantiate Java objects within the script just as you would in Java:


        engine.eval("importPackage(java.util); " +
                    "today = new Date(); " +
                    "print('Today is ' + today);");

Calling methods on Java classes is also easy to do, both on object instances passed to the script engine, and on static class members.


        engine.put("name","John Doe");
        engine.eval(
            "name2 = name.toUpperCase();" +
            "print('Converted name = ' + name2);");
        > Converted name = JOHN DOE

Compilable and Invocable Engines

Some script engine implementations support script compilation, which allows considerable performance gains. Scripts can be compiled and reused, rather than being interpreted at each execution. The compile() method returns a CompiledScript instance, which can then be used to evaluate the compiled expression via the eval() method:


        ScriptEngineManager manager
                 = new ScriptEngineManager();
        ScriptEngine engine
                 = manager.getEngineByName("js");
        Compilable compilable
                 = (Compilable) engine;

        CompiledScript script
            = compilable.compile(
                 "if (age < 25){ " +
                 "    riskFactor = 1.5;" +
                 "} else if (noClaims) {" +
                 "    riskFactor = 0.75;" +
                 "} else {" +
                 "    riskFactor = 1.0;" +
                 "}");

        Bindings bindings
            = engine.createBindings();
        bindings.put("age", 26);
        bindings.put("noClaims", Boolean.TRUE);
        bindings.put("riskFactor", 1);
        script.eval();

The equivalent Java code would be something along the following lines:


    public double calculateRiskFactor(int age, boolean noClaims) {
        double riskFactor;
        if (age < 25) {
            riskFactor = 1.5;
        } else if (noClaims) {
            riskFactor = 0.75;
        } else {
            riskFactor = 1.0;
        }
        return riskFactor;
    }

How much is to be gained by script compilation is something you need to evaluate and benchmark in each particular circumstance. Some simple benchmarks using the script illustrated here show performance gains of around 60%. In general, the more complex the script, the more you can expect to gain from compilation. Just as a rough test, I ran the above script 10,000 times, along with the equivalent Java code shown above. I obtained the following cumulative results:

Interpreted JS: 1,550ms
Compiled JS: 579ms
Compiled Java: 0.0172ms

So, the compiled JavaScript ran roughly three times faster than the interpreted JavaScript. The interpreted code took an average of 15ms to run, whereas the compiled code averaged at about 6ms. Of course, as would be expected, real compiled Java code is roughly 100,000 times faster than the interpreted JavaScript. However, as mentioned above, the advantages of scripting languages to be found elsewhere.

The Invocable interface lets you call individual functions defined in the script from your Java code. The invoke() method takes the name of the function to be invoked, and an array of parameters, and returns the result of the call.


        ScriptEngineManager manager
            = new ScriptEngineManager();
        ScriptEngine engine
            = manager.getEngineByName("js");
        engine.eval(
          "function increment(i) {return i + 1;}");
        Invocable invocable = (Invocable) engine;
        Object result
            = invocable.invoke("increment",
                               new Object[] {10});
        System.out.print("result = " + result);
    > result = 11

This approach allows libraries to be written and maintained in JavaScript (or in some other scripting language), and called from a Java application. In business, it is important to be able to quickly update pricing policies that are subject to market pressures. An insurance company, for example, might want actuaries to be able to directly design and maintain insurance policies and premium calculation algorithms using an easy scripting language, which could then be invoked from within a larger J2EE enterprise architecture. Such an architecture might include an online quoting system, an extranet application for insurance brokers, as well as back-office business applications, all invoking the same centralized scripts.

Web Development

One of the more ambitious goals of the JSR 223 specification is to provide for integration of non-Java scripting pages (such as PHP) into a Java web application. This is designed to allow both non-Java scripting pages to be integrated as part of a Java web application, but also for Java classes to be invoked from the scripting pages. For example, the following PHP code shows how Java objects can be used from within a PHP page:


    //instantiate a java object

    $javadate = new Java( java.util.Date );

    //call a method
    $date =$javadate->toString();

    //display return value
    echo($date);

More importantly for integration into a Java web application server, the specification provides a standard API for accessing and modifying the servlet container session data:


    <ul>
    <?
      //display session attributes in table
      $enum=$request->getSession()->getAttributeNames();
      foreach ($enum as $name) {
        $value = $request->getSession()->getAttribute($name);
        print("<li>$name = $value<li>");
      }
    ?>
    </ul>

The implications of this integration are far-reaching. It is now possible to code web applications in a J2EE environment not only in Java, but also with other scripting languages, using Java as a powerful cross-platform architecture. And existing pages or applications written in other scripting languages can now be integrated with J2EE applications with little effort.

Conclusion

Scripting languages are hailed by some as the answer to all our programming woes and decried by others as encouraging unstructured and unmaintainable code. As with any tool, scripting can be used or abused. Script languages are flexible, easy to learn and fast to write. However, they have only limited support in Java IDEs, are difficult to test using traditional testing frameworks such as JUnit, and errors may not appear until runtime. Nevertheless, correct and appropriate use of scripting can certainly make life easier in many cases. Scripting should be considered in situations such as:

Overall, scripting support undoubtedly offers a rich new addition to the Java developer's toolbox.

Resources

John Ferguson Smart is a freelance consultant specializing in Enterprise Java, Web Development, and Open Source technologies, currently based in Wellington, New Zealand.


Return to ONJava.com.

Copyright © 2009 O'Reilly Media, Inc.