imaginary family values presents
a blog that reclines to the left
Suppose you’re writing a Java application that reads information from an XML file, using SAX to parse it, and you want to dispatch to different methods in your Java code based on the names of elements the parser is encountering. That is, you have one method that you want to execute when the parser reads a <bird>
tag, another one for when the parser reads a <dog>
tag, and so forth.
The obvious way to do this is to write an implementation of ContentHandler that explicitly tests the tag name and calls the right procedure, like so:
public class MundaneDispatcher implements ContentHandler {
public void startElement
(String uri, String localName, String qName, Attributes atts)
throws SAXException {
if (localName.equals(“bird”)) {
System.out.println(“Open beak”);
} else if (localName.equals(“dog”)) {
System.out.println(“Open jaws”);
} else if (localName.equals(“flower”)) {
System.out.println(“Open petals”);
} else if (localName.equals(“ventureCapitalist”)) {
System.out.println(“Open wallet”);
} else {
System.err.println(“No handler for “ + localName);
} // … other methods … }
We can also solve this problem using Java’s reflection API. You can define a class that looks at its own list of methods to find out which ones can process an XML tag, and whose startElement
method uses the results of that introspection to handle the dispatching. Like so:
public abstract class AbstractElegantDispatcher implements ContentHandler { private Map startElementHandlers; protected AbstractElegantDispatcher() { startElementHandlers = new HashMap(); Method[] methods = getClass().getMethods(); for (int i = 0; i < methods.length; i++) { Method oneMethod = methods[i]; if (oneMethod.getReturnType().equals(Void.TYPE)) { Class[] arguments = oneMethod.getParameterTypes(); if (arguments.length == 1 && arguments0.isAssignableFrom(Attributes.class)) { startElementHandlers.put(oneMethod.getName(), oneMethod); } } } } public void startElement (String uri, String localName, String qName, Attributes atts) throws SAXException { Method thisElementHandler = (Method) startElementHandlers.get(localName); if (thisElementHandler != null) { invokeElementHandler(thisElementHandler, atts); } else { System.err.println(“No handler for “ + localName); } } // … other methods … private void invokeElementHandler (Method handler, Attributes atts) throws SAXException { try { handler.invoke(this, new Object[] { atts }); } catch (IllegalAccessException e) { System.err.println(“Access violation!”); } catch (InvocationTargetException e) { Throwable t = e.getCause(); if (t instanceof RuntimeException) { throw (RuntimeException) t; } else if (t instanceof SAXException) { throw (SAXException) t; } else if (t instanceof Exception) { throw new SAXException((Exception) t); } else { throw new RuntimeException(t); } } } }
With that framework in place, you can define a class like this:
public class AnimalHandler extends AbstractElegantDispatcher { public void bird(Attributes atts) { System.out.println(“Open beak”); } public void dog(Attributes atts) { System.out.println(“Open jaws”); } public void flower(Attributes atts) { System.out.println(“Open petals”); } public void ventureCapitalist(Attributes atts) { System.out.println(“Open wallet”); } }
That’s a hell of a lot of extra code. What do you get for it?
In MundaneDispatcher, the mapping between XML tags and method calls, a very significant aspect of your code’s organization, is buried in the chain of if ... else if ...
blocks. Yes, you can follow a naming convention saying that <bird>
tags always invoke birdHandler()
methods, etc., but what if someone makes a typo and writes birdHadnler()
instead?
In AnimalHandler, the mapping couldn’t be clearer: every method that takes a single Attributes
argument corresponds to an XML opening tag with the same name. If another programmer takes responsibility for the AnimalHandler class, they can make it support more tags by adding more methods. If someone else wants to write another class that processes a different XML-based language in a similar way, they can just make another subclass of AbstractElementDispatcher.
In general, whenever I’m trying to code something, and I find myself bored by the prospect of typing the same damn thing over and over and over again, I look for some kind of higher-level mechanism—in this case, the Java reflection API—to spare myself the tedium. If computers can save accountants the chore of adding up long columns of numbers, and save writers the chore of retyping a heavily edited document from beginning to end, why can’t they save computer programmers some of the chores associated with our job?
(For a self-contained class that demonstrates both of these methods, see here. Since this is all based on stuff I did at work, my employer owns the copyright, but my boss has given me permission to publish the demo code under a BSD license. Share and enjoy!)