prev table of contents next

3.2 Using the Element Tree

The javadoc contained within the classes generated from an XML schema documents all the getters for accessing an XML element's attributes and child elements. A good approach is to implement a set of handler classes, one for each schema element type. Its handle method retrieves attributes and child elements, for which it invokes the handle method in turn. This corresponds to a depth-first traversal of the document tree.The example assumes that there is a simple set of schema types:

<xsd:complexType name="PersonType">
  <xsd:sequence>
    <xsd:element name="Name"  type="NameType">
    <xsd:element name="Addr"  type="AddrType" minOccurs="0">
    <xsd:element name="Child" type="ChildType"
                 minOccurs="0" maxOccurs="unbounded">
  </xsd:sequence>
  <xsd:attribute name="resident" type="xsd:boolean"/>
</xsd:complexType>

<xsd:complexType name="ChildType">
  <xsd:complexContent>
    <xsd:extension base="PersonType"/>
  </xsd:complexContent>
</xsd:complexType>
Below is the essential Java code for a handler class hierachy. Note that delegation to some handler for a sub-element or attribute depends on the item having a specific class.
abstract class Handler {
    protected static Map<Class<?>,Handler> ourClass2Conv =
        new HashMap<Class<?>,Handler>();

    static {
        ourClass2Conv.put( PersonType.class, new PersonHandler() );
        ourClass2Conv.put( NameType.class,   new NameHandler() );
        ourClass2Conv.put( AddrType.class,   new AddrHandler() );
        ourClass2Conv.put( ChildType.class,  new ChildHandler() );
        //...
    }

    public abstract void handle( Object o );

    protected void process( Object obj ){
        if( obj != null ){
            Handler h = ourClass2Conv.get( obj.getClass() );
            if( h != null ){
                h.handle( obj );
            }
        }
    }

    protected <T> void processList( List<T> list ){
        for( T obj: list ){
            Handler h = this.getHandler( obj );
            h.process( obj );
        }
    }
}

class PersonHandler extends Handler {
    public void handle( Object o ){
        PersonType p = (PersonType)o;
        process( p.getName() );
        if( p.isResident() ){
            process( p.getAddr() );
        }
        processList( p.getChild );
    }
}

Not all subclasses of Handler will be quite so simple. There is one noteworthy complication that arises if subordinate elements have to be distinguished by their tag. Let's assume a small change in the definition of PersonType.

<xsd:complexType name="PersonType">
  <xsd:sequence>
    <xsd:element name="Name"  type="xsd:string"/>
    <xsd:element name="Addr"  type="xsd:string" minOccurs="0"/>
    <xsd:choice minOccurs="0" maxOccurs="unbounded">
      <xsd:element name="Boy"  type="ChildType"/>
      <xsd:element name="Girl" type="ChildType"/>
    </xsd:choice>
  </xsd:sequence>
  <xsd:attribute name="resident" type="xsd:boolean"/>
</xsd:complexType>
To get at a person's children, we now have (in class PersonType) a method getBoyOrGirl(), that returns a List<JAXBElement<ChildType>>. All we have to do is a slight extension of the generic method processList, to access the JAXBElement object and continue to use its value attribute instead of the object obtained from the list.
protected <T> void processList( List<T> list ){
    for( T obj: list ){
        if( obj instanceof JAXBElement ){
            obj = ((JAXBElement<?>)obj).getValue();
	}
        Handler h = this.getHandler( obj );
        h.process( obj );
    }
}

Finally, if the tag is required for processing as well, the methods process and handle would have to be extended by an additional String parameter. The value is obtained by a call of the JAXBElement method getName(). An additional lookup table mapping tag names to handlers might be required as well. This is best put into the handler class hosting the list. Don't make such a map global, because XML tags need not be unique across the various element types.


prev table of contents next