Sep 20, 1999 1:00 AM PT

XML and Java: A potent partnership, Part 4

Learn how Java, laced with JavaScript, pushes XML's flexibility into new dimensions

XML is quickly becoming the standard for both document markup and data exchange. Many factors are driving XML's popularity, including its inherent flexibility, exemplified by its ability to be extended to include tags not found in the original tag set.

XML's flexibility comes with a price, however. Consider HTML, XML's predecessor. HTML sports a fixed tag set (although in the early days of the Web, Netscape and others defined new tags at such a frantic pace that at times the tag set seemed anything but fixed), which allows developers to completely specify an HTML tool's functionality -- up front. No such guarantee exists for many XML tools. Instead, these tools must be as flexible as the XML they work with. They must "do the right thing" when they encounter a novel tag in their XML input. They must expect the unexpected.

"XML and Java: A potent partnership:: Read the whole series!

Java, with its ability to dynamically load code into an executing program, provides a clean solution to this problem. A Java-based XML tool can load code and, thereby, flexibly extend its core functionality to meet the needs of any document it encounters.

This is the heart of the concept that I've been pursuing for the last few months.

Last month, I introduced JavaScript into the mix because it enables us to push XML's flexibility into a new dimension. Consider this: with scripting support, XML documents can include code, affecting their own processing.

I've provided the complete source code for those readers who can't sleep until they see it all. For instructions on downloading, extracting, setting up, and running the code, please read the accompanying sidebar, "Install and run the code."

The design in review

Let's begin with an examination of our basic design from two months ago. If you're new to this series, or in need of a refresher, I'd suggest going back and reading the first two articles in this series (see Resources).

The core of our design, as shown in Figure 1, consists of two tree structures: one is the DOM (Document Object Model) tree, representing, in memory, the information in an XML document; the other is the element tree, corresponding one-to-one with the nodes in the DOM tree that represented tags in the XML document.

Figure 1. The core design

A design like this isn't bad for static information. However, if we intend to modify the information by adding, deleting, or moving nodes or subtrees, keeping two parallel trees in sync quickly becomes a maintenance headache. Therefore, let's consider another design, as shown below in Figure 2.

Figure 2. An alternate design

In this design, we still have handlers (or hooks, in the parlance of two months ago) that encapsulate the behaviors we want to associate with elements in the DOM tree, but we don't compose them into another tree. Instead, we map the handlers to the elements they handle (and vice versa) with a hash table. Now maintenance has to be performed only on one tree -- the DOM tree.

It's important to realize that we can still think of the handlers as being part of an element tree. This tree, however, is a virtual tree because there is no physical linkage between handlers.

Interaction via JavaScript

To accomplish our task, we need to be able to interact with the element tree. Recall that the fundamental building block in JavaScript is the object. We'd like the nodes in the tree to be objects in JavaScript.

Objects in JavaScript have properties. Properties correspond to memory slots to which values can be written and from which values can be read.

Now let's examine how we'd like to represent nodes and the relationships between them.

Two notations

There are two fundamental notations for accessing the properties of a JavaScript object: array-like notation and attribute-like notation. In array-like notation, properties are typically specified as integers:

   $ foo[0] = 5
   > 5
   $ foo[2] = 3
   > 3
   $ foo[0]
   > 5

In attribute-like notation, properties are typically specified by name:

   $ foo.one = "hello"
   > hello
   $ foo.bar = "baz"
   > baz
   $ foo.one
   > hello

Both notations are semantically the same -- in fact, they are different interfaces to the same underlying data. They differ only in their syntax.

Next, we examine the basic schema.

The schema

Recall that XML organizes information hierarchically.

Figure 3. Tree-like element collection

Figure 3 above illustrates a tree-like collection of elements, which correspond to the tags in an XML document. Each node in the tree has zero or more children. We'll use the array-like notation in JavaScript to represent the relationship between parents and children. If foo is an object representing a parent node that has three children, we refer to the children of foo as follows:

  • foo[0] the first child
  • foo[1] the second child
  • foo[2] the third child

We can refer to additional descendents via additional array subscripts:

foo[1][0] the first child of the second child

We will make use of named (or attribute-like) properties as well. Named properties will hold various values associated with a node, such as the number of children, the corresponding XML tag, and the values of the tag's attributes:

foo.tag the associated tag

The implementation

As we learned last month, all JavaScript objects must implement the Scriptable interface. Here's a brief overview of the methods this interface specifies:

