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!
- Part 1. Use the Simple API for XML (SAX) to process XML in Java easily
- Part 2. Learn about SAX and XML validation through illustrative examples
- Part 3. DOMination: Take control of structured documents with the Document Object Model
: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.
Item encountered | Parser 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
.)
<a id="Line012" name="Line012">012 /**
</a><a id="Line013" name="Line013">013 * SAX DocumentHandler class that prints the contents of "TITLE" element
</a><a id="Line014" name="Line014">014 * of an input document.
</a><a id="Line015" name="Line015">015 */
</a><a id="Line016" name="Line016">016 public class TitleFinder extends HandlerBase {
</a><a id="Line017" name="Line017">017 boolean _isTitle = false;
</a><a id="Line018" name="Line018">018 public TitleFinder() {
</a><a id="Line019" name="Line019">019 super();
</a><a id="Line020" name="Line020">020 }
</a><a id="Line021" name="Line021">021 /**
</a><a id="Line022" name="Line022">022 * Print any text found inside a <b><TITLE></b> element.
</a><a id="Line023" name="Line023">023 */
</a><a id="Line024" name="Line024">024 public void characters(char[] chars, int iStart, int iLen) {
</a><a id="Line025" name="Line025">025 if (_isTitle) {
</a><a id="Line026" name="Line026">026 String sTitle = new String(chars, iStart, iLen);
</a><a id="Line027" name="Line027">027 System.out.println("Title: " + sTitle);
</a><a id="Line028" name="Line028">028 }
</a><a id="Line029" name="Line029">029 }
</a><a id="Line030" name="Line030">030 /**
</a><a id="Line031" name="Line031">031 * Mark title element end.
</a><a id="Line032" name="Line032">032 */
</a><a id="Line033" name="Line033">033 public void endElement(String element) {
</a><a id="Line034" name="Line034">034 if (element.equals("TITLE")) {
</a><a id="Line035" name="Line035">035 _isTitle = false;
</a><a id="Line036" name="Line036">036 }
</a><a id="Line037" name="Line037">037 }
</a><a id="Line038" name="Line038">038 /**
</a><a id="Line039" name="Line039">039 * Find contents of titles
</a><a id="Line040" name="Line040">040 */
</a><a id="Line041" name="Line041">041 public static void main(String args[]) {
</a><a id="Line042" name="Line042">042 TitleFinder titleFinder = new TitleFinder();
</a><a id="Line043" name="Line043">043 try {
</a><a id="Line044" name="Line044">044 Parser parser = ParserFactory.makeParser("com.ibm.xml.parsers.SAXParser");
</a><a id="Line045" name="Line045">045 parser.setDocumentHandler(titleFinder);
</a><a id="Line046" name="Line046">046 parser.parse(new InputSource(args[0]));
</a><a id="Line047" name="Line047">047 } catch (Exception ex) {
</a><a id="Line048" name="Line048">048 ; // OK, so sometimes laziness *isn't* a virtue.
</a><a id="Line049" name="Line049">049 }
</a><a id="Line050" name="Line050">050 }
</a><a id="Line051" name="Line051">051 /**
</a><a id="Line052" name="Line052">052 * Mark title element start
</a><a id="Line053" name="Line053">053 */
</a><a id="Line054" name="Line054">054 public void startElement(String element, AttributeList attrlist) {
</a><a id="Line055" name="Line055">055 if (element.equals("TITLE")) {
</a><a id="Line056" name="Line056">056 _isTitle = true;
</a><a id="Line057" name="Line057">057 }
</a><a id="Line058" name="Line058">058 }
</a>
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 Menu
s, MenuItem
s, and CheckboxMenuItem
s. 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.
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 ActionEvent
s and ItemEvent
s 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 MenuBar
s and PopupMenu
s 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.)
<a id="Line001" name="Line001">001 <b><?xml</b> version="<i>1.0</i>"<b>?></b>
</a><a id="Line002" name="Line002">002
</a><a id="Line003" name="Line003">003 <b><Menus></b>
</a><a id="Line004" name="Line004">004
</a><a id="Line005" name="Line005">005 <!-- The menu bar at the top of the frame -->
</a><a id="Line006" name="Line006">006 <b><MenuBar</b> NAME="<i>TopMenu</i>"<b>></b>
</a><a id="Line007" name="Line007">007
</a><a id="Line008" name="Line008">008 <b><Menu</b> NAME="<i>File</i>" <b>HANDLER="<i>FileHandler</i>"<b>></b>
</b></a><b><a id="Line009" name="Line009">009 <b><MenuItem</b> NAME="<i>FileOpen</i>" <b>LABEL="<i>Open...</i>"<b>/></b>
</b></a><b><a id="Line010" name="Line010">010 <b><MenuItem</b> NAME="<i>FileSave</i>" <b>LABEL="<i>Save</i>"<b>/></b>
</b></a><b><a id="Line011" name="Line011">011 <b><MenuItem</b> NAME="<i>FileSaveAs</i>" <b>LABEL="<i>Save As...</i>"<b>/></b>
</b></a><b><a id="Line012" name="Line012">012 <b><MenuItem</b> NAME="<i>FileExit</i>" <b>LABEL="<i>Exit</i>"<b>/></b>
</b></a><b><a id="Line013" name="Line013">013 <b></Menu></b>
</a><a id="Line014" name="Line014">014
</a><a id="Line015" name="Line015">015 <b><Menu</b> NAME="<i>Edit</i>" <b>HANDLER="<i>EditHandler</i>"<b>></b>
</b></a><b><a id="Line016" name="Line016">016 <b><MenuItem</b> NAME="<i>EditUndo</i>" <b>LABEL="<i>Undo</i>"<b>/></b>
</b></a><b><a id="Line017" name="Line017">017 <b><MenuItem</b> NAME="<i>EditCut</i>" <b>LABEL="<i>Cut</i>"<b>/></b>
</b></a><b><a id="Line018" name="Line018">018 <b><MenuItem</b> NAME="<i>EditPaste</i>" <b>LABEL="<i>Paste</i>"<b>/></b>
</b></a><b><a id="Line019" name="Line019">019 <b><MenuItem</b> NAME="<i>EditDelete</i>" <b>LABEL="<i>Delete</i>"<b>/></b>
</b></a><b><a id="Line020" name="Line020">020 <b><CheckboxMenuItem</b> NAME="<i>EditReadOnly</i>" <b>LABEL="<i>Disable Button 1</i>"<b/></b></a><b><b><a id="Line021" name="Line021">021 HANDLER="<i>Button1Enabler</i>"<b>/></b>
</a><a id="Line022" name="Line022">022 <b></Menu></b>
</a><a id="Line023" name="Line023">023
</a><a id="Line024" name="Line024">024 <b><Menu</b> NAME="<i>Help</i>" <b>HANDLER="<i>HelpHandler</i>"<b>></b>
</b></a><b><a id="Line025" name="Line025">025 <b><MenuItem</b> NAME="<i>HelpAbout</i>" <b>LABEL="<i>About</i>"<b>/></b>
</b></a><b><a id="Line026" name="Line026">026 <b><MenuItem</b> NAME="<i>HelpTutorial</i>" <b>LABEL="<i>Tutorial</i>"<b>/></b>
</b></a><b><a id="Line027" name="Line027">027 <b></Menu></b>
</a><a id="Line028" name="Line028">028
</a><a id="Line029" name="Line029">029 <b></MenuBar></b>
</a><a id="Line030" name="Line030">030
</a><a id="Line031" name="Line031">031 <b><PopupMenu</b> NAME="<i>Pop1</i>" <b>HANDLER="<i>PopupHandler</i>"<b>></b>
</b></a><b><a id="Line032" name="Line032">032 <b><Menu</b> NAME="<i>Sub Menu 1</i>" <b>HANDLER="<i>SubMenu1Handler</i>"<b>></b>
</b></a><b><a id="Line033" name="Line033">033 <b><MenuItem</b> NAME="<i>Item 1</i>" <b>COMMAND="<i>Item One</i>"<b>/></b>
</b></a><b><a id="Line034" name="Line034">034 <b><MenuItem</b> NAME="<i>Item 2</i>" <b>COMMAND="<i>Item Two</i>"<b>/></b>
</b></a><b><a id="Line035" name="Line035">035 <b></Menu></b>
</a><a id="Line036" name="Line036">036 <b><MenuItem</b> NAME="<i>Item 3</i>" <b>COMMAND="<i>Item Three</i>"<b>/></b>
</b></a><b><a id="Line037" name="Line037">037 <b><MenuItem</b> NAME="<i>Item 4</i>" <b>COMMAND="<i>Item Four</i>"<b>/></b>
</b></a><b><a id="Line038" name="Line038">038 <b><MenuItem</b> NAME="<i>Item 5</i>" <b>COMMAND="<i>Item Five</i>"<b/></b></a><b><b><a id="Line039" name="Line039">039 HANDLER="<i>com.javaworld.feb2000.sax.DynamicMenuItemHandler</i>"<b>/></b>
</a><a id="Line040" name="Line040">040 <b></PopupMenu></b>
</a><a id="Line041" name="Line041">041
</a><a id="Line042" name="Line042">042 <b></Menus></b>
</a></b></b>
</b></b></b></b></b></b></b></b></b></b></b></b></b></b></b></b></b></b></b></b></b>
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 newjava.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 newjava.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 ajava.awt.MenuItem
. The item’s label defaults to its name, but can be set with theLABEL
attribute. The defaultactionCommand
for the item is also the item’s name, but may be set with theCOMMAND
attribute.<CheckboxMenuItem NAME="name" [LABEL="label"] [COMMAND="command"]>
: This tag defines ajava.awt.CheckboxMenuItem
. It’s just like aMenuItem
, 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 MenuItem
s. 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
andPopupMenu
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 fromMenuItem
s defined in Menu XML. (I’ll cover theMenuItemHandler
in more detail shortly.) - Acts as an
ActionListener
andItemListener
for all menu items. - Dispatches
ActionEvent
s andItemEvent
s 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
.)
<a id="Line094" name="Line094">094 public void runDemo(String[] args) {
</a><a id="Line095" name="Line095">095 SaxMenuLoader sml = new SaxMenuLoader();
</a><a id="Line096" name="Line096">096
</a><a id="Line097" name="Line097">097 // Bind names of handlers to the MenuItemHandlers they represent
</a><a id="Line098" name="Line098">098 sml.registerMenuItemHandler("FileHandler", this);
</a><a id="Line099" name="Line099">099 sml.registerMenuItemHandler("EditHandler", this);
</a><a id="Line100" name="Line100">100 sml.registerMenuItemHandler("HelpHandler", this);
</a><a id="Line101" name="Line101">101 sml.registerMenuItemHandler("PopupHandler", this);
</a><a id="Line102" name="Line102">102 sml.registerMenuItemHandler("SubMenu1Handler", this);
</a><a id="Line103" name="Line103">103 sml.registerMenuItemHandler("Button1Enabler", this);
</a><a id="Line104" name="Line104">104
</a><a id="Line105" name="Line105">105 // Parse the file
</a><a id="Line106" name="Line106">106 sml.loadMenus(args[0]);
</a><a id="Line107" name="Line107">107
</a><a id="Line108" name="Line108">108 // If menu load succeeded, show the menu in a frame
</a><a id="Line109" name="Line109">109 MenuBar menubarTop = sml.menubarFind(args[1]);
</a><a id="Line110" name="Line110">110 if (menubarTop != null) {
</a><a id="Line111" name="Line111">111 Frame frame = new Frame("Menu demo 1");
</a><a id="Line112" name="Line112">112 frame.addWindowListener(new WindowAdapter() {
</a><a id="Line113" name="Line113">113 public void windowClosing(WindowEvent e) {
</a><a id="Line114" name="Line114">114 System.exit(0);
</a><a id="Line115" name="Line115">115 }
</a><a id="Line116" name="Line116">116 });
</a><a id="Line117" name="Line117">117 frame.setMenuBar(menubarTop);
</a><a id="Line118" name="Line118">118 _b1 = new Button("Button");
</a><a id="Line119" name="Line119">119 _b1.addMouseListener(new MenuPopper(_b1, sml, "Pop1"));
</a><a id="Line120" name="Line120">120 frame.add(_b1);
</a><a id="Line121" name="Line121">121 frame.pack();
</a><a id="Line122" name="Line122">122 frame.show();
</a><a id="Line123" name="Line123">123 } else {
</a><a id="Line124" name="Line124">124 System.out.println(args[1] + ": no such menu");
</a><a id="Line125" name="Line125">125 }
</a><a id="Line126" name="Line126">126 }
</a>
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.
<a id="Line279" name="Line279">279 public void loadMenus(Reader reader_) {
</a><a id="Line280" name="Line280">280 if (_parser == null)
</a><a id="Line281" name="Line281">281 return;
</a><a id="Line282" name="Line282">282 _parser.setDocumentHandler(this);
</a><a id="Line283" name="Line283">283 try {
</a><a id="Line284" name="Line284">284 _parser.parse(new InputSource(reader_));
</a><a id="Line285" name="Line285">285 } catch (SAXException ex) {
</a><a id="Line286" name="Line286">286 System.out.println("Parse error: " + ex.getMessage());
</a><a id="Line287" name="Line287">287 } catch (Exception ex) {
</a><a id="Line288" name="Line288">288 System.err.println("SaxMenuFactory.loadMenus(): " + ex.getClass().getName() +
</a><a id="Line289" name="Line289">289 ex.getMessage());
</a><a id="Line290" name="Line290">290 ex.printStackTrace();
</a><a id="Line291" name="Line291">291 }
</a><a id="Line292" name="Line292">292 }
</a>
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.
<a id="Line445" name="Line445">445 public void startElement(String sName_, AttributeList attrs_) {
</a><a id="Line446" name="Line446">446
</a><a id="Line447" name="Line447">447 // Anything may override handler for its context
</a><a id="Line448" name="Line448">448 String sHandler = attrs_.getValue("HANDLER");
</a><a id="Line449" name="Line449">449 pushMenuItemHandler(sHandler);
</a><a id="Line450" name="Line450">450
</a><a id="Line451" name="Line451">451 // If "menubar", we're building a MenuBar
</a><a id="Line452" name="Line452">452 if (sName_.equals("MenuBar")) {
</a><a id="Line453" name="Line453">453 defineMenuBar(attrs_);
</a><a id="Line454" name="Line454">454 }
</a><a id="Line455" name="Line455">455
</a><a id="Line456" name="Line456">456 // If "popupMenu", we're building a PopupMenu
</a><a id="Line457" name="Line457">457 else if (sName_.equals("PopupMenu")) {
</a><a id="Line458" name="Line458">458 definePopupMenu(attrs_);
</a><a id="Line459" name="Line459">459 }
</a><a id="Line460" name="Line460">460
</a><a id="Line461" name="Line461">461 // If "menu", then create a menu.
</a><a id="Line462" name="Line462">462 else if (sName_.equals("Menu")) {
</a><a id="Line463" name="Line463">463 defineMenu(attrs_);
</a><a id="Line464" name="Line464">464 }
</a><a id="Line465" name="Line465">465
</a><a id="Line466" name="Line466">466 else if (sName_.equals("MenuItem")) {
</a><a id="Line467" name="Line467">467 defineMenuItem(attrs_);
</a><a id="Line468" name="Line468">468 }
</a><a id="Line469" name="Line469">469
</a><a id="Line470" name="Line470">470 else if (sName_.equals("CheckboxMenuItem")) {
</a><a id="Line471" name="Line471">471 defineCheckboxMenuItem(attrs_);
</a><a id="Line472" name="Line472">472 }
</a><a id="Line473" name="Line473">473 }
</a>
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 CheckboxMenuItem
s 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 — MenuItem
s can’t be added to MenuBar
s). The only real difference between the two is that a MenuItem
notifies ActionListener
s of user actions, and a CheckboxMenuItem
notifies ItemListener
s. 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 MenuBar
s and PopupMenu
s 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
.
<a id="Line038" name="Line038">038 public void actionPerformed(ActionEvent e) {
</a><a id="Line039" name="Line039">039 Object oSource = e.getSource();
</a><a id="Line040" name="Line040">040 if (oSource instanceof MenuItem) {
</a><a id="Line041" name="Line041">041 MenuItem mi = (MenuItem) oSource;
</a><a id="Line042" name="Line042">042 MenuItemHandler mih = menuitemhandlerFind(mi);
</a><a id="Line043" name="Line043">043 if (mih != null) {
</a><a id="Line044" name="Line044">044 mih.itemActivated(mi, e, mi.getActionCommand());
</a><a id="Line045" name="Line045">045 }
</a><a id="Line046" name="Line046">046 }
</a><a id="Line047" name="Line047">047 }
...
</a><a id="Line203" name="Line203">203 public void itemStateChanged(ItemEvent e) {
</a><a id="Line204" name="Line204">204 Object oSource = e.getSource();
</a><a id="Line205" name="Line205">205 if (oSource instanceof MenuItem) {
</a><a id="Line206" name="Line206">206 MenuItem mi = (MenuItem) oSource;
</a><a id="Line207" name="Line207">207 MenuItemHandler mih = menuitemhandlerFind(mi);
</a><a id="Line208" name="Line208">208 if (mih != null) {
</a><a id="Line209" name="Line209">209 if (e.getStateChange() == ItemEvent.SELECTED) {
</a><a id="Line210" name="Line210">210 mih.itemSelected(mi, e, mi.getActionCommand());
</a><a id="Line211" name="Line211">211 } else {
</a><a id="Line212" name="Line212">212 mih.itemDeselected(mi, e, mi.getActionCommand());
</a><a id="Line213" name="Line213">213 }
</a><a id="Line214" name="Line214">214 }
</a><a id="Line215" name="Line215">215 }
</a><a id="Line216" name="Line216">216 }
</a>
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:
<a id="Line038" name="Line038">038 <b><MenuItem</b> NAME="<i>Item 5</i>" <b>COMMAND="<i>Item Five</i>"<b/></b></a><b><b><a id="Line039" name="Line039">039 HANDLER="<i>com.javaworld.feb2000.sax.DynamicMenuItemHandler</i>"<b>/></b>
</a></b>
</b>
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.
<a id="Line340" name="Line340">340 protected MenuItemHandler menuitemhandlerFind(String sName_) {
</a><a id="Line341" name="Line341">341 if (sName_ == null)
</a><a id="Line342" name="Line342">342 return null;
</a><a id="Line343" name="Line343">343 MenuItemHandler mih = (MenuItemHandler) _htMenuItemHandlers.get(sName_);
</a><a id="Line344" name="Line344">344
</a><a id="Line345" name="Line345">345 // Not registered. See if it's a class name, and if it is, create an
</a><a id="Line346" name="Line346">346 // instance of that class and register it.
</a><a id="Line347" name="Line347">347 if (mih == null) {
</a><a id="Line348" name="Line348">348 try {
</a><a id="Line349" name="Line349">349 Class classOfHandler = Class.forName(sName_);
</a><a id="Line350" name="Line350">350 MenuItemHandler newHandler = (MenuItemHandler)classOfHandler.newInstance();
</a><a id="Line351" name="Line351">351 registerMenuItemHandler(sName_, newHandler);
</a><a id="Line352" name="Line352">352 mih = newHandler;
</a><a id="Line353" name="Line353">353 } catch (Exception ex) {
</a><a id="Line354" name="Line354">354 System.err.println("Couldn't find menu item handler '" + sName_ +
</a><a id="Line355" name="Line355">355 ": no such registered handler, and couldn't create");
</a><a id="Line356" name="Line356">356 System.err.println(sName_ + ": " + ex.getClass().getName() + ": " + ex.getMessage());
</a><a id="Line357" name="Line357">357 }
</a><a id="Line358" name="Line358">358 }
</a><a id="Line359" name="Line359">359 return mih;
</a><a id="Line360" name="Line360">360 }
...
</a><a id="Line406" name="Line406">406 protected void pushMenuItemHandler(String sName_) {
</a><a id="Line407" name="Line407">407 MenuItemHandler l = menuitemhandlerFind(sName_);
</a><a id="Line408" name="Line408">408 if (l == null)
</a><a id="Line409" name="Line409">409 l = menuitemhandlerCurrent();
</a><a id="Line410" name="Line410">410 pushMenuItemHandler(l);
</a><a id="Line411" name="Line411">411 }
</a>
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!