Programming XML in Java, Part 1

Create Java apps with SAX appeal

So, you understand (more or less) how you would represent your data in XML, and you're interested in using XML to solve many of your data-management problems. Yet you're not sure how to use XML with your Java programs.

TEXTBOX: TEXTBOX_HEAD: Programming XML in Java: Read the whole series!

:END_TEXTBOX

This article is a follow-up to my introductory article, "XML for the absolute beginner", in the April 1999 issue of JavaWorld (see the Resources section below for the URL). That article described XML; I will now build on that description and show in detail how to create an application that uses the Simple API for Java (SAX), a lightweight and powerful standard Java API for processing XML.

The example code used here uses the SAX API to read an XML file and create a useful structure of objects. By the time you've finished this article, you'll be ready to create your own XML-based applications.

The virtue of laziness

Larry Wall, mad genius creator of Perl (the second-greatest programming language in existence), has stated that laziness is one of the "three great virtues" of a programmer (the other two being impatience and hubris). Laziness is a virtue because a lazy programmer will go to almost any length to avoid work, even going so far as creating general, reusable programming frameworks that can be used repeatedly. Creating such frameworks entails a great deal of work, but the time saved on future assignments more than makes up for the initial effort invested. The best frameworks let programmers do amazing things with little or no work -- and that's why laziness is virtuous.

XML is an enabling technology for the virtuous (lazy) programmer. A basic XML parser does a great deal of work for the programmer, recognizing tokens, translating encoded characters, enforcing rules on XML file structure, checking the validity of some data values, and making calls to application-specific code, where appropriate. In fact, early standardization, combined with a fiercely competitive marketplace, has produced scores of freely available implementations of standard XML parsers in many languages, including C, C++, Tcl, Perl, Python, and, of course, Java.

The SAX API is one of the simplest and most lightweight interfaces for handling XML. In this article, I'll use IBM's XML4J implementation of SAX, but since the API is standardized, your application could substitute any package that implements SAX.

SAX is an event-based API, operating on the callback principle. An application programmer will typically create a SAX Parser object, and pass it both input XML and a document handler, which receives callbacks for SAX events. The SAX Parser converts its input into a stream of events corresponding to structural features of the input, such as XML tags or blocks of text. As each event occurs, it is passed to the appropriate method of a programmer-defined document handler, which implements the callback interface org.xml.sax.DocumentHandler. The methods in this handler class perform the application-specific functionality during the parse.

For example, imagine that a SAX parser receives a document containing the tiny XML document shown in Listing 1 below. (See Resources for the XML file.)

<POEM>
<AUTHOR>Ogden Nash</AUTHOR>
<TITLE>Fleas</TITLE>
<LINE>Adam</LINE>
<LINE>Had 'em.</LINE>
</POEM>

Listing 1. XML representing a short poem

When the SAX parser encounters the <POEM> tag, it calls the user-defined DocumentHandler.startElement() with the string POEM as an argument. You implement the startElement() method to do whatever the application is meant to do when a POEM begins. The stream of events and resulting calls for the piece of XML above appears in Table 1 below.

Table 1. The sequence of callbacks SAX produces while parsing Listing 1
Item encounteredParser callback
{Beginning of document}startDocument()
<POEM> startElement("POEM", {AttributeList})
"\n"characters("<POEM>\n...", 6, 1)
<AUTHOR>startElement("AUTHOR", {AttributeList})
"Ogden Nash"characters("<POEM>\n...", 15, 10)
</AUTHOR> endElement("AUTHOR")
"\n"characters("<POEM>\n...", 34, 1)
<TITLE> startElement("TITLE", {AttributeList})
"Fleas"characters("<POEM>\n...", 42, 5)
</TITLE> endElement("TITLE")
"\n"characters("<POEM>\n...", 55, 1)
<LINE> startElement("LINE", {AttributeList})
"Adam"characters("<POEM>\n...", 62, 4)
</LINE> endElement("LINE")
<LINE> startElement("LINE", {AttributeList})
"Had 'em."characters("<POEM>\n...", 67, 8)
</LINE> endElement("LINE")
"\n"characters("<POEM>\n...", 82, 1)
</POEM> endElement("POEM")
{End of document}endDocument()

You create a class that implements DocumentHandler to respond to events that occur in the SAX parser. These events aren't Java events as you may know them from the Abstract Windowing Toolkit (AWT). They are conditions the SAX parser detects as it parses, such as the start of a document or the occurrence of a closing tag in the input stream. As each of these conditions (or events) occurs, SAX calls the method corresponding to the condition in its DocumentHandler.

So, the key to writing programs that process XML with SAX is to figure out what the DocumentHandler should do in response to a stream of method callbacks from SAX. The SAX parser takes care of all the mechanics of identifying tags, substituting entity values, and so on, leaving you free to concentrate on the application-specific functionality that uses the data encoded in the XML.

