Scripting Languages For Java

Scripting Languages For Java

By Weiqi Gao, Ph.D., OCI Senior Software Engineer

March 2001


Introduction

This month, we bring you to the world of scripting languages.

In this article, we focus on two scripting-language implementations that interact nicely with Java: Rhino and Jython.

Obviously, one of the prerequisites for using a scripting language is familiarity with the language. Good tutorials and users' guides exist for all of the languages discussed in this article (see resources). For the rest of this article, I'll assume that you are familiar with JavaScript and Python. And I'll explain how Rhino and Jython allow you to access arbitrary Java objects.

A Survey of the Field

Since the release of JDK 1.0 in 1995, many popular languages have been re-implemented in Java. These re-implementations usually give their users the ability to access any Java classes in the JVM. Some are even capable of compiling arbitrary scripts into Java class files.

At the same time, many languages gained the ability to access the JVM and all the classes in it in their original C implementations. Here is a partial list of languages that I have found on the internet as free software:

LanguageC implementation / Java Access LayerJava Implementation
ECMAScript JS Engine / LiveConnect Rhino
Python Python (a.k.a. CPython) / None Jython (formerly JPython)
Tcl/Tk Tcl / Tk / TclBlend Jacl
Perl Perl / JPL (bundled with Perl) None
Scheme Guile and others / None Kawa, Skij, and more
Java itself The JDK BeanShell and others

Getting Started

To start using the Java implementation of a scripting language, downloads the jar files, put them into the CLASSPATH, and write a shell script or two to make using the language convenient. Jython even comes with an installer.

A portion of my CLASSPATH reads:

... ~/rhino15R2pre/js.jar:~/jython-2.0/jython.jar: ...

And I have the following shell scripts:

rhino: java org.mozilla.javascript.tools.shell.Main $* (Rhino interactive shell)

rhinoc: java org.mozilla.javascript.tools.jsc.Main $* (Compile JavaScript code to class files)

jython: ... (Jython interactive shell, supplied by Jython)

jythonc: ... (Compile Python code to class files, supplied by Jython)

Here's the transcript of a couple of sessions with Rhino and Jython respectively:

[weiqi@gao] 1 $ rhino 
js> importPackage(java.awt,java.awt.event) 
js> for (m in AWTEvent) print("AWTEvent." + m + ": " + AWTEvent[m]) 
AWTEvent.ADJUSTMENT_EVENT_MASK: 256 
AWTEvent.RESERVED_ID_MAX: 1999 
AWTEvent.TEXT_EVENT_MASK: 1024 
  ... 
AWTEvent.MOUSE_EVENT_MASK: 16 
js> 3 * 3 + 4 * 4 == 5 * 5 
true 
js> quit()
[weiqi@gao] 2 $ jython 
Jython 2.0 on java1.2.2 (JIT: sunwjit) 
Type "copyright", "credits" or "license" for more information. 
>>> from java.util import Random 
>>> r = Random() 
>>> r.nextInt() 
-21966670 
>>> for i in range(4, 7): 
...     print r.nextDouble() 
... 
0.22142449772414396 
0.3263682239532196 
0.8371217576770793 
>>> ^D

Exploratory Programming and Testing

We are writing JavaScript and Python code while making use of standard Java classes. The interactivity and the economy of expression in the scripting languages account for their first big use: exploratory programming and testing.

Rhino and LiveConnect

LiveConnect is the mechanism that JavaScript uses to access Java objects. LiveConnect is used by both the C engine and Rhino, while Rhino contains a few more convenient functions.

Although LiveConnect relies on a set of wrapper classes to communicate between JavaScript and Java, to the user, it works almost transparently.

Let's look at some examples.

1. Java classes and packages can be imported.

js> importPackage(java.awt, java.awt.event, Packages.javax.swing) 
js> // Note packages not in the java hierarchy need to be prefaced with Packages

2. Instantiating a Java object is straightforward.

js> f = new Frame("A Java Frame in Rhino") 
js> b = new Button("Hello") 
js> f.add(b); f.pack();

3. JavaBeans style properties (defined by getter and setter methods) can be accessed directly.

js> f.visible = true

4. Java interfaces can be implemented with JavaScript code.

js> foo = function(e) { print("World") } 
js> bar = { actionPerformed: foo } 
js> listener = ActionListener(bar) 
js> b.addActionListener(listener)

5. Java classes can be extended with JavaScript methods.

js> baz = function(e) { quit() } 
js> o = { windowClosing: baz } 
js> adapter = WindowAdapter(o) 
js> f.addWindowListener(adapter)

Since Java arrays are not in direct correspondence to JavaScript arrays in Rhino, creating a Java array in Rhino is more complicated than in Java.

6. Java reflection is used to create a Java array in Rhino.

js> a = java.lang.reflect.Array.newInstance(Object, 3) 
js> a.length 
3

Using Jython

For the most part, accessing Java using Jython has the same feel as using Rhino. However, since Python (the language implemented by Jython) has a richer set of features than JavaScript, Jython provides more convenience for the exploratory Java programmer.

Let's try out our Rhino examples in Jython.

1. Java classes and packages can be imported using the Python import statement (note the extra work Jython does for you, compared to Java).

>>> import java # Import the java 'module' so that we can use names therein 
>>> java # This works now 
<java package java at 3665287> 
>>> java.awt # Surprise, this works too 
<java package java.awt at 26038046> 
>>> from java.awt import * # Import all names from the java.awt 'module' into the current 'module' 
>>> Frame # We can now refer to java.awt.Frame as simply Frame 
<jclass java.awt.Frame at 30892413>

