Multihandler Adapters


The callback interfaces in SAX are reminiscent of the callback interfaces in the AWT, such as ActionListener and MouseListener . There is, however, one crucial difference between the patterns that the two callback interfaces follow. The AWT allows you to register multiple listener objects with any one component, whereas SAX limits you to just one listener of a certain type per parser. Filters allow you to parse a document through several listeners in turn . However, sometimes it's desirable to install multiple handlers that are not filters.

For example, the DocBook XML source for this book is parsed multiple times to produce HTML, XHTML, PDF, and several other output formats. Each time a new output format is required, the parser must reparse the document. This is really wasted effort because it's the same document. If it's well-formed and valid for HTML output, then it's well- formed and valid for PDF and XHTML output. There's no need to run the checks anew for each separate output document, as long as the input is the same. It makes sense to attach multiple handlers to the parser to avoid wasting time by unnecessarily reparsing the same document.

A properly designed filter can dispatch its events to multiple handlers. The trick is that rather than storing a single ContentHandler / DTDHandler / ErrorHandler the filter stores a list of each. Then each callback method iterates through one of the lists, invoking the method on each of the registered listeners in turn.

When changing the architecture of a class in a subclass as I'm doing here, the naming conventions of the superclass class often don't make perfect sense in the subclass. In this case, it's not clear what the various setFooHandler() methods should do. Do they add a new handler to the list? If so, what happens when null is passed (the idiom for removing a handler from an XMLReader )? Or should I provide new addFooHandler() and removeFooHandler() methods? And which object from the list should getFooHandler() return? There's no obvious answer to these questions. Indeed, I changed my mind several times while designing this class and writing this chapter. Here, I chose to keep the interface as similar to the interface of the superclass as possible. Thus the three setFooHandler() methods add a new handler without replacing the existing handler. But if null is passed, the entire list is cleared. There's no easy way to remove a handler or retrieve or delete a specific one from the list. The getFooHandler() method simply returns the first handler in the list. This is not the most flexible design, but it does keep the interface the same as the interface of the superclass. For example, the following shows how content handlers are stored, added, and removed:

 private List contentHandlers = new ArrayList(2);  public void setContentHandler(ContentHandler handler) {   if (handler == null) {     contentHandlers.clear();   }   contentHandlers.add(handler); } public ContentHandler getContentHandler() {   if (contentHandlers.isEmpty()) return null;   return (ContentHandler) contentHandlers.get(0); } 

Other approaches are certainly workable as well.

However the list is filled, the startElement() method would iterate through the list as follows :

 public void startElement(String namespaceURI, String localName,   String qualifiedName, Attributes atts) throws SAXException {   Iterator handlers = contentHandlers.iterator();   while(handlers.hasNext()) {     ContentHandler handler = (ContentHandler) handlers.next();     handler.startElement(namespaceURI, localName,      qualifiedName, atts);   } } 

The other handlers and methods are implemented similarly. Example 8.15 demonstrates the entire class.

