Let's start with reading. To read the XML document and create the corresponding object structure, use the builder pattern on top of the XML parser. Figure 1.5 illustrates the generic builder pattern. The various components of the pattern are as follows :
Applying the Builder PatternFigure 1.6 illustrates how to apply the pattern to your object structure. In addition to the Catalog , Product , and Description classes introduced previously, this diagram has the following:
In effect, XMLDirector convert SAX's events into calls to CatalogBuilder . The CatalogBuilder is responsible for creating the various catalog objects. Figure 1.6. Applying the builder pattern.
In practice, CatalogBuilder is implemented in Listing 1.8. It declares one build method for each object in the data structure: buildCatalog() , buildVisualProduct() , and so on. Notice that it does not declare a buildProduct() because Product is an abstract class. It is therefore impossible to instantiate it. Listing 1.8 CatalogBuilder.javapackage com.psol.catalog; public interface CatalogBuilder { public void buildCatalog(); public void buildVisualProduct(String text, String id, boolean checked, String image); public void buildTextualProduct(String text, String id, boolean checked); public void buildDescription(String language, String text); public Catalog getCatalog(); } XMLDirector and DefaultCatalogBuilder are more interesting classes. XMLDirector is demonstrated in Listing 1.9; let's walk through it step by step. First, XMLDirector implements the SAX's DocumentHandler interface, which declares SAX events related to the document: public class XMLDirector implements ContentHandler The constructor accepts an object that implements the CatalogBuilder interface. As XMLDirector progresses through the XML document, it collects information on the various objects and calls the Catalogbuilder to create the Product objects: public XMLDirector(CatalogBuilder builder) { this.builder = builder; } The meat of XMLDirector is in startElement() and endElement() . These two event handlers track where the reader is in the document using the state variable. startElement() also initializes various buffers, depending on the current element. For the <Product> and <Text> elements, it collects the value of their attributes: public void startElement(String namespaceURI, String localName, String tag, Attributes atts) { if(tag.equals("Catalog") && ROOT == state) state = CATALOG; else if(tag.equals("Product") && CATALOG == state) { state = PRODUCT; id = atts.getValue("id"); String st = atts.getValue("checked"); checked = Boolean.valueOf(st).booleanValue(); text = null; image = null; } else if(tag.equals("Text") && PRODUCT == state) { state = PRODUCT_TEXT; buffer = new StringBuffer(); } else if(tag.equals("Image") && PRODUCT == state) { state = IMAGE; buffer = new StringBuffer(); } else if(tag.equals("Descriptions") && PRODUCT == state) state = DESCRIPTIONS; else if(tag.equals("Text") && DESCRIPTIONS == state) { state = DESCRIPTIONS_TEXT; language = atts.getValue("xml:lang"); buffer = new StringBuffer(); } } When an XML element corresponds to a Java object, endElement() calls the builder, passing it the appropriate information. This illustrates how the builder pattern works. The director accumulates just enough information to construct one object and calls the builder to do the actual work: public void endElement(String namespaceURI, String localName, String tag) { if(tag.equals("Catalog") && CATALOG == state) { state = ROOT; builder.buildCatalog(); } else if(tag.equals("Product") && PRODUCT == state) { state = CATALOG; if(null == image) builder.buildTextualProduct(text,id,checked); else builder.buildVisualProduct(text,id,checked,image); } else if(tag.equals("Text") && PRODUCT_TEXT == state) { state = PRODUCT; text = buffer.toString(); } else if(tag.equals("Image") && IMAGE == state) { state = PRODUCT; image = buffer.toString(); } else if(tag.equals("Descriptions") && DESCRIPTIONS == state) state = PRODUCT; else if(tag.equals("Text") && DESCRIPTIONS_TEXT == state) { state = DESCRIPTIONS; builder.buildDescription(language,buffer.toString()); } } Tip XMLDirector does not validate the structure of the XML document ”for example, it does not test whether the attributes or the elements exist. If your applications need to validate the structure of the document, you should consider using a validating parser. As promised , the code for XMLDirector is in Listing 1.9. Listing 1.9 XMLDirector.javapackage com.psol.catalog; import org.xml.sax.*; public class XMLDirector implements DocumentHandler { protected CatalogBuilder builder; protected static final int ROOT = 0, CATALOG = 1, PRODUCT = 2, PRODUCT_TEXT = 3, IMAGE = 4, DESCRIPTIONS = 5, DESCRIPTIONS_TEXT = 6; protected int state; protected StringBuffer buffer; protected String text, id, image, language; protected boolean checked; public XMLDirector(CatalogBuilder builder) { this.builder = builder; } public void setDocumentLocator (Locator locator) { } public void startDocument() { state = ROOT; } public void endDocument() { } public void startElement(String tag,AttributeList atts) { if(tag.equals("Catalog") && ROOT == state) state = CATALOG; else if(tag.equals("Product") && CATALOG == state) { state = PRODUCT; id = atts.getValue("id"); String st = atts.getValue("checked"); checked = Boolean.valueOf(st).booleanValue(); text = null; image = null; } else if(tag.equals("Text") && PRODUCT == state) { state = PRODUCT_TEXT; buffer = new StringBuffer(); } else if(tag.equals("Image") && PRODUCT == state) { state = IMAGE; buffer = new StringBuffer(); } else if(tag.equals("Descriptions") && PRODUCT == state) state = DESCRIPTIONS; else if(tag.equals("Text") && DESCRIPTIONS == state) { state = DESCRIPTIONS_TEXT; language = atts.getValue("xml:lang"); buffer = new StringBuffer(); } } public void endElement(String tag) { if(tag.equals("Catalog") && CATALOG == state) { state = ROOT; builder.buildCatalog(); } else if(tag.equals("Product") && PRODUCT == state) { state = CATALOG; if(null == image) builder.buildTextualProduct(text,id,checked); else builder.buildVisualProduct(text,id,checked,image); } else if(tag.equals("Text") && PRODUCT_TEXT == state) { state = PRODUCT; text = buffer.toString(); } else if(tag.equals("Image") && IMAGE == state) { state = PRODUCT; image = buffer.toString(); } else if(tag.equals("Descriptions") && DESCRIPTIONS == state) state = PRODUCT; else if(tag.equals("Text") && DESCRIPTIONS_TEXT == state) { state = DESCRIPTIONS; builder.buildDescription(language,buffer.toString()); } } public void characters(char ch[],int start,int len) { if(PRODUCT_TEXT == state IMAGE == state DESCRIPTIONS_TEXT == state) buffer.append(ch,start,len); } public void ignorableWhitespace(char ch[], int start, int length) {} public void processingInstruction(String target,String data) {} } The builder pattern cleanly separates the work between the director (responsible for collecting the information from the XML file) and the builder (responsible for creating and maintaining the object structure). Listing 1.10 is DefaultCatalogBuilder . Again, let's first review the salient points. DefaultCatalogBuilder provides storage for the catalog in the making. It stores a list of descriptions because it is being built through calls to buildDescription() . It also stores a list of products because it's being built through calls to buildTextualProduct() and buildVisualProduct() : protected Catalog catalog = null; protected Vector products = new Vector(), descriptions = new Vector(); buildCatalog() is a very simple method. It simply creates a catalog object: public void buildCatalog() { catalog = new Catalog(products); } buildVisualProduct() creates new product objects and stores them in the products vector. buildTextualProduct() and buildDescription() are very similar: public void buildVisualProduct(String text, String id, boolean checked, String image) { Product product = new VisualProduct(text, id, checked, image); products.addElement(product); } As promised, the code for DefaultCatalogBuilder is in Listing 1.10. Listing 1.10 DefaultCatalogBuilder.javapackage com.psol.catalog; import java.util.Vector; public class DefaultCatalogBuilder implements CatalogBuilder { protected Catalog catalog = null; protected Vector products = new Vector(), descriptions = new Vector(); public void buildCatalog() { catalog = new Catalog(products); } public void buildVisualProduct(String text, String id, boolean checked, String image) { Product product = new VisualProduct(text, id, checked, image); products.addElement(product); } public void buildTextualProduct(String text, String id, boolean checked) { Product product = new TextualProduct(text, id, checked, descriptions); products.addElement(product); descriptions = new Vector(); } public void buildDescription(String language, String text) { Description description = new Description(language,text); descriptions.addElement(description); } public Catalog getCatalog() { return catalog; } } To start the pattern, it suffices to create an XMLDirector and register it, as a DocumentHandler , with a SAX parser: XMLReader xmlReader = XMLReaderFactory.createXMLReader(PARSER_NAME); CatalogBuilder builder = new DefaultCatalogBuilder(); xmlReader.setContentHandler(new XMLDirector(builder)); xmlReader.parse("catalog.xml"); Catalog catalog = builder.getCatalog(); |