A flood of mail from excited JavaWorld readers indicates that with my previous column I've hit a nerve: there's a great deal of interest in XML in the Java community. Readers are also particularly interested in the marriage of JavaBeans with XML, because it's a concrete example of using a platform-neutral component technology (JavaBeans) with a generalized document format (XML). The result of this combination is a component technology that is network-mobile, standards-based, and potentially interoperable with new and legacy systems alike.
I've received some great ideas from readers on how to improve XML JavaBeans, so I've expanded this topic from two installments to three. This month, we're going to develop a class,
XMLBeanWriter, which writes a JavaBean in XML format. Then next month, we'll look at some of the improvements suggested by readers, and implement them (in Java, of course).
Last month's sample program,
XMLBeanReader, reads XML and produces a running JavaBean in memory. If you're not yet up to speed on exactly what XML is, or if you're not familiar with the World Wide Web Consortium's (W3C's) Document Object Model (DOM), you'll probably want to read last month's cover story, " XML JavaBeans, Part 1." Once you've read and understood that article, you'll be up to speed for this month's topic,
Flattening object structures
In most nontrivial programs, information is processed using data structures. In object-oriented programming, these data structures are usually composed of objects that contain the data of interest to the application. It's often desirable to use the data in the application objects in more than one work session. (Just try to imagine a word processor without a Save command: turn off your computer, and all of your documents would die with the editor!) Persistence is the common term for how a software system arranges for such data to "survive" the death of the process in which they are running, so that they may live to run again another day (or on another machine).
Persistence has been around longer than object-oriented programming -- at least in practice, if not in name. Word processor files, data requests moving through a network, and punch card decks are all examples of persistence. In fact, data persistence is the whole reason for all of the various types of data media we have: from the ancient and lowly clay tablets and papyrus, to the slightly more modern paper tapes, magnetic reels, and punch cards of yesteryear, to today's CD-ROMs and DVDs. The basic idea behind persistence is to encode a software structure into a stream of bytes (a process sometimes called "flattening") in such a way that the stream can later be used to recreate the identical structure.
When object-oriented system design and programming began to appear on the scene, it was immediately clear (to anyone who knew about such things) that it would be very convenient to be able to create persistent objects. An object running inside a program could be converted to a series of bytes, and then stored or transmitted, and that series of bytes could be used later and/or elsewhere to recreate the object. This is precisely what Java serialization does. The Java core has a set of built-in methods (in package java.io) that allows a programmer to arrange for an object to write itself to a byte stream, or to read itself from a byte stream. (To find out more about Java serialization, see the JavaWorld articles on those topics, linked in Resources below.) Persistence in the context of object-oriented systems is commonly called object persistence; or, said another way, objects that have been "persisted" (note that "persist" has suddenly become a transitive verb) are called "persistent objects".
Practically every object-oriented system has some object persistence mechanism, usually involving a persistence format (the layout of the bytes in the data stream) that is "standard", at least for that application. It's been said that the nice thing about standards is that there are so many to choose from, and object persistence formats are no exception. Just take a look at the list of document "filters" in a commercial word processor if you don't believe it: every one of those filters is trying to convert from one proprietary persistence format to the one used internally by the word processor. The Java serialization mechanism was, in fact, designed (via the java.io.Externalizable interface) to allow a programmer to read and write the object persistence formats of other applications.
A standard standard
What does all this have to do with XML and JavaBeans? Well, in these XML JavaBeans articles, we're using XML as a persistence format for JavaBeans components. The nice thing about XML, though, is that it's a true standard, as the explosion of interest in and products available for XML demonstrates. The current XML standard is called the Extensible Markup Language (XML) 1.0 W3C Recommendation, and it is available for you to read in full (see the link in Resources). Any application that is "XML-enabled" (meaning simply that the application can read and write its objects in XML) potentially will be able to interoperate with other systems more easily than before XML was available, because all XML systems conform to the standard (or they're not XML-compliant, by definition.)
For example, a new custom markup language called RDF, for Resource Definition Framework, is currently being standardized. You can read the RDF specification, which is a work-in-progress (see Resources). RDF is a "dialect" (technically called an application) of XML that is being defined for general descriptions of metadata (data about data). Once this format is standardized, every application that uses the standard will be able to manipulate and use data and metadata from other compliant systems, because the applications will all be making the same assumptions about what the data mean.
Now, this doesn't necessarily mean that every application will always understand every other application's markup tags, as anyone who's tried to write browser-neutral HTML can tell you. There's always tension between making a system extensible on one hand, and maintaining compatibility on the other. (Of course, there also are spoilsports who deliberately "extend" existing standards to try to wrest control away from the standards bodies and muddy the water for their competition. End of editorial.) But XML provides a common ground for systems developers to structure object persistence and to publish the interfaces to their systems. Custom markup languages are already appearing for application domains as varied as molecular chemistry, graphical user interfaces, and business forms. As these standards become widespread, system interoperation becomes easier for everyone.
Notice from last month that the
Player object we were reading from an XML file wasn't simply one object. It was three objects: a
Player object, which held references to a
Statistics object, and a
PersonName object. That entire data structure was encoded in the XML, and
XMLBeanReader was able to create the corresponding structure in memory by examining the Document Object Model (DOM) object structure created by the XML parser. And, remember, we didn't need to write any parsing code. We simply gave the XML parser the name of the XML file, and it returned the entire data structure that the XML file represented. Not bad for one line of code!
XMLBeanReader gives us the ability to take a "flat" representation of an object structure (that is, a JavaBean and its properties represented as a text stream that just happens to be XML) and create the corresponding structure in memory. Now, let's have a look at
XMLBeanReader, which can take almost any JavaBean instance and represent it in our XML dialect.
Before we dive into writing a class to convert a JavaBean into XML, there's an issue I want to clarify. The particular XML file format we're using for our XML JavaBeans is not generic XML. It's an application of XML -- that is, a dialect of XML that we defined ourselves, simply by making it up. No other system I know of currently uses it, though judging from my reader mail, some people may have already written little applications that do so.
Our little XML JavaBeans language is also very simple: a
<JavaBean> element contains a
<Properties> element, which itself can contain multiple
<Property> elements. A
<Property> element may contain either a text element, indicating the value of the property, or a
<JavaBean> element, if the property's value is a JavaBean. That's our custom JavaBean markup language. So, you can't (currently) use
XMLBeanReader to read generic XML. The input XML has to conform to the "little language" we've defined.
A class that reads a JavaBean's properties and writes the JavaBean to a file needs to be able to do several things. Here are the tasks it needs to perform, along with some discussion about how they need to be done:
Identify the class of a JavaBean. The class of a JavaBean can be identified simply by calling the bean's
getClass()method, which every Java object must have. (It's defined in
java.lang.Object, which every object inherits.)
Identify the JavaBean's property names, types and values. Getting a JavaBean's property names, types, and values is fairly easy because most of that work comes with the Java core distribution in the class
java.beans.Introspectorand its associated class
Represent each property value as XML. Representing each property's value as XML is a little more difficult, because not every property can be represented as text. If a particular property has no text representation, but the value of the property is a JavaBean, then we can represent the property's value as an XML-encoded JavaBean. This fact indicates that the code that encodes a JavaBean as XML should be a standalone method, so that it can be called recursively if the value of a JavaBean's property is also a JavaBean.
- Write the resulting XML somewhere. The simplest place to write the output XML would be to a file, but that solution isn't very general. What if you wanted to write the XML to a network, or even to a memory buffer? Both of these seem pretty likely scenarios. Fortunately, the Java platform comes to the rescue again with the
Writerinterface, which we'll cover when we discuss the code in-depth below.
Now that we've identified what we're going to do, let's get into the details.
To begin with, I've replanted
XMLBeanReader from the default package into a package called
com.javaworld.JavaBeans.XMLBeans , to which I've added
XMLBeanWriter, and to which we'll be adding other classes and interfaces later.
You can see the entire source for the
XMLBeanWriter at XMLBeanWriter.java, but I've placed the code sections we're discussing in scrolling text boxes below for your convenience. Use whichever works best for you.
At the highest level of abstraction, we want to define a method that writes the XML representation of a JavaBean to a file, given the bean class and a filename. I actually implemented three overloaded methods for added flexibility. The Java Core API provides an interface called
java.io.Writer, which is an abstraction for writing data. The concrete class
java.io.FileWriter writes data to a file, but other subclasses of
Writer allow writing to
String objects, buffers, print streams, pipes, and so on. This is sufficiently general that I put the actual logic in a method called
writeXMLBean, which takes the bean instance and a
Writer as arguments. By using the
XMLBeanWriter can write to any class that implements
java.io.Writer -- for example, a network connection. Two overloaded methods (meaning they have the same name, but different arguments) accept a
File object or a
String filename as an indication of where the data are to go. These methods are just for convenience, since our example program will write to a file. Let's take a look at the code.
Building and printing a document tree
There are three methods called
XMLBeanWriter.writeXMLBean(), and two of them simply call the third one. The code for all three
XMLBeanWriter.writeXMLBean() methods appears in the figure below.