Meeting the Builder Pattern

   

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 :

  • A builder interface and one (or more) concrete builders, which create the object structure.

  • A director that interacts with the builder to create the object structure. The director is driven by the parser.

  • Product , which is a placeholder in the pattern for the object structure being created.

    Figure 1.5. The builder pattern.

    graphics/01fig05.gif

Applying the Builder Pattern

Figure 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:

  • A CatalogBuilder interface that defines methods to create the various objects in the structure (such as buildCatalog() , buildVisualProduct() , and so on)

  • A DefaultCatalogBuilder class that implements the CatalogBuilder interface

  • An XMLDirector class that drives the CatalogBuilder and is the class that implements the SAX-defined DocumentHandler interface

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.

graphics/01fig06.gif

A Simple API for XML

SAX stands for the Simple API for XML. It declares an interface to an XML parser, and in this respect, SAX is similar to W3C's Document Object Model (DOM). However, the way SAX works is different the way DOM does. An XML parser is a library to read XML documents. The parser enforces the XML syntax, decodes the elements, resolves the entities, and more. In a nutshell , it takes care of low-level work for the programmer.

SAX is an event-based interface. Similar to AWT, your application must register for events of interest. However, unlike AWT, SAX events are not related to buttons and menus . SAX events relate to the XML document instead. Events exists for the beginning and the end of the document, for the beginning and the end of an element, for character data, for processing instructions, and more (see Figure 1.7).

Figure 1.7. A SAX parser generates events as it reads the XML document.

graphics/01fig07.gif

Because it is event based, SAX does not explicitly build the document tree in memory. It is therefore more efficient than DOM and, in particular, it can process documents larger than the available memory.

You can learn more about SAX at http://www.meggison.com/SAX.

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.java
 package 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.java
 package 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.java
 package 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(); 
   


Applied XML Solutions
Applied XML Solutions
ISBN: 0672320541
EAN: 2147483647
Year: 1999
Pages: 142

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net