XML and Java: A potent partnership, Part 3

Learn how to integrate Java and JavaScript, two popular programming languages

When it comes to getting the most bang for your buck in the world of programming languages, nothing beats a good scripting language. A scripting language provides a clean, easy-to-use, high-level toolkit geared toward high-speed application prototyping and development. This month, I'll demonstrate how to integrate JavaScript, a popular scripting language, into Java, so that Java applications can enjoy these benefits as well.

Our past

Recently, we've explored the interface between Java and XML. More specifically, we've been looking at ways to use Java's ability to dynamically load code to improve our ability to process XML -- in particular, to handle XML tags we didn't specifically design our application to handle.

"XML and Java: A Potent Partnership": Read the whole series!

The Java-based framework I developed over the preceding two columns was capable of examining tags and their attributes, and even analyzing the context in which tags occur in order to properly handle them. As an application developed within this framework encountered novel tags in its XML input, it located and loaded code to handle those tags. The one feature it lacked was scripting.

Adding scripting to the framework brings a number of advantages. First, scripting languages provide a natural and concise method of specifying behavior (scripting languages are programming languages, after all). Second, since scripts are text, they can be incorporated into the XML itself. This is essential for XML applications such as the GUI definition application we've been developing.

The plan

Here's the roadmap for what lies ahead.

Because of the sheer volume of what I'm about to cover, I'll present this material in two parts. This month, I will cover the JavaScript language and discuss how to integrate the Rhino JavaScript implementation into Java. Next month, I'll demonstrate how to integrate JavaScript into our XML framework.

Rhino

There are several capable scripting languages available for Java, covering a wide feature space, from compiled to interpreted and from procedural to declarative. The list includes versions of many of our favorites from the days before Java: TCL (Jacl), Python (JPython), JavaScript (Rhino), Scheme (at least a dozen different implementations), and so on. For an exhaustive list, take a look at Robert Tolksdorf's Web page, "Languages for the Java VM," in the Resources section at the bottom of this article.

For this exercise, I've selected the Rhino JavaScript implementation. I chose JavaScript because it's reasonably easy to use and learn, it's powerful, it's already both a de facto and a de jure standard (see ECMA-262 in the Resources), and it's extensible.

JavaScript was developed by Netscape for use in its Web browser. Originally called LiveScript, the name was changed to JavaScript at roughly the same time that Java was incorporated into Netscape Navigator 2.0. Since then, JavaScript (or a derivative) has found its way into most major browsers.

In late 1996, the ECMA international standards body began a standardization process to define a scripting language based on a number of existing technologies, the most significant being JavaScript. The ECMA standardized the core portion of the language (the generic, nonbrowser pieces). The first version of the standard appeared in 1997; the second, called ECMA-262, followed in June 1998. The language that was thus defined is technically called ECMAScript, but it looks and feels just like JavaScript -- minus the browser-specific parts.

There are at least two Java-based scripting languages that implement JavaScript in Java: FESI (which actually attempts to implement the ECMA-262 specification) and Rhino. Rhino is one component of the Mozilla project. (See the Resources for links to both languages.) Please note that, from here on in, I'll use the term JavaScript throughout this article to refer to the JavaScript scripting language in general and Rhino when I need to refer to or emphasize the Rhino implementation of that language in particular.

Learn JavaScript

There are so many introductions to JavaScript programming freely available via the Web that I can't bring myself to take up the space to cover the language here. Instead, consult the Resources section for references.

Objects all around

JavaScript is object-based. Just as they do in Java, objects in JavaScript have properties (member data) and methods (member functions). If you want to build applications, you do so by creating objects.

JavaScript objects

Let's create an object directly in JavaScript:

x = {
 a : 5,
 b : function() {
  print(this.a)
 }
}

We can now access the properties and invoke the methods of object x:

$ x.a
5
$ x.b()
5
$ x.a = 4
4
$ x.b()
4

Java-based JavaScript objects

Now let's create a JavaScript object in Java. This is how you extend JavaScript's built-in functionality. I've numbered the lines of code, and I'll refer to them by number in the following sections.