Table 1 shows only events associated with elements and characters. SAX also includes facilities for handling other structural features of XML files, such as entities and processing instructions, but these are beyond the scope of this article.

The astute reader will notice that an XML document can be represented as a tree of typed objects, and that the order of the stream of events presented to the DocumentHandler corresponds to an in-order, depth-first traversal of the document tree. (It isn't essential to understand this point, but the concept of an XML document as a tree data structure is useful in more sophisticated types of document processing, which will be covered in later articles in this series.)

The key to understanding how to use SAX is understanding the DocumentHandler interface, which I will discuss next.

Customize the parser with org.xml.sax.DocumentHandler

Since the DocumentHandler interface is so central to processing XML with SAX, it's worthwhile to understand what the methods in the interface do. I'll cover the essential methods in this section, and skip those that deal with more advanced topics. Remember, DocumentHandler is an interface, so the methods I'm describing are methods that you will implement to handle application-specific functionality whenever the corresponding event occurs.

Document initialization and cleanup

For each document parsed, the SAX XML parser calls the DocumentHandler interface methods startDocument() (called before processing begins) and endDocument() (called after processing is complete). You can use these methods to initialize your DocumentHandler to prepare it for receiving events and to clean up or produce output after parsing is complete. endDocument() is particularly interesting, since it's only called if an input document has been successfully parsed. If the Parser generates a fatal error, it simply aborts the event stream and stops parsing, and endDocument() is never called.

Processing tags

The SAX parser calls startElement() whenever it encounters an open tag, and endElement() whenever it encounters a close tag. These methods often contain the code that does the majority of the work while parsing an XML file. startElement()'s first argument is a string, which is the tag name of the element encountered. The second argument is an object of type AttributeList, an interface defined in package org.xml.sax that provides sequential or random access to element attributes by name. (You've undoubtedly seen attributes before in HTML; in the line <TABLE BORDER="1">, BORDER is an attribute whose value is "1"). Since Listing 1 includes no attributes, they don't appear in Table 1. You'll see examples of attributes in the sample application later in this article.

Since SAX doesn't provide any information about the context of the elements it encounters (that <AUTHOR> appears inside <POEM> in Listing 1 above, for example), it is up to you to supply that information. Application programmers often use stacks in startElement() and endElement(), pushing objects onto a stack when an element starts, and popping them off of the stack when the element ends.

Process blocks of text

The characters() method indicates character content in the XML document -- characters that don't appear inside an XML tag, in other words. This method's signature is a bit odd. The first argument is an array of bytes, the second is an index into that array indicating the first character of the range to be processed, and the third argument is the length of the character range.

It might seem that an easier API would have simply passed a String object containing the data, but characters() was defined in this way for efficiency reasons. The parser has no way of knowing whether or not you're going to use the characters, so as the parser parses its input buffer, it passes a reference to the buffer and the indices of the string it is viewing, trusting that you will construct your own String if you want one. It's a bit more work, but it lets you decide whether or not to incur the overhead of String construction for content pieces in an XML file.

The characters() method handles both regular text content and content inside CDATA sections, which are used to prevent blocks of literal text from being parsed by an XML parser.

Other methods

