Scripting Jython with JDK 6

Submitted By: Josh Juneau

Introduction

There are many features within the JDK 6 builds which are going to open new doors for Java programmers. Perhaps one of the most unique new features of JDK 6 is the inclusion of a scripting project which allows usage of countless java scripting languages within Java applcations. Sun's implementation of Java SE6 includes a script engine based upon [http://www.mozilla.org/rhino Rhino: JavaScript for Java]. However, this scripting framework has been implemented in such a way that it also supports third-party engines that implement [http://download.java.net/jdk6/docs/api/javax/script/package-summary.html JSR 223 Scripting APIs].

Of course, the topic for this article is the utilization of Jython within a Java application with the new JDK 6 scripting framework. The jython engine is available for download along with the other scripting language implementations on the [https://scripting.dev.java.net/ scripting.java.net website]. While the Jython scripting engine only supports release 2.1, it's utilization provides seamless integration within Java applications...with a minor effort.

Setting Up The Environment

To begin using the JDK 6 scripting engine, you will need to obtain a copy of JDK6. You can use [http://java.sun.com/javase/downloads/ea.jsp this link] to grab the latest release as of this writing. You'll also need to visit the [https://scripting.dev.java.net/ scripting.java.net website] mentioned previously and obtain a copy of the JSR 223 engines.

Once you've installed the JDK and set up the Jython engine, you are ready to begin using the scripting framework. A command line scripting environment is shipped along with JDK 6 and it is useful for testing scripts. However, you can just as easily use the jython interactive command console. Just in case you want to try your hand at using the scripting environment shipped with JDK 6, it is called jrunscript. You will need to use the following syntax in order to invoke a jython command shell using jrunconsole. Execute the following (plugging in your correct paths of course) from the command line:

<<path to jdk6 bin>>\jrunscript -cp <<path to jython jar>>; <<path to jython scripting engine>> -l jython

IDE Recommendation

In order to facilitate the usage of Jython within your Java application, I recommend using an IDE which provides some support for the Jython language. For instance, I use Netbeans along with the [https://coyote.dev.java.net/ Coyote] plugin. You may prefer to use Eclipse or something else as it is personal preference. Personally, I find it cumbersome to go from a Java IDE to a text editor for writing Jython scripts.

Utilizing Jython from Java

If you are testing this code, you must ensure that you have all of the appropriate packages imported first!

As stated previously, hrough the usage of this new JDK 6 scripting engine you can integrete Jython code with Java application code in a seamless manner. I have found some nuances within the documentation for JDK 6 because most of the docs use javascript examples. As I show you some examples, I will point out the differences that I have found in the implementations of Javascript and Jython so you do not run into the same issues.

Everything we will need to use for coding Java with Jython resides within the javax.scripting API. As stated earlier, the scripting API has support for a number of languages and this is orchestrated via the the ScriptEngineManager. In order to select a specified scripting engine to use, you'll first need to obtain the scripting engine from the [https://scripting.dev.java.net/ scripting.java.net website]. Once you've extracted the engine of your choice, in our case Jython, you'll have to ensure that the engine resides within your classpath. Once this task has been completed, it is easy to gain access to your scripting language.

To list all of the available engines, you can use the following method:

    public static void listEngines(){
        ScriptEngineManager mgr = new ScriptEngineManager();
        List<ScriptEngineFactory> factories =
                mgr.getEngineFactories();
        for (ScriptEngineFactory factory: factories) {
            System.out.println("ScriptEngineFactory Info");
            String engName = factory.getEngineName();
            String engVersion = factory.getEngineVersion();
            String langName = factory.getLanguageName();
            String langVersion = factory.getLanguageVersion();
            System.out.printf("\tScript Engine: %s (%s)\n",
                    engName, engVersion);
            List<String> engNames = factory.getNames();
            for(String name: engNames) {
                System.out.printf("\tEngine Alias: %s\n", name);
            }
            System.out.printf("\tLanguage: %s (%s)\n",
                    langName, langVersion);
        }
    }

And a sample result:

ScriptEngineFactory Info
        Script Engine: jython (2.1)
        Engine Alias: jython
        Engine Alias: python
        Language: python (2.1)
ScriptEngineFactory Info
        Script Engine: Mozilla Rhino (1.6 release 2)
        Engine Alias: js
        Engine Alias: rhino
        Engine Alias: JavaScript
        Engine Alias: javascript
        Engine Alias: ECMAScript
        Engine Alias: ecmascript
        Language: ECMAScript (1.6)

In the code below, I've created two engines and invoke an inline Jython script.

       ScriptEngineManager m = new ScriptEngineManager();
        //Create Jython Engine
        ScriptEngine jyEngine = m.getEngineByName("jython");
        //Create Javascript Engine
        ScriptEngine jsEngine = m.getEngineByName("js");
        try {
            jyEngine.eval("print 'hello jython!'");
        } catch (ScriptException ex) {
            ex.printStackTrace();
        }

The result (as expected):

hello jython!

Now that we have seen an easy inline script, we probably need to pass variables so we can actually perform a useful task. This is easily performed by invoking the put(String, value) method on the scripting engine you've created. Once you have assigned values to variables then you can also obtain the result by using the getBindings. For example, below I will perform simple addition of two values and obtain the result. Now you can see that this is a lot of code for such a minute task, but remember that this is just a simple example.

       jyEngine.put("a",6);
       jyEngine.put("b",7);
       try {
           // Invoke inline Jython script and obtain the result
           Object retval = jyEngine.eval("d = a + b");
       } catch (ScriptException ex) {
            ex.printStackTrace();
        }
        
        // Retrieve the bindings we from the script
        Bindings bindings = jyEngine.getBindings(ScriptContext.ENGINE_SCOPE);
        // Obtain the value we wish to use
        Integer containsKey = (Integer) bindings.get("d");
        System.out.println("Our number is: " + containsKey);

Our result...

Our number is: 13

The new scripting API becomes extemely useful when you wish to access functions from an external Jython file within a Java application. For instance, you can create an external Jython library and invoke functions as needed from Java.

Suppose we have the following simple Jython script:

# File:  hello.py

from java.lang import Object
from java.lang import System

x = None

def hello(Object):
   return "Hello " + Object

def calcNumbers(y, z):
   x = y + z
   return x

if __name__ == "__main__":
   print calcNumbers()

We can utilize any of the functions defined within hello.py from inside our Java application. However, I have been unable to utilize the scripting API as it is [http://developers.sun.com/learning/javaoneonline/2006/coreplatform/TS-1382.pdf docuemented for Javascript]. Within the other sources of documentation there are plenty of examples using Javascript and they all work as expected. The Jython engine does not perform in the same fashion...I ran into error messages when I tried to use the Invocable interface. Therefore, I had to use some Jython specific code in order to make external scripting function in a useful manner.

In the example below, I invoke the external Jython script by actually returning a reference to a named function. In order to pass values to the script I've instantiated Jython objects. The code below functions without issue.

        try {
            // Evaluate our external jython script
            jyEngine.eval(new FileReader("C:/path-to-external-script/hello.py"));

            // Obtain the script bindings
            Bindings bindings2 = jyEngine.getBindings(ScriptContext.ENGINE_SCOPE);

            // Instantiate a Jython object to pass
            PyString name = new PyString("Josh");

            // Obtain a reference to the Jython function contained in the external script
            PyFunction containsKey2 = (PyFunction) bindings2.get("hello");
 
            // Invoke the script using the Jython __call__() method, and pass the appropriate parameter
            System.out.println("How useful?  Oh well, we have it: " + containsKey2.__call__(name));

            // Repeat as above to invoke another function within the external script
            PyInteger y = new PyInteger(10);
            PyInteger z = new PyInteger(386);
            PyFunction ourResult = (PyFunction) bindings2.get("calcNumbers");
            System.out.println("The result of our number calculation is " + ourResult.__call__(y,z));
            Integer x = (Integer) bindings2.get("x");
        } catch (FileNotFoundException ex) {
            ex.printStackTrace();
        } catch (ScriptException ex) {
            ex.printStackTrace();
        }

Our results...

How useful?  Oh well, we have it: Hello Josh
The result of our number calculation is 396

This may not seem useful if we only invoke a function or two since there is a good amount of coding in order to seamlessly integrate the external script. However, if you were to create an entire Jython function library within this external script, then this procedure may be very useful.

Conclusion

The JDK 6 scripting API adds yet another way of integrating Jython code with your Java applications. It is all a matter of preference because you have also seen other ways of performing these same concepts. The scripting API is easy to use and it is nice to have this functionality integrated in the JDK. I suggest trying to use the scripting API and see what methods you prefer. Perhaps this will be the solution for you.

Resources

[http://developers.sun.com/learning/javaoneonline/2006/coreplatform/TS-1382.pdf Scripting for the Java Platform]

[http://java.sun.com/javase/6/docs/technotes/tools/share/jrunscript.html JRunscript Command Line script Shell]

[https://jdk6.dev.java.net/ JDK 6 Project]

[https://scripting.dev.java.net/ scripting.dev.java.net]