XML JavaBeans, Part 1

Make JavaBeans mobile and interoperable with XML

1 2 3 Page 2
Page 2 of 3

The Java package org.w3c.dom is the standard "binding" of the DOM specification in terms of Java interfaces (meaning all of their methods are abstract, and therefore have no implementation). Various vendors can implement the classes in this package in any way they wish. IBM, for example, has implemented this package in its xml4j distribution. There are several implementations of the DOM, most of which include vendor-specific extensions. We'll be using the DOM in the sample application below. See Resources to read up on the Document Object Model, and to find out how to get IBM's implementation of it.

DOM XML parsers

Now that we've defined a new language, and we have a data structure system (DOM) to represent any document in this language as a tree in memory, it would be useful to have a parser that automatically converts an XML document into its DOM tree. Once we have the DOM representation of a document in memory, we can process the document as a tree of nodes, instead of as a series of lines or tokens. What we need is a parser in Java which will read the XML file (which is only a text file, after all) and produce a single Document object containing a tree of DOM nodes that represent the document completely.

Fortunately, we don't have to go out and write a parser from scratch. Several companies and individuals have written parsers that read XML documents from files or streams and produce a DOM Document object, which is the root of a tree of DOM objects (as seen in Figure 4). The entire process of reading in an XML file and turning it into a usable tree is encapsulated in the parser. Many of the DOM object implementations also include extensions for going the other way; that is, a DOM Document can be printed as XML with a single method call.

In the sample code below (yes, we're finally getting to a coding example), I've used the parser from IBM's xml4j package, which is available free for noncommercial use from IBM's alphaWorks site (you can find the URL in Resources). IBM apparently has gone completely bananas for XML, and I consider alphaWorks to be one of the most interesting Java technology sites on the Web. The xml4j package implements the W3C DOM completely, extends it in a sensible yet encapsulated manner, and comes with copious, excellent documentation.

Beans as XML documents

In thinking about how to use XML with JavaBeans, I decided it would be interesting to use XML as a serialization format for beans. In other words, I decided to create a markup language that allows a user to create an XML file that specifies the values for a JavaBean's properties.

If you're not familiar with JavaBeans, the concepts of "properties" and "serialization" also may not be familiar to you. If this is the case, you may want to get some background by first reading some or all of the following articles:

If you're already familiar with these concepts (perhaps because you're a regular reader of the JavaWorld JavaBeans column,) just dive right in. You can always return to these references if there's something in this article you don't understand.

The example class we develop in this article is called XMLBeanReader. This class reads an XML file (of a specific format that we define) and uses its contents to create a JavaBean and initialize that bean's properties. The JavaBean class name and the property values all come from the XML file contents. All of the methods of this class are static.

If you understand how this small sample program works, you can extend it to handle hookup up event relationships, looking up default values for properties, or seeking out information that's not in the XML file itself in order to configure the JavaBean. The possibilities are endless, once you understand that a data structure of JavaBeans can be converted to an XML file and back.

The XMLBeanReader class works something like the standard Java serialization mechanism, in that it takes a "flat" stream of data and uses those data to set properties in a JavaBean. It doesn't create a new class. It uses XML to instantiate a JavaBean and set that bean's properties for a JavaBean class that already exists.

JavaBean Markup Language

Before writing any code, we need to define what our simple XML dialect looks like. Since our application deals with JavaBeans, I'm going to create a language that allows the user to specify a JavaBean and its class, and then specify a list of properties for the JavaBean.

Despite some similarities, the XMLBeanReader class you're about to see is in no way related to IBM's Bean Markup Language (BML is available from the alphaWorks site -- see Resources for the appropriate link). Once you understand what's going on with the code in this column, though, you'll be better able to tackle projects using BML.

For this simple XML dialect, the only tags we need are:

  • <JavaBean>: a tag enclosing a specification of the contents of a JavaBean

  • <Properties>: a tag that encloses all <Property> elements of a particular JavaBean

  • <Property>: a tag that encloses the value of a single property

Now, imagine we had a class Player, which was a JavaBean with four properties:

  • int Number: the player's number

  • String HighSchool: the name of the player's high school

  • PersonName Name: a JavaBean of class PersonName that is the player's name

  • Statistics Stats: a JavaBean of class Statistics containing player's batting statistics for a particular year

Notice that two of the property's values are JavaBeans themselves. The source code for the three JavaBean classes we'll be using can be found in Player.java, PersonName.java, and Statistics.java.

Given the JavaBean class we've just defined (Player), the tags we've just defined above, and the data from Figure 2, we could express the JavaBean in XML in terms of its properties, like this:

<?xml version="1.0"?>
<JavaBean CLASS="Player">
  <Properties>    <Property NAME="Number">12</Property>
    <Property NAME="HighSchool">Eaton</Property>
    <!-- Notice that the value for the properties "Name" and
      ** "Stats" are themselves JavaBeans!
      ** Notice also that comments in XML files look
      ** just like comments in HTML files.      -->
    <Property NAME="Name">
      <JavaBean CLASS="PersonName">
        <Properties>
          <Property NAME="First">Jonas</Property>
          <Property NAME="Last">Grumby</Property>
        </Properties>      </JavaBean>    </Property>
    <Property NAME="Stats">
      <JavaBean CLASS="Statistics">
        <Properties>
          <Property NAME="Year">1997</Property>
          <Property NAME="AtBats">69</Property>
          <Property NAME="Runs">31</Property>
          <Property NAME="Hits">30</Property>
          <Property NAME="HomeRuns">2</Property>
          <Property NAME="RunsBattedIn">15</Property>
        </Properties>
      </JavaBean>
    </Property>
  </Properties>