2. Instantiating a Java object is straightforward (you don't need the 'new' when instantiating an object in Python).

>>> f = Frame("A Java Frame in Jython") 
>>> b = Button("Hello") 
>>> f.add(b); f.pack()

3. JavaBeans style properties (defined by getter and setter methods) can be accessed directly.

>>> f.visible = 1 # In Python, 1 is true, 0 is false

4. Java interfaces can be implemented with Python code.

>>> class MyActionListener (java.awt.event.ActionListener): 
...   def actionPerformed(self, event): # Note the extra self argument 
...     print("World") 
>>> listener = MyActionListener() 
>>> b.addActionListener(listener)

5. Java classes can be extended with Python code, (however Python's multiple inheritance does not apply when Java classes are extended).

>>> class MyWindowListener (java.awt.event.WindowAdapter): 
...   def windowClosing(self, event): 
...     from sys import exit; exit() # Calling Python's sys.exit() rather than Java's System.exit() 
>>> adapter = MyWindowListener() 
>>> f.addWindowListener(adapter)

6. Jython makes creating Java arrays easier through the jarray module.

>>> from jarray import array, zeros 
>>> a = array([1,2,3], 'i') 
array([1, 2, 3], int) 
>>> from java.util import HashMap 
>>> b = array([HashMap(), HashMap()], HashMap) 
array([{}, {}], java.util.HashMap)

Jython goes one step further than Rhino in JavaBeans scripting.

7. All properties and event listener methods can be assigned at object construction time using keyword arguments.

>>> f = Frame("A Java Frame in Jython", visible=1, windowClosing=exit)

This is roughly equivalent to the following Java code.

Frame f = new Frame("A java Frame in Jython"); 
f.setVisible(true); 
f.addWindowListener(new WindowAdapter() { 
  public void windowClosing(WindowEvent e) { 
    exit(); 
  } 
});

I have found this style of exploratory programming useful in the rapid prototyping of ideas, in getting acquainted with new Java APIs, and in informal testing of newly developed Java classes and packages.

User-Level Scripting of Applications

All scripting languages mentioned above have an interpreter interface that can be embedded into applications to make them scriptable by end users. The Netscape Navigator web browser and Xalan-J XSLT processor are good examples of applications that contain an embedded scripting language. This is the second big use of scripting languages.

Embedding a scripting language into an application has been a winning strategy for application designers for a long time. With the proliferation of scripting languages for the JVM, embedding one into an application becomes as easy as choosing a language and instantiating an interpreter object of the chosen language.

Issues such as the execution environment, the scoping of objects, threading, and synchronization need to be considered carefully when you embed a scripting language into an application. We will not go into detailed discussion about these issues in this article. Instead I will restrict my attention to a very simple Java class and write a Java application around it, giving the user the power to drive the application through scripts.

The Java class I'll script is called Turtle. It represents a point that moves on a plane, tracing its path.

[weiqi@gao] 1 $ javap Turtle 
Compiled from Turtle.java 
public class Turtle extends java.awt.Canvas { 
    public void reset(); 
    public void move(int); 
    public void turn(double); 
    ... 
}

The methods of this class do the obvious things. However, merely using objects of this class in a plain Java application isn't very exciting. You probably prescribe a series of move() and turn() actions in Java code, compile and execute the code, and observe the resulting pattern.

To make it more interesting, I'll write an AWT application containing a Turtle, a TextArea for the user to enter scripts, and an 'OK' button to send scripts to the interpreter.

I have chosen to use the Jython interpreter to do the job. However, Rhino would do the job equally well.

import org.python.util.PythonInterpreter; 
public class EtchSketch extends Frame implements ActionListener { 
  // I want a Turtle ... 
  private Turtle turtle = new Turtle(); 
  // ... and a TextArea, ... 
  private TextArea ta = new TextArea(10, 40); 
  // ... and a Button ... (register an ActionListener) 
  private Button b = new Button("OK"); { b.addActionListener(this); } 
  // ... and a Jython interpreter! 
  private PythonInterpreter interp = new PythonInterpreter();
  public EtchSketch() { 
    // Do the GUI stuff 
    ... 
    // Add a variable named 'turtle' to the toplevel scope of the Jython interpreter, 
    // bound to the Java object turtle 
    interp.set("turtle", turtle); 
  }
 
  public void actionPerformed(ActionEvent e) { 
    // When the OK button is clicked, we grab the text from the TextArea, ... 
    String script = ta.getText(); 
    // ... execute it in the Jython interpreter, ... 
    interp.exec(script); 
    // ... and prepare for the next user command. 
    ta.selectAll(); ta.requestFocus(); 
  }
 
  public static void main(String[] args) { 
    ScriptFrame f = new ScriptFrame(); 
  } 
}

In this application, the user can interactively enter commands to direct the turtle's movement on the Canvas. 

Since the user has the turtle object and the whole Python language at his disposal, he can do things that may surprise the original designer. Putting myself into my user's shoes, I immediately concocted a script that, well, surprised myself!

Scripting turtle with jython

Summary

In this article, we illustrated in Rhino and Jython two ways of using Java-aware scripting languages: exploratory programming and testing and user level scripting of applications. But we have only scratched the surface.

The scripting languages offer much more that are worth studying and using. Visit their websites to learn more about them.

All the scripting languages mentioned, in either their C or Java implementations, have other uses that are not directly related to the Java platform. These uses include rapid GUI application development, CGI programming, testing frameworks, and even full blown application development. The developments in this space should be interesting for Java developers to watch.

Resources



Software Engineering Tech Trends (SETT) is a regular publication featuring emerging trends in software engineering.


secret