There are three other methods in the DocumentHandler interface: ignorableWhitespace(), processingInstruction(), and setDocumentLocator(). ignorableWhitespace() reports occurrences of white space, and is usually unused in nonvalidating SAX parsers (such as the one we're using for this article); processingInstruction() handles most things within <? and ?> delimiters; and setDocumentLocator() is optionally implemented by SAX parsers to give you access to the locations of SAX events in the original input stream. You can read up on these methods by following the links on the SAX interfaces in Resources.

Implementing all of the methods in an interface can be tedious if you're only interested in the behavior of one or two of them. The SAX package includes a class called HandlerBase that basically does nothing, but can help you take advantage of just one or two of these methods. Let's examine this class in more detail.

HandlerBase: A do-nothing class

Often, you're only interested in implementing one or two methods in an interface, and want the other methods to simply do nothing. The class org.xml.sax.HandlerBase simplifies the implementation of the DocumentHandler interface by implementing all of the interface's methods with do-nothing bodies. Then, instead of implementing DocumentHandler, you can subclass HandlerBase, and only override the methods that interest you.

For example, say you wanted to write a program that just printed the title of any XML-formatted poem (like TitleFinder in Listing 1). You could define a new DocumentHandler, like the one in Listing 2 below, that subclasses HandlerBase, and only overrides the methods you need. (See Resources for an HTML file of TitleFinder.)

012 /**
013 * SAX DocumentHandler class that prints the contents of "TITLE" element
014 * of an input document.
015 */
016 public class TitleFinder extends HandlerBase {
017     boolean _isTitle = false;
018 public TitleFinder() {
019     super();
020 }
021     /**
022  * Print any text found inside a <TITLE> element.
023   */
024 public void characters(char[] chars, int iStart, int iLen) {
025     if (_isTitle) {
026         String sTitle = new String(chars, iStart, iLen);
027         System.out.println("Title: " + sTitle);
028     }
029 }
030     /**
031  * Mark title element end.
032   */
033 public void endElement(String element) {
034     if (element.equals("TITLE")) {
035         _isTitle = false;
036     }
037 }
038     /**
039  * Find contents of titles
040   */
041 public static void main(String args[]) {
042     TitleFinder titleFinder = new TitleFinder();
043     try {
044         Parser parser = ParserFactory.makeParser("com.ibm.xml.parsers.SAXParser");
045         parser.setDocumentHandler(titleFinder);
046         parser.parse(new InputSource(args[0]));
047     } catch (Exception ex) {
048         ; // OK, so sometimes laziness *isn't* a virtue.
049     }
050 }
051     /**
052  * Mark title element start
053   */
054 public void startElement(String element, AttributeList attrlist) {
055     if (element.equals("TITLE")) {
056         _isTitle = true;
057     }
058 }


Listing 2. TitleFinder: A DocumentHandler derived from HandlerBase that prints TITLEs

This class's operation is very simple. The characters() method prints character content if it's inside a <TITLE>. The private boolean field _isTitle keeps track of whether the parser is in the process of parsing a <TITLE>. The startElement() method sets _isTitle to true when a <TITLE> is encountered, and endElement() sets it to false when </TITLE> is encountered.

To extract <TITLE> content from <POEM> XML, simply create a <Parser> (I'll show you how to do this in the sample code below), call the Parser's setDocumentHandler() method with an instance of TitleFinder, and tell the Parser to parse XML. The parser will print anything it finds inside a <TITLE> tag.

The TitleFinder class only overrides three methods: characters(), startElement(), and endElement(). The other methods of the DocumentHandler are implemented by the HandlerBase superclass, and those methods do precisely nothing -- just what you would have done if you'd implemented the interface yourself. A convenience class like HandlerBase isn't necessary, but it simplifies the writing of handlers because you don't need to spend a lot of time writing idle methods.

As an aside, sometimes in Sun documentation you'll see javadocs with method descriptions like "deny knowledge of child nodes." Such a description has nothing to do with paternity suits or Mission: Impossible; instead, it is a dead giveaway that you're looking at a do-nothing convenience class. Such classes often have the words Base, Support, or Adapter in their names.

A convenience class like HandlerBase does the job, but still isn't quite smart enough. It doesn't limit you to a <TITLE> element inside a <POEM>; it would print the titles of HTML files, too, for example. And any tags inside a <TITLE>, such as <B> tags for bolding, would be lost. Since SAX is a simplified interface, it's left up to the application developer to handle things like tag context.

Now you've seen a useless, simple example of SAX. Let's get into something more functional and interesting: an XML language for specifying AWT menus.

An applied example: AWT menus as XML

Recently I needed to write a menu system for a Java program I was developing. Writing menus in Java 1.1 is really quite easy. The top-level object in a menu structure is either a MenuBar or a PopupMenu object. A MenuBar contains sub-Menu objects, while PopupMenu and Menu objects can contain Menus, MenuItems, and CheckboxMenuItems. Typically, objects of this type are constructed manually in Java code, and built into a menu tree via calls to the add() methods of the parent object.

Listing 3 shows the Java code that creates the menu shown in Figure 1.

   MenuBar menubarTop = new MenuBar();
   Menu menuFile = new Menu("File");
   Menu menuEdit = new Menu("Edit");
    
   menubarTop.add(menuFile);
   menubarTop.add(menuEdit);
        
   menuFile.add(new MenuItem("Open"));
   menuFile.add(new MenuItem("Close"));
   menuFile.add(new MenuItem("And so on..."));
   menuEdit.add(new MenuItem("Cut"));
   menuEdit.add(new MenuItem("Paste"));
   menuEdit.add(new MenuItem("Delete"));
        
   Frame frame = new Frame("ManualMenuDemo");
   frame.addWindowListener(new WindowAdapter() {
    public void windowClosing(WindowEvent e) {
        System.exit(0);
    }
   });
   frame.setMenuBar(menubarTop);
   frame.pack();
   frame.show();

Listing 3. Creating a simple menu

Figure 1 below shows the simple menu that was handcoded in Java from Listing 3.

Figure 1. The resulting menu of Listing 3 (below)

Simple enough, right? Well, not for me. Remember, I'm a lazy programmer, and I don't like having to write all of this code to create these menus. And I haven't even begun to write all of the ActionListener and ItemListener classes I need to actually make these menus operate. No, I want something easier.

I'd much rather have a menu specification language that lets me specify the menu structurally, and notifies my program through a single interface when user events occur. I also want to be able to reconfigure my menus without having to rewrite any code. I want to create menu structures for naive or expert users simply by changing the menu specification, and possibly rename the menu items without changing any code. I want lots of functionality, and I don't want to have to work for it.

Since I'm lazy, I'll choose an off-the-shelf SAX XML parser to do my work for me. I'll specify the file format as an XML file. Then I'll create a class called SaxMenuLoader that uses a SAX XML parser to create menu structures defined by XML, stores the menus in a Hashtable, and then returns the menus when I ask for them by name.

This SaxMenuLoader will also listen for ActionEvents and ItemEvents from the menu items it creates, and will call appropriate handler methods to handle the actions. Once I've written this SaxMenuLoader, all I need to do in the future is create a SaxMenuLoader instance and tell it to load my XML menu specification; then I can ask it by name for the MenuBars and PopupMenus defined in the XML. (Well, I'll also have to write and name the handlers, but that's application functionality. This system can't do everything for me. Yet.)

Menu XML

For this example, I've created a little language I'll call Menu XML. Depending on your application, you may want to implement a standard XML dialect, defined in a document type definition (DTD) by a standards organization or some other group. In this case, I'm just using XML for controlling the configuration of my application, so I don't care if the XML is standardized.

I'll introduce Menu XML with an example, which appears in Listing 4. (See Resources for an HTML file for Menu XML.)

001 <?xml version="1.0"?>
002 
003 <Menus>
004 
005    <!-- The menu bar at the top of the frame -->
006    <MenuBar NAME="TopMenu">
007 
008       <Menu NAME="File" HANDLER="FileHandler">
009          <MenuItem NAME="FileOpen" LABEL="Open..."/>
010          <MenuItem NAME="FileSave" LABEL="Save"/>
011          <MenuItem NAME="FileSaveAs" LABEL="Save As..."/>
012          <MenuItem NAME="FileExit" LABEL="Exit"/>
013       </Menu>
014 
015       <Menu NAME="Edit" HANDLER="EditHandler">
016          <MenuItem NAME="EditUndo" LABEL="Undo"/>
017          <MenuItem NAME="EditCut" LABEL="Cut"/>
018          <MenuItem NAME="EditPaste" LABEL="Paste"/>
019          <MenuItem NAME="EditDelete" LABEL="Delete"/>
020          <CheckboxMenuItem NAME="EditReadOnly" LABEL="Disable Button 1"021              HANDLER="Button1Enabler"/>
022       </Menu>
023 
024       <Menu NAME="Help" HANDLER="HelpHandler">
025          <MenuItem NAME="HelpAbout" LABEL="About"/>
026          <MenuItem NAME="HelpTutorial" LABEL="Tutorial"/>
027       </Menu>
028 
029    </MenuBar>
030 
031    <PopupMenu NAME="Pop1" HANDLER="PopupHandler">
032       <Menu NAME="Sub Menu 1" HANDLER="SubMenu1Handler">
033          <MenuItem NAME="Item 1" COMMAND="Item One"/>
034          <MenuItem NAME="Item 2" COMMAND="Item Two"/>
035       </Menu>
036       <MenuItem NAME="Item 3" COMMAND="Item Three"/>
037       <MenuItem NAME="Item 4" COMMAND="Item Four"/>
038       <MenuItem NAME="Item 5" COMMAND="Item Five"039                 HANDLER="com.javaworld.feb2000.sax.DynamicMenuItemHandler"/>
040    </PopupMenu>
041    
042 </Menus>

f

Listing 4. Sample Menu XML to be processed by sample code

This language has just a few tags and attributes:

  • <Menus>: This is the document element for this language. The <menus> tag simply groups all of the menus below it.
  • <MenuBar NAME="name">: The <MenuBar> tag defines a new java.awt.MenuBar object. When parsing is completed, the menu bar will be accessible by the given name.
  • <PopupMenu NAME="name">: The <PopupMenu> tag defines a new java.awt.PopupMenu object. When parsing is completed, the popup menu will be accessible by the given name.
  • <MenuItem NAME="name" [LABEL="label"] [COMMAND="command"]>: This tag defines a java.awt.MenuItem. The item's label defaults to its name, but can be set with the LABEL attribute. The default actionCommand for the item is also the item's name, but may be set with the COMMAND attribute.
  • <CheckboxMenuItem NAME="name" [LABEL="label"] [COMMAND="command"]>: This tag defines a java.awt.CheckboxMenuItem. It's just like a MenuItem, except that the menu item checks and unchecks when selected, instead of executing an action.

Any of these tags may optionally take an attribute HANDLER="handlerName", which indicates the name of the handler for that object and all of its children (unless one of its children overrides the current handler by defining its own handler). The handler name indicates what object and method are to be called when the menu item is activated. The mechanism for associated handler names with their handler objects is explained in the implementation discussion below.

The containment relationship among the tags directly reflects the containment relationship of the resulting objects. So, for example, the PopupMenu called Pop1 defined in Listing 4, line 31, contains a single Menu and three MenuItems. As the SaxMenuLoader class parses the XML file, it creates appropriate Java menu objects and connects them to reflect the XML structure. Let's look at the code for SaxMenuLoader.

Load Menu XML with SAX: The SaxMenuLoader class

The following is a list of SaxMenuLoader's responsibilities:

  • Parses the Menu XML file using a SAX parser.
  • Builds the menu tree.
  • Acts as a repository for the MenuBar and PopupMenu items defined in the Menu XML.
  • Maintains a repository of event handler objects that are called when the user selects menu items. An event handler object is any object that implements interface MenuItemHandler, defined by this package to unify action and item events from menu items. Any object that implements this interface can receive events from MenuItems defined in Menu XML. (I'll cover the MenuItemHandler in more detail shortly.)
  • Acts as an ActionListener and ItemListener for all menu items.
  • Dispatches ActionEvents and ItemEvents to the appropriate handlers for the menu items.

Use SaxMenuLoader

The MenuDemo class takes two arguments: the name of the Menu XML file to parse, and the name of the MenuBar to place in the application. MenuDemo.main() simply creates a MenuDemo instance, and calls that instance's runDemo() method. The method MenuDemo.runDemo(), shown in Listing 5, demonstrates how to use the SaxMenuLoader in use. (See Resources for an HTML file of SaxMenuLoader and MenuDemo.)

094 public void runDemo(String[] args) {
095     SaxMenuLoader sml = new SaxMenuLoader();
096 
097     // Bind names of handlers to the MenuItemHandlers they represent
098     sml.registerMenuItemHandler("FileHandler", this);
099     sml.registerMenuItemHandler("EditHandler", this);
100     sml.registerMenuItemHandler("HelpHandler", this);
101     sml.registerMenuItemHandler("PopupHandler", this);
102     sml.registerMenuItemHandler("SubMenu1Handler", this);
103     sml.registerMenuItemHandler("Button1Enabler", this);
104 
105     // Parse the file
106     sml.loadMenus(args[0]);
107 
108     // If menu load succeeded, show the menu in a frame
109     MenuBar menubarTop = sml.menubarFind(args[1]);
110     if (menubarTop != null) {
111         Frame frame = new Frame("Menu demo 1");
112         frame.addWindowListener(new WindowAdapter() {
113             public void windowClosing(WindowEvent e) {
114                 System.exit(0);
115             }
116         });
117         frame.setMenuBar(menubarTop);
118         _b1 = new Button("Button");
119         _b1.addMouseListener(new MenuPopper(_b1, sml, "Pop1"));
120         frame.add(_b1);
121         frame.pack();
122         frame.show();
123     } else {
124         System.out.println(args[1] + ": no such menu");
125     }
126 }

Listing 5. Using the SaxMenuLoader in the MenuDemo class

In Listing 5, line 95 creates the SaxMenuLoader. Then, lines 98 through 103 register the MenuDemo instance (this) as the MenuItemHandler for all of the handler names referenced in the Menu XML file. Since MenuDemo implements MenuItemHandler, it can receive callbacks from the menu items created in the Menu XML. These registrations are what associate the symbolic menu item handler names with the application objects that actually do the work. Line 106 tells the SaxMenuLoader to load the file, and line 109 gets the menu named MenuTop from the SaxMenuLoader.

The rest of the code is straightforward AWT, except for line 119, which uses a MenuPopper object to associate a Button object with a pop-up menu. MenuPopper is a convenience class I wrote that looks up a named pop-up menu from a given SaxMenuLoader, and associates the pop-up menu with the given AWT component. A MenuPopper is also a MouseListener, so that when the user clicks the center or left mouse button on the MenuPopper's component, the MenuPopper shows the pop-up menu on top of that component.

This is all the code necessary to get menus from a Menu XML file. You might have noticed that this is about as many lines of code as it took to create a small menu manually. But this technique provides much more power. You can reconfigure the menus without recompiling or redistributing any class files. What's more, you can extend the application with new menu items and handlers for those items without recompiling. (I'll discuss how to dynamically extend a running application with dynamic menu item handlers in the "Dynamic Menu Item Handlers" section later in the article.) From now on, creating extensible application menus is a lazy person's job!

So far, I've shown you how to use the SaxMenuLoader. Now let's take a look at how it works.

Parse the XML with SAX

You'll remember that an object that implements DocumentHandler can receive events from a SAX parser. Well, the SaxMenuLoader has a SAX parser and it also implements DocumentHandler, so it can receive events from that parser. SaxMenuLoader's loadMenus() method is overloaded for multiple types of inputs (File, InputStream, and so forth), but all eventually call the method shown in Listing 6.

279 public void loadMenus(Reader reader_) {
280     if (_parser == null)
281         return;
282     _parser.setDocumentHandler(this);
283     try {
284         _parser.parse(new InputSource(reader_));
285     } catch (SAXException ex) {
286         System.out.println("Parse error: " + ex.getMessage());
287     } catch (Exception ex) {
288         System.err.println("SaxMenuFactory.loadMenus(): " + ex.getClass().getName() +
289             ex.getMessage());
290         ex.printStackTrace();
291     }
292 }

Listing 6. loadMenus() uses a SAX parser to parse menus

There's not much to this method -- it simply sets the parser's DocumentHandler to this, calls the parser's parse() method, and handles any exceptions. How could this possibly build a menu?

The answer is in the implementation of DocumentHandler. Since SaxMenuLoader implements DocumentHandler, all of the menu-building functionality (which is specific to this application) occurs in the DocumentHandler implementation methods -- primarily in startElement().

SaxMenuLoader.startElement()

Listing 7 shows the implementation of startElement() that creates the MenuBar, PopupMenu, Menu, MenuItem, and CheckboxMenuItem objects and associates them with one another. As the parser parses the XML, it calls SaxMenuLoader.startElement() each time it encounters an opening XML tag, passing the tag name and the list of attributes for the tag. startElement() simply calls an appropriate protected method within SaxMenuLoader based on the tag name.

445 public void startElement(String sName_, AttributeList attrs_) {
446 
447     // Anything may override handler for its context
448     String sHandler = attrs_.getValue("HANDLER");
449     pushMenuItemHandler(sHandler);
450     
451     // If "menubar", we're building a MenuBar
452     if (sName_.equals("MenuBar")) {
453         defineMenuBar(attrs_);
454     }
455     
456     // If "popupMenu", we're building a PopupMenu
457     else if (sName_.equals("PopupMenu")) {
458         definePopupMenu(attrs_);
459     } 
460 
461     // If "menu", then create a menu.
462     else if (sName_.equals("Menu")) {
463         defineMenu(attrs_);
464     }
465     
466     else if (sName_.equals("MenuItem")) {
467         defineMenuItem(attrs_);
468     }
469 
470     else if (sName_.equals("CheckboxMenuItem")) {
471         defineCheckboxMenuItem(attrs_);
472     }
473 }

Listing 7. SaxMenuLoader.startElement()

This method does one additional thing: as noted above, any tag in Menu XML can include an optional HANDLER name, which defines the handler for all items that element contains. For example, line 8 of Listing 4 defines FileHandler as the name of the handler to call when any item in the File menu is selected. startElement() implements this functionality in lines 448 and 449 by detecting the HANDLER attribute on any tag and calling pushMenuItemHandler, which pushes the named MenuItemHandler onto a stack maintained by the SaxMenuLoader. Therefore, whatever is on top of the MenuItemHandler stack is always the appropriate handler for any item to be created. startElement() always pushes a handler (unless none has ever been specified); when an element doesn't specify a HANDLER, pushMenuItemHandler pushes another copy of whatever is on top of the stack. Later, endElement() always pops a handler off of the stack if it can, so the balance between stack pushes and pops is always maintained.

The methods called by startElement do the actual work of creating the menu tree. I'll cover those next.

Create the Menu tree

Listing 8 shows defineMenuBar, which is called when startElement receives a MenuBar element.

154 protected void defineMenuBar(AttributeList attrs_) {
155   String sMenuName = attrs_.getValue("NAME");
156   _menubarCurrent = new MenuBar();
157   if (sMenuName != null) {
158       _menubarCurrent.setName(sMenuName);
159   }
160   register(_menubarCurrent);
161 }
...
190 protected void definePopupMenu(AttributeList attrs_) {
191   String sMenuName = attrs_.getValue("NAME");
192   _popupmenuCurrent = new PopupMenu();
193   if (sMenuName != null) {
194       _popupmenuCurrent.setName(sMenuName);
195   }
196   register(_popupmenuCurrent);
197 }

Listing 8. defineMenuBar() and definePopupMenu()

As you can see, defineMenuBar() does very little: it simply creates a new MenuBar, assigns it a name if one is provided, and then registers it. The register() method simply stores the MenuBar in a protected hash table, so that you can retrieve it by name using the method menuBarFind() (as in Listing 5, line 109). definePopupMenu() works just like defineMenuBar(), except it creates a PopupMenu object and registers it so that popupmenuFind() can return the new PopupMenu by name.

The private static fields _menubarCurrent and _popupmenuCurrent contain a reference to the current MenuBar or PopupMenu being built, to which subsequent menus or menu items are added. Listing 9 shows the definition of a new Menu object.

052 protected void add(Menu menu_) {
053   Menu menuCurrent = menuCurrent();
054   if (menuCurrent != null) {
055       menuCurrent.add(menu_);
056   } else {
057       if (_menubarCurrent != null) {
058           _menubarCurrent.add(menu_);
059       }
060       if (_popupmenuCurrent != null) {
061           _popupmenuCurrent.add(menu_);
062       }
063   }
064 }
...
130 protected void defineMenu(AttributeList attrs_) {
131   String sMenuName = attrs_.getValue("NAME");
132 
133   Menu menuNew = new Menu(sMenuName);
134   if (sMenuName != null) {
135       menuNew.setName(sMenuName);
136   } else {
137       sMenuName = menuNew.getName();
138   }
139   System.out.print("Created menu " + sMenuName);
140 
141   // Add to current context and make new menu the current menu to build
142   add(menuNew);
143   pushMenu(menuNew);
144 }

Listing 9. add(Menu) and defineMenu()

defineMenu() is only slightly more complicated than defineMenuBar(), because the menu being created is added to whatever is currently being built, whether that is a MenuBar, a PopupMenu, or another Menu. The defineMenu() method creates a new Menu object, sets its name, and then calls add(Menu), which adds the given Menu either to the top menu of the menu stack (a private field), the current MenuBar, or the PopupMenu under construction. After adding the new Menu to the appropriate parent, defineMenu() pushes the new menu onto the menu stack. Anything contained inside the current menu in the XML file will be added to the current menu at the top of the stack, so the resulting menu structure reflects the XML structure. endElement() always calls popMenu() when it receives a <Menu> tag, so the top of the stack always refers to the menu currently under construction.

These stacks are necessary because, as stated before, SAX doesn't keep track of tag context; that's part of the application-specific functionality that SAX leaves up to you.

MenuItem and CheckboxMenuItems are created by the code shown in Listing 10, and work in a fashion very similar to defineMenu().

104 protected void defineCheckboxMenuItem(AttributeList attrs_) {
105 
106   // Get attributes
107   String sItemName = attrs_.getValue("NAME");
108   String sItemLabel = attrs_.getValue("LABEL");
109 
110   // Create new item
111   CheckboxMenuItem miNew = new CheckboxMenuItem(sItemName);
112 
113   if (sItemName != null) {
114       miNew.setName(sItemName);
115   } else {
116       sItemName = miNew.getName();
117   }
118 
119   // Set menu attributes
120   if (sItemLabel != null) {
121       miNew.setLabel(sItemLabel);
122   } else {
123       miNew.setLabel(sItemName);
124   }
125 
126   // Add menu item to whatever's currently being built
127   add(miNew);
128   miNew.addItemListener(this);
129 }
...
155 protected void defineMenuItem(AttributeList attrs_) {
156 
157   // Get attributes
158   String sItemName = attrs_.getValue("NAME");
159   String sItemLabel = attrs_.getValue("LABEL");
160 
161   // Create new item
162   MenuItem miNew = new MenuItem(sItemName);
163   if (sItemName != null) {
164       miNew.setName(sItemName);
165   } else {
166       sItemName = miNew.getName();
167   }
168 
169   // Set menu attributes
170   if (sItemLabel != null) {
171       miNew.setLabel(sItemLabel);
172   } else {
173       miNew.setLabel(sItemName);
174   }
175 
176   // Add menu item to whatever's currently being built
177   add(miNew);
178   miNew.addActionListener(this);
179 }

Listing 10. defineMenuItem() and defineCheckboxMenuItem()

Both of these methods create an object of the appropriate type (MenuItem or CheckboxMenuItem), set the new object's name and label, and add() the object to whatever is on top of the menu stack (or to the PopupMenu under construction -- MenuItems can't be added to MenuBars). The only real difference between the two is that a MenuItem notifies ActionListeners of user actions, and a CheckboxMenuItem notifies ItemListeners. In either case, the SaxMenuLoader instance itself is listening for the item events, so that it can dispatch them to the appropriate MenuItemHandler when the ActionEvent or ItemEvent occurs.

When the parser successfully completes parsing, the MenuItemHandler stack and the Menu stack will both be empty, and the hash tables will hold all of the the MenuBar and PopupMenu objects, indexed by their names. You can ask for MenuBars and PopupMenus by name, since they're built and waiting to be requested.

Let's now turn our attention to the runtime behavior of these menus.

SaxMenuLoader menus at runtime

I've described how menus are created when the menu is parsed, but how do menus actually appear in an application?

You'll recall that the top-level menu bar comes from the SaxMenuLoader, when you fetch it by name from the SaxMenuLoader (if you don't recall, see Listing 5 and the following discussion).

