Adding Common Methods to JAXB-Generated Java Classes (JAXB2 Basics Plugins)

I've used Java Architecture for XML Binding (JAXB) successfully for a wide set of problems and generally really like it. However, it is not without its downsides. One down side that occasionally manifests itself as an issue is the lack of toString(), equals(Object), and hashCode() method implementations in default JAXB-generated objects. In this post, I look at using JAXB2 Basic Plugins (referenced from JAXB Commons) to remedy that.

For this post, I use a very simple example XML Schema Definition that describes XML grammar for storing basic movie information. Although I could generate Java classes from DTD with JAXB, I'm going to use the more typical approach here of using an XSD as the source.

Movie.xsd

<?xml version="1.0"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">

   <xs:element name="Movies">
      <xs:complexType>
         <xs:sequence maxOccurs="unbounded">
            <xs:element name="Movie" type="movieType"/>
         </xs:sequence>
      </xs:complexType>
   </xs:element>

   <xs:complexType name="movieType">
      <xs:attribute name="title" type="xs:string" />
      <xs:attribute name="year" type="xs:long"/>
      <xs:attribute name="genre" type="movieGenre"/>
   </xs:complexType>

   <xs:simpleType name="movieGenre">
      <xs:restriction base="xs:string">
         <xs:enumeration value="Action"/>
         <xs:enumeration value="Animated"/>
         <xs:enumeration value="Comedy"/>
         <xs:enumeration value="Documentary"/>
         <xs:enumeration value="Drama"/>
         <xs:enumeration value="Romance"/>
         <xs:enumeration value="Science Fiction"/>
      </xs:restriction>
   </xs:simpleType> 

</xs:schema>

It is easy to generate Java classes using the xjc binding compiler provided with the Oracle's JDK 7 implementation. This is shown in the next screen snapshot.

The command shown in the above screen snapshot (xjc -d src -p dustin.examples Movie.xsd ) places the generated classes in a destination directory specified by the -d option (src) and generates all classes in a package called dustin.examples (based on -p option).

The next screen snapshot shows that the appropriate classes have been generated (two Java classes representing the contents described by the XSD and an ObjectFactory). The Main.java class is not JAXB-generated and was there before running the xjc compiler.

The generated enum representing movie genre is shown next. It's not an issue that it lacks the common methods because, as an enum, these aren't really necessary to be explicitly coded.

MovieGenre.java

//
// This file was generated by the JavaTM Architecture for XML Binding(JAXB) Reference Implementation, v2.2.4 
// See <a href="http://java.sun.com/xml/jaxb">http://java.sun.com/xml/jaxb</a> 
// Any modifications to this file will be lost upon recompilation of the source schema. 
// Generated on: 2011.08.21 at 11:17:19 PM MDT 
//


package dustin.examples;

import javax.xml.bind.annotation.XmlEnum;
import javax.xml.bind.annotation.XmlEnumValue;
import javax.xml.bind.annotation.XmlType;


/**
 * <p>Java class for movieGenre.
 * 
 * <p>The following schema fragment specifies the expected content contained within this class.
 * <p>
 * <pre>
 * <simpleType name="movieGenre">
 *   <restriction base="{http://www.w3.org/2001/XMLSchema}string">
 *     <enumeration value="Action"/>
 *     <enumeration value="Animated"/>
 *     <enumeration value="Comedy"/>
 *     <enumeration value="Documentary"/>
 *     <enumeration value="Drama"/>
 *     <enumeration value="Romance"/>
 *     <enumeration value="Science Fiction"/>
 *   </restriction>
 * </simpleType>
 * </pre>
 * 
 */
@XmlType(name = "movieGenre")
@XmlEnum
public enum MovieGenre {

    @XmlEnumValue("Action")
    ACTION("Action"),
    @XmlEnumValue("Animated")
    ANIMATED("Animated"),
    @XmlEnumValue("Comedy")
    COMEDY("Comedy"),
    @XmlEnumValue("Documentary")
    DOCUMENTARY("Documentary"),
    @XmlEnumValue("Drama")
    DRAMA("Drama"),
    @XmlEnumValue("Romance")
    ROMANCE("Romance"),
    @XmlEnumValue("Science Fiction")
    SCIENCE_FICTION("Science Fiction");
    private final String value;

