Simplify XML processing with XMLConvenience Beans
This final article in the XML JavaBeans series builds on the previous two parts. Part 1 discussed nonvisual JavaBeans that parse XML documents into DOM trees, and Part 2 explained how Integrated Development Environments (IDEs) interconnect JavaBeans with event and property change relationships. This month, I tie together those articles by developing a somewhat more complex (though still functionally trivial) XML editor. You’ll see that as the application becomes more complex, the standard nonvisual XML JavaBeans quickly become more difficult to use. So, I explain how the XMLConvenience bean set simplifies creating XML GUI editors, with an example. I also cover a general Java programming topic: how to use interfaces to simulate multiple inheritance.
TEXTBOX: TEXTBOX_HEAD: Process XML with JavaBeans: Read the whole series!
-
Part 1. Interconnect JavaBeans to process XML
-
Part 2. How IDEs interconnect components
-
Part 3. Simplify XML processing with XMLConvenience Beans
:END_TEXTBOX
Building a better editor
The result of last month’s article was an XML editor that edited an XML document containing a single tag, <Name>
. Though the little application provided a good example of how IDEs wire applications together with event relationships, I think the result was disappointing. I certainly wasn’t hurrying home from work every night to play with the Recipe Name Editor. Figure 1 shows the extensions I’ve created to make the application more useful. I call this improved Recipe Editor the ComplicatedRecipeEditor
.
I’m making three extensions to last month’s editor. I start by adding a <Description>
tag (indicated by the letter A in Figure 1). Second, I add a Save File button (B), which saves the editor’s DOM document as an XML file. Third, I add a status bar for error messages (C), which also indicates the name of the file being edited.
I’ll do all of this the hard way first, using nonvisual beans and connecting them to standard AWT (Abstract Windowing Toolkit) interface components. The result will be a tangled web of event and property relationships. Then I’ll show you how to use XMLConvenience beans to create the same application with just a few objects and connections.
Adding the Description tag
Figure 2 shows last month’s application. I’m going to add a TextArea
so you can edit a recipe’s description as well as its name.
Figure 3 shows the additional components and connections that I made to generate a <Description>
tag to the editor. Don’t be intimidated by Figure 3’s complexity. (My apologies for the event and property connection lines overlapping the object labels. That’s how VisualAge for Java’s Visual Composition Editor draws things.)
The only additions are that of another ElementContainer
(F in Figure 3), which holds the <Description>
element, and a TextContainer
(H), which holds the text inside the <Description>
.
Take a look at the objects underneath the DocumentContainer1
object (C) in Figure 3. DocumentContainer1
has an ElementContainer
called ECFindRecipe
, which itself has two ElementContainer
s, ECFindName
and ECFindDesc
, each of which has a TextContainer
.
This structure of XML JavaBeans objects exactly reflects the structure of the DOM tree it represents, as Figure 4 illustrates. XML JavaBeans container classes are designed to can contain any structure that can be described by an XML DTD (document type definition). XML JavaBeans programmers can then connect these containers to other objects, such as GUI components, to do useful work.
A number of readers have been a bit confused about what the labels in the diagram refer to: classes or instances. Each object label in the diagram is the name of the instance, and it is the value of that instance’s beanName
property. VisualAge for Java makes up instance names by adding a number to the class name (as in the case of DocumentContainer1
). In some cases (like DOMGenerator
), I changed the instance name to the class name. It would be like naming my dog “Dog.” Just remember that all of the objects in the diagram are instances, not classes, and the whole discussion will make a lot more sense.
The class diagrammed in Figure 3 works by objects passing events and values to one another. A look at the diagram may not make it clear how these DOM elements are “passed” from one XML JavaBean to another as I’ve been describing. It’s really quite simple. The connection between the output of one XML JavaBean and the input of another is a bound property relationship. A bound property is a property whose value is set to track some other property of an object. In this case, the result
property of each XML JavaBean is bound to the input property of another bean. When the first bean produces a result, it sets its result
property, and the property relationship (by way of a PropertyChange
event) updates the other bean’s input to that same value. It is as if the first bean passed its result to the second bean.
The class whose structure is displayed in Figure 3 works almost the same as RecipeEditor
did in last month’s article. When a user clicks the Open File button, the DOMGenerator
(B in Figure 3) acquires an input filename from ExtendedFileDialog
and produces a DOM tree that represents the XML document in the file. DocumentContainer1
(C), which receives the DOM tree as input, passes any <Recipe>
element it finds at the top level of the tree through to its output. The <Recipe>
element in DocumentContainer1
is passed to an ElementContainer
called ECFindRecipe
(D), whose outputs are in turn connected to two other ElementContainer
s, ECFindName
(E) and ECFindDesc
(F). These containers search for the <Name>
and <Description>
nodes (respectively) of the Recipe
that contains them. Connected to the output of those last two containers are the TextContainer
objects NameContainer
(G) and DescContainer
(H), which contain Text
nodes that hold the text values of the recipe’s name and description. The content of the two Text
nodes is edited in the GUI.
Now that the editor can add descriptions, it’s time to add the status bar.
Adding a status bar to RecipeEditor
Figure 5 shows the ComplicatedRecipeEditor
, now with a status bar(A). The status bar itself is simply a java.awt.Label
with a navy blue background added to the application frame.
The Label
‘s text property controls the displayed text. Each of the event connections in Figure 5 sets this text to some value. DOMGenerator
(B) has three event connections that may set the status Label
‘s text:
-
Connection C:
DOMGenerator
‘sgenerationStarted
event, whichDOMGenerator
produces before it starts parsing, is connected to set theLabel
text to the string"Started parsing..."
. -
Connection D:
DOMGenerator
‘sgenerationError
event is connected to set theLabel
text to the string"Parse error!"
when a parse error occurs. - Connection E:
DOMGenerator
‘sgenerationOver
event, produced whenDOMGenerator
completes a successful parse, sets theLabel
text to the name of the file being edited. The link F attached toExtendedFileDialog
(G) supplies the name of the file as an argument to connection E.
By using the connections described above, DOMGenerator
reports its status to the user. It would be nice to extract the error message from DOMGenerator
and display it in the status bar; unfortunately, there is no easy way to do so from this IDE, since the error itself is expressed as an event.
The final improvement to the application is the addition of a Save To feature.
Saving the edited document
The implementation of the Save File function is straightforward. As you may remember from Part 1 of this series, the XMLFileGenerator
‘s XMLCore bean turns a DOM tree into an XML file and writes it to the file system. In other words, it performs the inverse operation of the DOMGenerator
class. Figure 6 shows an XMLFileGenerator
bean (B) added to the application, and the event relationships connected to it that control its operation.
When the user clicks the Save File button, the button’s actionPerformed()
method calls the XMLFileGenerator
‘s triggerAction
method. triggerAction
tells XMLFileGenerator
to write the DOM document at its inputDocument
property to the file whose name is the value of its xmlSaveLocation
property.
Note that the XMLFileGenerator
‘s autoAction
property is set to false
(though this is not visible on the diagram in Figure 6). autoAction
, when true
, causes the XMLFileGenerator
to write its output file immediately whenever its inputDocument
property is set to any DOM structure. autoAction
is false
in this example because if it weren’t, the XMLFileGenerator
would try to write a file every time a successful parse occurred. The application should write a file only when the user indicates that it should (by clicking the Save File button.)
Before XMLFileGenerator
processes its input, it emits a fetchArguments
event, which is used to set up the generator’s inputs. This event is used to set both the output filename and the input document for the XMLFileGenerator
.
To set the output filename, connection D hooks the fetchArguments
event to the show()
method of the ExtendedFileDialog
(C), causing the dialog to present a file selection box to the user. When the user selects a file, the ExtendedFileDialog
sets its fullpath
property to the pathname of the selected file. Connection E is a property change relationship that updates the XMLFileGenerator
‘s xmlSaveLocation
property, setting it to the selected file pathname.
Connection F is also an event relationship involving the fetchArguments
event fired by XMLFileGenerator
. Connection F sets the XMLFileGenerator
‘s inputDocument
property to the document contained in DocumentContainer1
— that is, the DOM document currently being edited. Connection G passes DocumentContainer1
‘s document to XMLFileGenerator
.
Once XMLFileGenerator
finishes with the list of objects listening for fetchArguments
, it assumes that its (possibly recently set) arguments are correct and writes the inputDocument
DOM tree to the filename indicated by xmlSaveLocation
in the form of an XML document.
Evaluating the improved application
Figure 6 shows the new ComplicatedRecipeEditor
with all of its modifications. If you download the sample code from Resources below, you can run the program. Try using the application with the example.xml
file included with the source code. The status bar reports what’s going on with the parse, the Save File feature saves the edited file, and the editor can edit both the name and the description in the XML file.
In sum, this application works quite well, but creating it was a pretty detailed process. Figure 6 shows a mess of property and event relationships. Since VisualAge for Java doesn’t label the connections, it’s hard to tell by looking at the diagram what is actually happening. A lot of the event connections are repetitive, such as those between the text container XML JavaBeans and the text items in the GUI. Fortunately, the creators of the XML JavaBeans suite also created classes that ease the creation of fully functional XML editors. These XMLConvenience classes are a combination of AWT user interface components with the XML containers we’ve been discussing. In the next section, I present a couple of these convenience classes and, with very few components, use them to implement the editor we just wrote.
SUBHEAD_BREAK: XMLConvenience to the rescue
ComplicatedRecipeEditor
was precisely that: too complicated. If such a small application starts looking tangled, imagine how cluttered a large application will look!
Many of the beans in the XMLConvenience bean set combine the XML-processing capabilities of the other XML JavaBeans classes with the graphic display and editing capabilities of the AWT. The connections of, for example, TextContainer
objects with the TextField
widgets used to edit them are combined into a single class called ElementEntryField
. An ElementEntryField
plays the role of both a TextContainer
and a GUI text field.
The XML JavaBeans authors recommend that programmers using the XML JavaBeans package with GUI frameworks other than AWT (Swing, for example) create analogous convenience components for the target framework, instead of trying to create the application with simple connections (as in the ComplicatedRecipeEditor
above).
To see how simple an editor application can be with these XML Convenience beans, see Figure 7.
Believe it or not, the application in Figure 7 performs the same functions as the tangled mess in Figure 6. As noted above, each convenience class groups one or more XML JavaBeans within a subclass of a GUI component. The first of these classes is the XMLEditor
class, a subclass of java.awt.Frame
that incorporates four “hidden” XML JavaBeans: a DOMGenerator
, an XMLFileGenerator
, an ExtendedFileDialog
, and a DocumentContainer
. They’re all connected to one another pretty much as the same components are connected in Figure 6, but the XML JavaBeans components are private members of the XMLEditor
class. This concept of “wrapping” several classes into a single class with higher-level functionality is illustrated in Figure 8.
The XMLEditor
class (A in Figure 7) provides several methods to which other component instances are attached in the new EasyRecipeEditor
:
-
Connection B: The Clear button is connected to the
XMLEditor
‘striggerNew()
method, which sets theXMLEditor
‘s internal DOM document to an empty document. -
Connection C: The Open button calls the
XMLEditor
‘striggerOpen()
method, which presents the user with a file dialog. The user chooses a file, and theXMLEditor
parses the file and sets itscurrentNode
property to the resulting DOM document. -
Connection D: The Save button’s
actionPerformed
event is connected to execute theXMLEditor
‘striggerSave()
method. This method gets theXMLEditor
‘sresult
property value, which is a DOMDocument
object, and writes it to the file whose name is given by theXMLEditor
‘sxmlPath
property. -
Connection E: It’s identical to connection D, but it prompts for a new filename before writing the file.
-
Connection I: This property change relationship binds the
XMLEditor
‘scurrentNode
property to theinputParentNode
property ofECFindRecipe
, anElementContainer
configured to match a DOMElement
with a<Recipe>
tagname. When theXMLEditor
‘sinputParentNode
property changes,ECFindRecipe
‘sinputParentNode
is set to the new document. - Connections H and J: Each of these connections links
ECFindRecipe
‘scurrentNode
property to theinputParentNode
property of anElementEntryField
.ElementEntryField
is another XMLConvenience class. It associates ajava.awt.TextField
element with anElementContainer
(to hold the element) and aTextContainer
(to hold the text within the element). EachElementEntryField
has a property,elementName
, which is the name of the element that the component references. The entry searches for a node directly underneath itsinputParentNode
for a tagname that is the same as itselementName
property. Editing the text in theElementEntryField
edits the text of theText
node within that element. The topElementEntryField
edits the recipe’sName
(because it’s looking for element<Name>
) and the bottom one edits the document’sDescription
.
The XMLEditor
class “exposes” a new API that offers higher-level functions such as New and Save As. XMLEditor
is a GUI component that contains private instances of XML JavaBeans. An XMLEditor
implements the XML editing functionality by using these internal XML JavaBeans, but the implementation is hidden from the programmer — as it should be. It makes sense to hide the XML JavaBeans from the programmer, since the whole point of creating this class is to hide the complexities of the connection between the GUI component and the XML JavaBean.
Only certain functions are exposed in the XMLEditor
‘s API, and some of the functions of the internal objects are missing. For example, the XMLEditor
doesn’t expose any of the XML parse error events that DOMGenerator
creates, so there’s no way to detect and report XML parse errors. This oversight on the part of the XML JavaBeans designers makes it impossible to completely emulate the ComplicatedRecipeEditor
. Despite this minor deficit, it was much easier to create the new editor with the XMLConvenience class.
The convenience classes bring up an interesting object-oriented programming issue: should delegation be used to mimic multiple inheritance? The ElementEntryField
, for example, provides some of the functionality of a java.awt.TextField
, an ElementContainer
, and a TextContainer
. If Java had multiple inheritance, as do some other object-oriented languages such as C++, it would be possible to say:
public class ElementEntryField extends TextField, ElementContainer, TextContainer
But that is not the case. The alternative is to create the ElementEntryField
class that has, rather than is, a TextField
, an ElementContainer
, and a TextContainer
, and then expose as public methods the API of each of those three subcomponents. The result is a class that looks as if it has multiple inheritance, but the methods of the new class simply defer (or “delegate”) the calls to the (internal) field that handle such a call.
Figure 9 illustrates that concept. On the left you see the class TextEntryElement
, which is a subclass of three other classes. The subclass inherits the union set of the methods of the superclass.
While it may be useful to create such objects, Java doesn’t allow multiple inheritance. Instead, the Java approach is to have private fields contain an instance of each of the superclasses (so to speak), and then write methods that use those instances for implementation. For example, if the TextContainer
in Figure 9 had a method called getInputParentNode()
, then ElementEntryField()
could implement that method by delegation instead of inheriting it from TextContainer
:
private TextContainer _localTextContainer;
// and so on...
public Node getInputParentNode() {
return _localTextContainer.getInputParentNode();
}
That technique may also be used with interfaces if the methods have no reasonable default implementation. A single Java class may implement an arbitrary number of interfaces, but it may have only one superclass.
In this way, multiple inheritance can be simulated without the ambiguities and confusion that often accompany multiple inheritance. All ambiguities are explicitly resolved by choosing an object to handle the delegation.
Going further
You may have noticed that up to this point the XML editors presented don’t handle relationships in which an arbitrary number of elements may be contained in an element. There’s been no mention, for example, of how to add an <Ingredients>
list to the recipe. This month’s sample code contains an example of how to do just that, although there’s not enough space to discuss it. The class, called RecipeEditorWithIngredients
, uses a StarOperatorPanel
object to edit the list of <Ingredients>
. The StarOperatorPanel
class is an XMLConvenience bean that combines an AWT Panel
, some Button
objects, and a StarOperator
object (discussed in the XML JavaBeans documentation) to visually edit a list of zero or more children of a given DOM Node
.
The StarOperator
object gets its name from the asterisk (*
) character, whose function it emulates. In a DTD, the asterisk (or “star”) indicates the presence of zero or more child nodes, as in:
<!ELEMENT Ingredients (Ingredient)*>
Other DTD operators — such as ?
and +
— have XML JavaBeans classes that perform their functions, so that XML processing structures can be built for any XML DTD.
Conclusion
In the past three months, you’ve learned about XML JavaBeans and seen some examples of creating a software application without writing any Java code. Among the benefits of component-based software development are increased software reuse and improved encapsulation in design. There’s much more to XML JavaBeans than I’ve been able to present here. I urge you to download the XML JavaBeans suite from alphaWorks and experiment with it yourself. You’ll be creating codeless XML processing applications in no time.