When the user selects a menu item, all of that menu item's listeners are notified of the selection, right? Well, Listing 10 above shows that it is the SaxMenuLoader itself that is listening for these events. A menu item selected by a user notifies the SaxMenuLoader by calling its actionPerformed() or itemStateChanged() method (depending on whether the item was a regular or checkbox menu item). Listing 11 shows actionPerformed() and itemStateChanged() of SaxMenuLoader.

038 public void actionPerformed(ActionEvent e) {
039     Object oSource = e.getSource();
040     if (oSource instanceof MenuItem) {
041         MenuItem mi = (MenuItem) oSource;
042         MenuItemHandler mih = menuitemhandlerFind(mi);
043         if (mih != null) {
044             mih.itemActivated(mi, e, mi.getActionCommand());
045         }
046     }
047 }
...
203 public void itemStateChanged(ItemEvent e) {
204     Object oSource = e.getSource();
205     if (oSource instanceof MenuItem) {
206         MenuItem mi = (MenuItem) oSource;
207         MenuItemHandler mih = menuitemhandlerFind(mi);
208         if (mih != null) {
209             if (e.getStateChange() == ItemEvent.SELECTED) {
210                 mih.itemSelected(mi, e, mi.getActionCommand());
211             } else {
212                 mih.itemDeselected(mi, e, mi.getActionCommand());
213             }
214         }
215     }
216 }