    MovieGenre(String v) {
        value = v;
    }

    public String value() {
        return value;
    }

    public static MovieGenre fromValue(String v) {
        for (MovieGenre c: MovieGenre.values()) {
            if (c.value.equals(v)) {
                return c;
            }
        }
        throw new IllegalArgumentException(v);
    }

}

The real issues of not having common methods can arise for the other JAXB-generated class, which is shown next in the Movies and MovieType classes..

Movies.java (default 'xjc' output)

//
// This file was generated by the JavaTM Architecture for XML Binding(JAXB) Reference Implementation, v2.2.4 
// See <a href="http://java.sun.com/xml/jaxb">http://java.sun.com/xml/jaxb</a> 
// Any modifications to this file will be lost upon recompilation of the source schema. 
// Generated on: 2011.08.24 at 09:06:04 PM MDT 
//


package dustin.examples;

import java.util.ArrayList;
import java.util.List;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlType;


/**
 * <p>Java class for anonymous complex type.
 * 
 * <p>The following schema fragment specifies the expected content contained within this class.
 * 
 * <pre>
 * <complexType>
 *   <complexContent>
 *     <restriction base="{http://www.w3.org/2001/XMLSchema}anyType">
 *       <sequence maxOccurs="unbounded">
 *         <element name="Movie" type="{}movieType"/>
 *       </sequence>
 *     </restriction>
 *   </complexContent>
 * </complexType>
 * </pre>
 * 
 * 
 */
@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "", propOrder = {
    "movie"
})
@XmlRootElement(name = "Movies")
public class Movies {

    @XmlElement(name = "Movie", required = true)
    protected List<MovieType> movie;

    /**
     * Gets the value of the movie property.
     * 
     * <p>
     * This accessor method returns a reference to the live list,
     * not a snapshot. Therefore any modification you make to the
     * returned list will be present inside the JAXB object.
     * This is why there is not a <CODE>set</CODE> method for the movie property.
     * 
     * <p>
     * For example, to add a new item, do as follows:
     * <pre>
     *    getMovie().add(newItem);
     * </pre>
     * 
     * 
     * <p>
     * Objects of the following type(s) are allowed in the list
     * {@link MovieType }
     * 
     * 
     */
    public List<MovieType> getMovie() {
        if (movie == null) {
            movie = new ArrayList<MovieType>();
        }
        return this.movie;
    }

}

MovieType.java (default 'xjc' output)

//
//
// This file was generated by the JavaTM Architecture for XML Binding(JAXB) Reference Implementation, v2.2.4 
// See <a href="http://java.sun.com/xml/jaxb">http://java.sun.com/xml/jaxb</a> 
// Any modifications to this file will be lost upon recompilation of the source schema. 
// Generated on: 2011.08.24 at 09:06:04 PM MDT 
//


package dustin.examples;

import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlType;


/**
 * <p>Java class for movieType complex type.
 * 
 * <p>The following schema fragment specifies the expected content contained within this class.
 * 
 * <pre>
 * <complexType name="movieType">
 *   <complexContent>
 *     <restriction base="{http://www.w3.org/2001/XMLSchema}anyType">
 *       <attribute name="title" type="{http://www.w3.org/2001/XMLSchema}string" />
 *       <attribute name="year" type="{http://www.w3.org/2001/XMLSchema}long" />
 *       <attribute name="genre" type="{}movieGenre" />
 *     </restriction>
 *   </complexContent>
 * </complexType>
 * </pre>
 * 
 * 
 */
@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "movieType")
public class MovieType {

    @XmlAttribute(name = "title")
    protected String title;
    @XmlAttribute(name = "year")
    protected Long year;
    @XmlAttribute(name = "genre")
    protected MovieGenre genre;