001 public
002 class Y
003 extends ScriptableObject {
004
005  public
006  String
007  getClassName() { return "Y"; }
008
009  public
010  void
011  b() {
012  System.out.println(get("a", this));
013  }
014
015  public
016  static
017  void
018  main(String [] rgstring) {
019  Context context = Context.enter();
020  Scriptable scriptable = context.initStandardObjects(null);
021  Y y = new Y();
022  scriptable.put("y", scriptable, y);
023  y.put("a", y, new Integer(5));
024  Method [] rgmethod = FunctionObject.findMethods(y.getClass(),
       "b");
025  FunctionObject functionobject = new FunctionObject(null,
       rgmethod[0], y);
026  y.put("b", y, functionobject);
027  context.exit();
028  }
029 }

JavaScript objects must implement the Scriptable interface. The easiest way to create a new JavaScript object is to implement the ScriptableObject abstract class, which implements the Scriptable interface and provides definitions for its methods. The only method we must define is the getClassName() method.

All JavaScript activity occurs within the bounds of a valid runtime context. The Context.enter() method associates a Context instance with the currently executing thread. The Context instance stores execution information, such as the JavaScript call stack, and provides methods for interacting with the JavaScript engine. At any place within a valid context you can obtain a reference to the context via a call to the static method getCurrentContext(). Lines 019 and 027 in the listing above demonstrate how to enter and exit a context.

The ECMA-262 specification details the existence of a handful of standard objects, called built-in objects. These objects are guaranteed to be available when a JavaScript program begins execution. The important members of this group are the objects Object, Function, Array, String, Boolean, Number, Date, and Math. The Rhino implementation supports these standard objects.

Line 020 demonstrates how to initialize these objects and return a reference to the global object. The eight built-in objects are properties of the global object. We can make use of that fact if we need to access them from Java.

In lines 021 and 022, I create an instance of the Y class and make it a property of the global object. You can use this object exactly like any other JavaScript object, as in the example below:

$ y
[object Y]
$ y.foo = "bar"
bar

The put() method adds a property to an object. It requires three parameters:

  1. The name of the property

  2. A reference to the object to which the property should be added (for use with prototypes, which are outside the scope of this article)

  3. The value of the property

In line 023, I add the first of two properties -- property a; in lines 024, 025, and 026, I add another, property b. Recall from the first JavaScript example that b is a method that returns the value of property a. I am going to build this JavaScript method as a Java method. Rhino uses the Reflection API to find the Method instance associated with a method. It wraps the Reflection API in the method findMethods(), which searches a class for all methods of a given name. In line 025, I use the instance of the Method class to create an instance of the FunctionObject class. A function object is a first-class value in JavaScript and can be manipulated like any other value. Finally, I add the property to y.

You can now use this object in exactly the same way that you used the native JavaScript object:

$ y.a
5
$ y.b()
5
$ y.a = 4
4
$ y.b()
4

Evaluating code

In addition to providing general contextual information, the Context class also provides methods for evaluating scripts. The code in the listing below evaluates the strings passed in on the command line. See if you can figure out how to extend this method in order to evaluate code interactively or from a file.

public
static
void
main(String [] rgstring) {
 Context context = Context.enter();
 Scriptable scriptable = (ScriptableObject)context.initStandardObjects(null);
 for (int i = 0; i < rgstring.length; i++) {
  context.evaluateString(scriptable, rgstring[i], "<>", i, null);
 context.exit();
}

Conclusion

These techniques cover only part of Rhino's functionality, but by mastering them, you'll have the background necessary to understand next month's column. In that installment of our series, I'll use these techniques to reflect the XML tag elements into the JavaScript environment as properties so that you can access them from JavaScript scripts. I'll also show you how to attach scripts to the BUTTON tag, and how to evaluate them when the button is pressed.

Todd Sundsted has been writing programs since computers became available in convenient desktop models. Though originally interested in building distributed applications in C++, Todd moved on to the Java programming language when it became the obvious choice for that sort of thing. In addition to writing, Todd is an architect with ComFrame Software Corporation.

Learn more about this topic