Listing 11. actionPerformed() and itemStateChanged() receive notification from menu items

actionPerformed() gets the source object that caused the action; if that action was a MenuItem, it looks up that item's handler and calls the handler's itemActivated() method. itemStateChanged() is similar, except that it calls the handler's itemSelected() or itemDeselected() methods, depending on the state change indicated by the ItemEvent passed in.

Notice that in both cases, menuitemHandlerFind() is used to find a handler for the menu item. Remember that you register the menu item handlers with the SaxMenuLoader (Listing 5, lines 098 through 103). But reexamine for a moment Listing 4, lines 038 through 039:

038       <MenuItem NAME="Item 5" COMMAND="Item Five"039                 HANDLER="com.javaworld.feb2000.sax.DynamicMenuItemHandler"/>

Instead of a registered handler name, the value of the attribute HANDLER is a class name. This is how I implemented menu item handlers that are loaded at runtime, so that menus can be extended without recompiling the application.

Dynamic menu item handlers

Just a few lines of code allow a Menu XML file to specify any Java class name as a MenuItemHandler (assuming that the class is accessible and indeed implements that interface). Listing 12 shows how to do this.

340 protected MenuItemHandler menuitemhandlerFind(String sName_) {
341     if (sName_ == null)
342         return null;
343     MenuItemHandler mih = (MenuItemHandler) _htMenuItemHandlers.get(sName_);
344 
345     // Not registered. See if it's a class name, and if it is, create an
346     // instance of that class and register it.
347     if (mih == null) {
348         try {
349             Class classOfHandler = Class.forName(sName_);
350             MenuItemHandler newHandler = (MenuItemHandler)classOfHandler.newInstance();
351             registerMenuItemHandler(sName_, newHandler);
352             mih = newHandler;
353         } catch (Exception ex) {
354             System.err.println("Couldn't find menu item handler '" + sName_ +
355                 ": no such registered handler, and couldn't create");
356             System.err.println(sName_ + ": " + ex.getClass().getName() + ": " + ex.getMessage());
357         }
358     }
359     return mih;

360 } ...