    /**
     * Gets the value of the title property.
     * 
     * @return
     *     possible object is
     *     {@link String }
     *     
     */
    public String getTitle() {
        return title;
    }

    /**
     * Sets the value of the title property.
     * 
     * @param value
     *     allowed object is
     *     {@link String }
     *     
     */
    public void setTitle(String value) {
        this.title = value;
    }

    /**
     * Gets the value of the year property.
     * 
     * @return
     *     possible object is
     *     {@link Long }
     *     
     */
    public Long getYear() {
        return year;
    }

    /**
     * Sets the value of the year property.
     * 
     * @param value
     *     allowed object is
     *     {@link Long }
     *     
     */
    public void setYear(Long value) {
        this.year = value;
    }

    /**
     * Gets the value of the genre property.
     * 
     * @return
     *     possible object is
     *     {@link MovieGenre }
     *     
     */
    public MovieGenre getGenre() {
        return genre;
    }

    /**
     * Sets the value of the genre property.
     * 
     * @param value
     *     allowed object is
     *     {@link MovieGenre }
     *     
     */
    public void setGenre(MovieGenre value) {
        this.genre = value;
    }

}

Unfortunately, this generated MovieType and Movies classes lack implementations of common methods toString(), equals(Object), and hashCode(). There may be situations in which these methods are desirable. There are several approaches that could be used to deal with this, but the approach that is the focus of the remainder of this post is to use the JAXB2 Basic Plug-ins to instruct the xjc binding compiler to create these methods.

The JAXB2 Basics ZIP file can be downloaded at http://confluence.highsource.org/display/J2B/Downloads. As of this writing clicking on the links to download Release 0.6.1 or Release 0.6.2 lead to 404 errors, but you can download Release 0.6.0 by clicking on its link. The downloaded file is relatively small. JAXB2 Basics is intended to be run with the JAXB 2 Reference Implementation's xjc binding compiler.

The next code listing is for a Java class that will compare JAXB-generated objects based on the same XML file. A natural expectation is that there would be matching objects for matching XML content.

Main.java

package dustin.examples;

import java.io.File;
import javax.xml.bind.Unmarshaller;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;

import static java.lang.System.out;
import static java.lang.System.err;

/**
 * Main class for demonstrating use of common methods on JAXB objects.
 */
public class Main
{
   public Movies getContentsOfMoviesFile(final String xmlFileName)
   {
      Movies movies = null;
      try
      {
         final JAXBContext jc = JAXBContext.newInstance("dustin.examples");
         final Unmarshaller u = jc.createUnmarshaller();
         movies = (Movies) u.unmarshal(new File(xmlFileName));
      }
      catch (JAXBException jaxbEx)
      {
         err.println(jaxbEx.toString());
      }
      catch (ClassCastException castEx)
      {
         err.println("Unable to get Movies object out of file " + xmlFileName +
                 " - " + castEx.toString());
      }
      return movies;
   }

   /**
    * Main function for testing out JAXB-generated objects equality
    * and toString() functionality.
    * 
    * @param arguments Command line arguments; none expected.
    */
   public static void main(String[] arguments)
   {
      final Main me = new Main();
      final Movies movies1 = me.getContentsOfMoviesFile("movies1.xml");
      final Movies movies2 = me.getContentsOfMoviesFile("movies1.xml");
      if (movies1.equals(movies2))
      {
         out.println("YES! Expected same XML to lead to matching objects.");
      }
      else
      {
         out.println("No.  Did not expect same XML file to lead to different instances.");
      }
      for (final MovieType movie : movies1.getMovie())
      {
         boolean matchFound = false;
         for (final MovieType otherMovie : movies2.getMovie())
         {
            if (movie.equals(otherMovie))
            {
               matchFound = true;
               break;
            }
         }
         if (matchFound)
         {
            out.println("Match FOUND for " + movie);
         }
         else
         {
            out.println("NO match found for " + movie);
         }
      }
   }
}
Related:
1 2 3 Page 1
Page 1 of 3