</JavaBean>

Figure 5. Batting statistics represented in XML as a JavaBean

The first thing you'll notice in the code above is that there are strings embedded with quotes inside of the tags, like this:

<JavaBean CLASS="Player">

These strings are called attributes, and if you've written HTML, you've used them. They appear in tags such as hyperlinks (<A HREF="...">) and images (<IMG SRC="...">.) Attributes are simply another way of associating data with a DOM Element node. The method Element.getAttribute() returns an Element's attribute with a specific name. There are no hard-and-fast rules about when to include data in an attribute and when to put it in the Text object inside the Element. I tend to use attributes for structural information (classes, property names, and so on) and Text node for instance data. Use what you consider the easiest.

Creating JavaBeans from XML

The XMLBeanReader class (source in XMLBeanReader.java) is quite straightforward. The main() method (lines 400 to 415) simply calls the static method readXMLBean(), passing it the name of the input file. The result returned from readXMLBean() is a JavaBean whose class and contents correspond to what was in the XML file. main() then checks to see if the JavaBean it just created has a method called print() and, if it does, main() invokes it. (Isn't reflection cool?)

readXMLBean() (lines 377 to 395) creates an XML parser and invokes it on the input file. The result of the parser's readStream() method is the document tree, which, if drawn, would look something like Figure 4. It then passes the top Element of the document tree (the tag of which must be JavaBean) to the static method instantiateBean(), which is where all the serious work is done. The result of instantiateBean() is the JavaBean that method created.

instantiateBean() (lines 269 to 294) creates a JavaBean from a DOM tree with a <JavaBean> element at the top. First, it creates an object of the type indicated by the CLASS attribute of the <JavaBean> element. It then finds the <Properties> node, which is a child of the top (<JavaBean>) node. Within this <Properties> may be several <Property> nodes, each of which corresponds to a property of the JavaBean. For each <Property> Element (among the children of <Properties>), instantiateBean() calls setProperty(), passing in the name of the property, the property descriptor (obtained by applying java.beans.Introspector to the created JavaBean), the Element corresponding to the property, and the new bean itself. When this whole process has completed, the JavaBean has been both created and initialized, and can be returned to the caller.

The setProperty() method (lines 048 to 249) takes care of setting a property of a JavaBean. It handles regular and indexed properties separately (by ignoring indexed properties, for the moment). In the (usual) case of a nonindexed property, setProperty() first searches all of the children of the <Property> for either a Text node or a <JavaBean> element, and records what it finds for later use. It also asks the PropertyDescriptor for the setter method for the property.

The remainder of setProperty() concerns itself with figuring out what arguments to pass to the setter method. There are a few possible conditions, all of which depend on the type of the property, or on the expected arguments of the setter method:

  • The property type is primitive. (Lines 110 to 147.) Primitive types can all be constructed from a String, so if the type of the property is primitive (boolean, byte, int, long, float, or double), setMethod simply constructs an object of the appropriate type using the constructor that takes a single String as an argument and passing it the text value of the Element. Properties of primitive type char are handled as a special case, since I decided to encode them as their integer values, and they can't be readily constructed from strings. The object that the method constructs is used as the argument to the setter method.

  • A <JavaBean> Element appeared as a child of the <Property>, so the property must be a JavaBean. (Lines 168 to 191.) In this case, the argument we want to pass to the setter method is actually a JavaBean. We need to instantiate that JavaBean before we can pass it to the setter, and we do so by calling instantiateBean() recursively, passing it the child <JavaBean> element. The resulting (JavaBean) object is placed in the argument list for the setter.

  • The setter method for the property takes as its only argument a String. (Lines 193 to 203.) This is an easy case. The argument list for the setter contains simply the Element text of the Property.

  • The setter method for the property takes as its only argument an object that can be constructed from a String. (Lines 205 to 222.) In this case, setMethod() does exactly what it did for the primitive case: it constructs an object of the appropriate type, using the Element text as the constructor argument, and then places this new object in the argument list (setterArgs) for the setter.

If none of these conditions are met, setProperty() isn't capable of setting the property, and it returns without doing anything. (It should probably throw an IllegalArgumentException, but this is just a demo program.)

Running the class file's main() method, and passing it the XML source file you see in Figure 5, produces a running JavaBean with all of its properties set to values that came (originally) from the XML, including properties whose values are JavaBeans. You can see the output of this program being run in Example.html. You see that the Statistics bean does indeed report its properties set correctly.

Conclusion

In a very small space, we've covered an enormous amount of ground. You've read about what XML is, and what it can do that HTML can't. The sample XML on batting statistics showed how the structure of XML can be used to reflect the meaning of the data, not its presentation. You then read about the standard programmatic interface to XML, the Document Object Model, and then saw an example of the DOM and XML in action, being used to create and initialize a new JavaBean instance.

Next month, we'll go the other way, creating a class that takes a running JavaBean and converts it to XML. We'll also use these two classes, XMLBeanReader and XMLBeanWriter, in a small application, to demonstrate the power and flexibility of XML.

Please write and let me know if you'd like to hear more about XML and JavaBeans; you can send e-mail using the address listed in my bio below. You can also send your comments on this article to JavaWorld by clicking the link at the bottom of the page.

1 2 3 Page 2
Page 2 of 3