406 protected void pushMenuItemHandler(String sName_) { 407 MenuItemHandler l = menuitemhandlerFind(sName_); 408 if (l == null) 409 l = menuitemhandlerCurrent(); 410 pushMenuItemHandler(l); 411 }

Listing 12. Implementation of dynamic item handlers

Remember that each time SaxMenuLoader encounters a HANDLER attribute, it calls pushMenuItemHandler (see Listing 7). Listing 12 (lines 406 through 411) shows that pushMenuItemHandler(String) uses menuitemhandlerFind(String) to look up the handler by name. menuitemhandlerFind(String) tries to find the item handler in the protected _htMenuItemHandlers hash table. If no such handler is registered, it assumes the name of the handler is a class name. It tries to load the class whose name is the handler name; if it succeeds, it creates an instance of that class. menuitemhandlerFind(String) returns the resulting handler, which was either found in the hash table or loaded on the fly.

The Menu XML package now provides a flexible, easy facility for defining the menus of an application, and extending them without recompiling. I can add items to the menu at will, and define handlers for those new menu items that are dynamically loaded at runtime. Menus are now easy!

Conclusion

SAX is a powerful tool for simple XML processing. With a little headwork, it's easy to create applications that take advantage of XML's extensibility, flexibility, and standardization. In this article, you've seen how SAX works, and have been introduced to a useful example of XML in action.

In the next article in this series, I'll show how to use a validating SAX parser, which detects errors in the input XML by checking its structure against a grammar called a document type definition (DTD). I'll also present a special DocumentHandler class called LAX (the Lazy API for XML), which makes writing document handler classes a piece of cake. Tune in next month!

Mark Johnson works in Ft. Collins, Colo., as a designer and developer for Velocity by day, and as a JavaWorld columnist by night -- very late at night.

Learn more about this topic

  • Download the source files for this article in one of the following formats:

Join the discussion
Be the first to comment on this article. Our Commenting Policies