prev table of contents next

2.2.12.6 Content: A Mixed List of Elements

To define an element type where the element should have a content consisting of a mixed list of elements use the schema element xsd:choice with the attribute maxOccurs="unbounded" or some value greater than 1. (Using xsd:choice as the sole element within a xsd:sequence would result in the same structure definition and, therefore, in the same Java code.)

<xsd:complexType name="MixType">
  <xsd:choice maxOccurs="unbounded">
    <xsd:element name="Text"   type="xsd:string"/>
    <xsd:element name="Number" type="xsd:int"/>
    <xsd:element name="Point"  type="PointType"/>
  </xsd:choice>
</xsd:complexType>
The generated Java class has an instance variable for a mixed list of such elements. Obviously, the generic parameter of the List object must refer to some superclass of all the element types. Sometimes only java.lang.Object will do. Other possibilities are java.lang.Serializable, or a user-defined type from which all the types in the choice set have been derived by subclassing.
public class MixType {

    protected List<Object> textOrNumberOrPoint;
 
    public List<Object> getTextOrNumberOrPoint() {
        if (textOrNumberOrPoint == null) {
            textOrNumberOrPoint = new ArrayList<Object>();
        }
        return this.textOrNumberOrPoint;
    }
}
Note well that this technique does not retain an indication of the XML tag in the objects created during unmarshalling as long as the types of the choices are distinct. You must distinguish individual elements by testing a list element with the instanceof operator, e.g.:
for( Object o: mix.getTextOrNumberOrPoint() ){
    if( o instanceof String ){
        // ... cast o to String and process
    } else if( o instanceof Integer ){
        // ... cast o to Integer and process
    } else if( o instanceof Point ){
        // ... cast o to Point and process
    } else {
        throw new IllegalArgumentException( "class " + o.getClass() );
    }
}
It's a good idea to use a third test to guard against the class not being one of the three expected ones. For one thing, the instanceof test avoids the compiler warning. Also, even though a failure of the cast would show that something went wrong, we can produce a more significant error message in the exception we throw in the final else branch.

Writing lengthy if statement cascades like this isn't considered to be good object oriented style. The preferred implementation technique is to map element classes to distinct objects created from subclasses of some element handler class hierarchy. Section Using the Element Tree contains a detailed example illustrating this approach.

If we add another element of, say, type xsd:string, the schema definition might then look like this:

<xsd:complexType name="Mix4Type">
  <xsd:choice maxOccurs="unbounded">
    <xsd:element name="Text"   type="xsd:string"/>
    <xsd:element name="Number" type="xsd:int"/>
    <xsd:element name="Point"  type="PointType"/>
    <xsd:element name="Token"  type="xsd:string"/>
  </xsd:choice>
</xsd:complexType>
Now the JAXB compiler is forced to use an artificial construct of type javax.xml.bind.JAXBElement as a container for each of the elements within MixType. The class Mix4Type reflects this by the generic list type being <JAXBElement<?>>. (Notice that the agglomeation of the list field's name stops, mercifully, after the third sub-element.)
public class Mix4Type {
 
    protected List<JAXBElement<?>> textOrNumberOrPoint;

    public List<JAXBElement<?>> getTextOrNumberOrPoint() {
        if (textOrNumberOrPoint == null) {
            textOrNumberOrPoint = new ArrayList<JAXBElement<?>>();
        }
        return this.textOrNumberOrPoint;
    } 
}
Consequently, a pass through the list would have to be changed as shown below, where the tag and the value are retrieved from the container of type JAXBElement<?>.
for( JAXBElement je: mix.getTextOrNumberOrPoint() ){
    String tag = je.getName().getLocalPart();
    if( "Text".equals( Tag ) ){
        String text = (String)je.getValue();
        // ... (process)
    } else if( "Number".equals( Tag ) ){
        Integer number = (Integer)je.getValue();
        // ... (process)
    } else if( //...
        // ... (other alternatives)
    }
}
Again, the cascading if statements aren't exactly the bee's knees. See section Using the Element Tree for a better way of dealing with tags to distinguish between elements.


prev table of contents next