Object

get(int nIndex, Scriptable start)

Gets a property from the object selected by an integral index

Object

get(String stringName, Scriptable start)

Gets a named property from the object

void

put(int nIndex, Scriptable start, Object value)

Sets an indexed property in this object

void

put(String stringName, Scriptable start, Object value)

Sets a named property in this object

boolean

has(int nIndex, Scriptable start)

Indicates whether or not an indexed property is defined in an object

boolean

has(String stringName, Scriptable start)

Indicates whether or not a named property is defined in an object

void

delete(int nIndex)

Removes a property from this object

void

delete(String stringName)

Removes a property from this object

Object []

getIds()

Get an array of property ids

Table 1. Scriptable methods

We are primarily interested in the methods that the Rhino engine calls to put and to get properties on the JavaScript object, because our schema relies on the ability to read and write the attributes that underlie these properties. To do this, we need to supply definitions for the put(), get(), has(), and getIds() methods.

The put(), get(), and has() methods each comes in two flavors -- one that requires an integer value to specify a property and one that uses a string to specify a property. These method flavors correspond to the array-like and attribute-like notations for indicating a property on a JavaScript object.

Here's the code for the Scriptable_Base class:

public Object get(String name, Scriptable start) { PropertyDescriptor propertydescriptor = null; if ((propertydescriptor = (PropertyDescriptor)_hashtableDescriptors.get(name)) != null) { Method method = null; if ((method = propertydescriptor.getReadMethod()) != null) { try { Object object = method.invoke(this, new Object [] { }); return object; } catch (Exception exception) { exception.printStackTrace(); } } } return Scriptable.NOT_FOUND; }

public Object get(int index, Scriptable start) { Enumeration enumeration = findChildren(); Object object = null; for (int i = 0; i < index + 1; i++) { object = null; if (!enumeration.hasMoreElements()) break; object = enumeration.nextElement(); } return object == null ? Scriptable.NOT_FOUND : object; }

public void put(String name, Scriptable start, Object value) { PropertyDescriptor propertydescriptor = null; if ((propertydescriptor = (PropertyDescriptor)_hashtableDescriptors.get(name)) != null) { Method method = null; if ((method = propertydescriptor.getWriteMethod()) != null) { try { Object object = method.invoke(this, new Object [] { value }); } catch (Exception exception) { exception.printStackTrace(); } } } }

public void put(int index, Scriptable start, Object value) { }

public boolean has(String name, Scriptable start) { return _hashtableDescriptors.get(name) != null; }

public boolean has(int index, Scriptable start) { Enumeration enumeration = findChildren(); Object object = null; for (int i = 0; i < index + 1; i++) { object = null; if (!enumeration.hasMoreElements()) break; object = enumeration.nextElement(); } return object != null; }

public void delete(String stringName) { }

public void delete(int nIndex) { }

public Object [] getIds() { Enumeration enumeration = findChildren(); int i = 0; while (enumeration.hasMoreElements()) { enumeration.nextElement(); i++; } int n = i + _hashtableDescriptors.size(); Object [] rgobject = new Object [n]; int j = 0; for (j = 0; j < i; j++) { rgobject[j] = new Integer(j); } enumeration = _hashtableDescriptors.keys(); while (enumeration.hasMoreElements()) { rgobject[j++] = enumeration.nextElement(); } return rgobject; }

The Scriptable_Base class implements the Scriptable interface. Instances of the class are therefore valid JavaScript objects. This class also serves as the base class of any XML elements handlers that need to be accessible from JavaScript scripts.

If we rewrote the GUI, FRAME, PANEL, and BUTTON classes from July's column so that they inherited from Scriptable_Base, instances of those classes would become JavaScript objects. If you'd like to see what they look like, take a look at the sidebar Installing and running the code.

Conclusion

I hope my efforts for the last four months have demonstrated XML's potential and have convinced you of the potency of the combination of XML and Java (and a scripting language such as JavaScript). The framework I've designed for you currently is being used in an evolving tool that generates models from XML descriptions.

Keep your eyes on XML. It's not going to solve every problem facing developers today, but, like Java, it's going to help solve a great many of them. We've barely crossed the starting line.

Next month, I'm going to shift my focus back toward the enterprise and discuss testing. Testing is a topic of critical importance, but one that is often overlooked. Together, we'll develop a standard testing harness that you can use to test your Java code.

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