Example 8.15 Attaching Multiple Handlers of the Same Type to a Single Parser
 import org.xml.sax.*; import org.xml.sax.helpers.XMLFilterImpl; import java.util.*; public class MultiHandlerAdapter extends XMLFilterImpl {   public MultiHandlerAdapter(XMLReader parent) {     super(parent);   }   List contentHandlers = new ArrayList(2);   List dtdHandlers = new ArrayList(2);   List errorHandlers = new ArrayList(2);   public void setContentHandler(ContentHandler handler) {     if (handler == null) {       contentHandlers.clear();     }     contentHandlers.add(handler);   }   // There's no good way to handle this within the XMLReader   // interface. I just pick the first in the list.   public ContentHandler getContentHandler() {     if (contentHandlers.isEmpty()) return null;     return (ContentHandler) contentHandlers.get(0);   }   public void setDTDHandler(DTDHandler handler) {     if (handler == null) {       dtdHandlers.clear();     }     dtdHandlers.add(handler);   }   public DTDHandler getDTDHandler() {     if (dtdHandlers.isEmpty()) return null;     return (DTDHandler) dtdHandlers.get(0);   }   public void setErrorHandler(ErrorHandler handler) {     if (handler == null) {       errorHandlers.clear();     }     errorHandlers.add(handler);   }   public ErrorHandler getErrorHandler() {     if (errorHandlers.isEmpty()) return null;     return (ErrorHandler) errorHandlers.get(0);   }   // ContentHandler implementation   public void startElement(String namespaceURI, String localName,    String qualifiedName, Attributes atts) throws SAXException {     Iterator handlers = contentHandlers.iterator();     while(handlers.hasNext()) {       ContentHandler handler = (ContentHandler) handlers.next();       handler.startElement(namespaceURI, localName,        qualifiedName, atts);     }   }   public void endElement(String namespaceURI, String localName,    String qualifiedName) throws SAXException {     Iterator handlers = contentHandlers.iterator();     while(handlers.hasNext()) {       ContentHandler handler = (ContentHandler) handlers.next();       handler.endElement(namespaceURI, localName,        qualifiedName);     }   }   public void characters(char[] text, int start, int length)    throws SAXException {     Iterator handlers = contentHandlers.iterator();     while(handlers.hasNext()) {       ContentHandler handler = (ContentHandler) handlers.next();       handler.characters(text, start, length);     }   }   public void ignorableWhitespace(char[] text, int start,    int length) throws SAXException {     Iterator handlers = contentHandlers.iterator();     while(handlers.hasNext()) {       ContentHandler handler = (ContentHandler) handlers.next();       handler.ignorableWhitespace(text, start, length);     }   }   public void processingInstruction(String target, String data)    throws SAXException {     Iterator handlers = contentHandlers.iterator();     while(handlers.hasNext()) {       ContentHandler handler = (ContentHandler) handlers.next();       handler.processingInstruction(target, data);     }   }   public void skippedEntity(String name)    throws SAXException {     Iterator handlers = contentHandlers.iterator();     while(handlers.hasNext()) {       ContentHandler handler = (ContentHandler) handlers.next();       handler.skippedEntity(name);     }   }   public void startPrefixMapping(String prefix, String uri)    throws SAXException {     Iterator handlers = contentHandlers.iterator();     while(handlers.hasNext()) {       ContentHandler handler = (ContentHandler) handlers.next();       handler.startPrefixMapping(prefix, uri);     }   }   public void endPrefixMapping(String prefix)    throws SAXException {     Iterator handlers = contentHandlers.iterator();     while(handlers.hasNext()) {       ContentHandler handler = (ContentHandler) handlers.next();       handler.endPrefixMapping(prefix);     }   }   public void startDocument() throws SAXException {     Iterator handlers = contentHandlers.iterator();     while(handlers.hasNext()) {       ContentHandler handler = (ContentHandler) handlers.next();       handler.startDocument();     }   }   public void endDocument() throws SAXException {     Iterator handlers = contentHandlers.iterator();     while(handlers.hasNext()) {       ContentHandler handler = (ContentHandler) handlers.next();       handler.endDocument();     }   }   public void setDocumentLocator(Locator locator) {     Iterator handlers = contentHandlers.iterator();     while(handlers.hasNext()) {       ContentHandler handler = (ContentHandler) handlers.next();       handler.setDocumentLocator(locator);     }   }   // DTDHandler implementation   public void notationDecl(String name, String publicID,    String systemID) throws SAXException {     Iterator handlers = dtdHandlers.iterator();     while(handlers.hasNext()) {       DTDHandler handler = (DTDHandler) handlers.next();       handler.notationDecl(name, publicID, systemID);     }   }   public void unparsedEntityDecl(String name, String publicID,    String systemID, String notationName) throws SAXException {     Iterator handlers = dtdHandlers.iterator();     while(handlers.hasNext()) {       DTDHandler handler = (DTDHandler) handlers.next();       handler.unparsedEntityDecl(name, publicID, systemID,        notationName);     }   }   // ErrorHandler implementation   public void warning(SAXParseException exception)    throws SAXException {     Iterator handlers = errorHandlers.iterator();     while(handlers.hasNext()) {       ErrorHandler handler = (ErrorHandler) handlers.next();       handler.warning(exception);     }   }   public void error(SAXParseException exception)    throws SAXException {     Iterator handlers = errorHandlers.iterator();     while(handlers.hasNext()) {       ErrorHandler handler = (ErrorHandler) handlers.next();       handler.error(exception);     }   }   public void fatalError(SAXParseException exception)    throws SAXException {     Iterator handlers = errorHandlers.iterator();     while(handlers.hasNext()) {       ErrorHandler handler = (ErrorHandler) handlers.next();       handler.fatalError(exception);     }   } } 

Note

MultiHandlerAdapter is a little on the long side. It contains a lot of duplicated code. This is definitely a case in which the use of templates could save some space.


MultiHandlerAdapter provides only the three main callback interfaces: ContentHandler , DTDHandler , and ErrorHandler . I chose not to provide EntityResolver because having more than one of those responding to the same request doesn't make sense. Each entity must be provided exactly once, not two, or three, or seventeen times. Potentially, you could register multiple entity resolvers and then iterate through them in sequence until one found the entity you were looking for. But because order would be very significant in that case, a proper API would be much trickier to design.

I also chose not to implement the property-based handlers, LexicalHandler and DeclHandler . First, not all parsers support them. Second, they are not part of the standard XMLFilterImpl class. Most important, adding and removing these objects solely by setting and getting features and properties would be very messy. If you need multiples of one of them, it seems simpler to implement the multi-way dispatching in a LexicalHandler / DeclHandler implementation rather than directly in the filter. A similar scheme could work for ContentHandler , DTDHandler , and ErrorHandler , but here the filter approach seems the most natural.



Processing XML with Java. A Guide to SAX, DOM, JDOM, JAXP, and TrAX
Processing XML with Javaв„ў: A Guide to SAX, DOM, JDOM, JAXP, and TrAX
ISBN: 0201771861
EAN: 2147483647
Year: 2001
Pages: 191

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