Wednesday, August 24, 2011

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);
         }
      }
   }
}

This simple code demonstrates the presence or lack of an overridden equals(Object) method and lack of an overridden toString() method. In this case, using the JAXB RI 2 xjc binding compiler, we see that the lack of these methods leads to surprising results as shown in the next screen snapshot.


The above output demonstrates that even when default xjc-generated JAXB objects are populated with the very same XML source file, they are not considered equal and don't override toString(). This is, of course, because they lack their own overridden versions of these most important methods.

There are numerous ways to handle this if it is necessary to print the contents of a JAXB object instantiated from a default generated class without the common methods. One approach would be to have a third-party class perform equality checks on the two provided objects and provide methods for building String representations of these objects. However, the approach that I focus on for the remainder of this post is that of using the JAXB2 Basic Plugins for equals, hashCode, and toString to have these common methods automatically generated and included in the JAXB/xjc-generated classes when they are constructed.

I think using Ant is the easiest way to run xjc with the JAXB2 Basic plugins. An Ant build file made especially for this purpose is shown next.

xjc-build.xml
<?xml version="1.0" encoding="UTF-8"?>
<project name="RunningXjc" default="generate-sources" basedir=".">
   <description>Runs Xjc Binding Compiler</description>

   <target name="generate-sources">
      <taskdef name="xjc" classname="org.jvnet.jaxb2_commons.xjc.XJC2Task">
		   <classpath>
            <fileset dir="C:\Users\Dustin\Downloads\jaxb-ri-20110601\lib">
               <include name="activation.jar"/>
               <include name="jaxb-api.jar"/>
				   <include name="jaxb-impl.jar"/>
		 		   <include name="jsr173_1.0_api.jar"/>
               <include name="stax-api-*.jar"/>
               <include name="jaxb-xjc.jar"/>
			   </fileset>
            <fileset dir="C:\jaxb2-basics-dist-0.6.0\dist">
               <include name="jaxb2-basics-ant-*.jar"/>
            </fileset>
		   </classpath>
      </taskdef>

	<!-- Generate the Java code for XSD -->
	<xjc destdir="${basedir}/src" extension="true" package="dustin.examples">
		<arg line="-Xequals -XhashCode -XtoString"/>
		<schema dir="${basedir}">
		 	<include name="Movie.xsd"/>
		</schema>
		<!-- JAXB2 Plugins and Dependencies -->
		<classpath>
			<fileset dir="C:\jaxb2-basics-dist-0.6.0\dist">
				<include name="jaxb2-basics-0.6.0.jar"/>
            <include name="jaxb2-basics-ant-0.6.0.jar"/>
				<include name="jaxb2-basics-runtime-0.6.0.jar"/>
				<include name="jaxb2-basics-tools-0.6.0.jar"/>
            <include name="jaxb2-basics-testing-0.6.0.jar"/>
            <include name="jaxb2-basics-annotate-0.6.0.jar"/>
			</fileset>
         <fileset dir="C:\jaxb2-basics-dist-0.6.0\jaxb2-basics-dist-0.6.0\lib">
				<include name="commons-beanutils-1.7.0.jar"/>
				<include name="commons-lang-2.2.jar"/>
				<include name="commons-logging-1.1.1.jar"/>
            <include name="annox-0.5.0.jar"/>
         </fileset>
		</classpath>
	</xjc>
</target>
</project>

When the above Ant target is executed, the xjc binding compiler is again run (via the Ant xjc task), but this time uses the three specified plugins to generate the equals, hashCode, and toString methods. The output of this is shown in the next screen snapshot.


The files are all generated again, but they are larger this time because of the common methods and support methods added for those. These generated classes are shown in their directory in the next screen snapshot.


The enum MovieGenre is no different with the plugins, which is fine because enums have reasonable toString methods and their identity and equality comparisons are always the same given the same classloader. The two classes, however, are significantly different with toString(), equals(Object), and hashCode() methods and several supporting methods added to them. I list the generated source code for these two classes, Movies and MovieType, next.

Movies.java (xjc with JAXB2 Plugins for 'common' methods)
//
// 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:23:08 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;
import org.jvnet.jaxb2_commons.lang.Equals;
import org.jvnet.jaxb2_commons.lang.EqualsStrategy;
import org.jvnet.jaxb2_commons.lang.HashCode;
import org.jvnet.jaxb2_commons.lang.HashCodeStrategy;
import org.jvnet.jaxb2_commons.lang.JAXBEqualsStrategy;
import org.jvnet.jaxb2_commons.lang.JAXBHashCodeStrategy;
import org.jvnet.jaxb2_commons.lang.JAXBToStringStrategy;
import org.jvnet.jaxb2_commons.lang.ToString;
import org.jvnet.jaxb2_commons.lang.ToStringStrategy;
import org.jvnet.jaxb2_commons.locator.ObjectLocator;
import org.jvnet.jaxb2_commons.locator.util.LocatorUtils;


/**
 * <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
    implements Equals, HashCode, ToString
{

    @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;
    }

    public boolean equals(ObjectLocator thisLocator, ObjectLocator thatLocator, Object object, EqualsStrategy strategy) {
        if (!(object instanceof Movies)) {
            return false;
        }
        if (this == object) {
            return true;
        }
        final Movies that = ((Movies) object);
        {
            List<MovieType> lhsMovie;
            lhsMovie = this.getMovie();
            List<MovieType> rhsMovie;
            rhsMovie = that.getMovie();
            if (!strategy.equals(LocatorUtils.property(thisLocator, "movie", lhsMovie), LocatorUtils.property(thatLocator, "movie", rhsMovie), lhsMovie, rhsMovie)) {
                return false;
            }
        }
        return true;
    }

    public boolean equals(Object object) {
        final EqualsStrategy strategy = JAXBEqualsStrategy.INSTANCE;
        return equals(null, null, object, strategy);
    }

    public int hashCode(ObjectLocator locator, HashCodeStrategy strategy) {
        int currentHashCode = 1;
        {
            List<MovieType> theMovie;
            theMovie = this.getMovie();
            currentHashCode = strategy.hashCode(LocatorUtils.property(locator, "movie", theMovie), currentHashCode, theMovie);
        }
        return currentHashCode;
    }

    public int hashCode() {
        final HashCodeStrategy strategy = JAXBHashCodeStrategy.INSTANCE;
        return this.hashCode(null, strategy);
    }

    public String toString() {
        final ToStringStrategy strategy = JAXBToStringStrategy.INSTANCE;
        final StringBuilder buffer = new StringBuilder();
        append(null, buffer, strategy);
        return buffer.toString();
    }

    public StringBuilder append(ObjectLocator locator, StringBuilder buffer, ToStringStrategy strategy) {
        strategy.appendStart(locator, this, buffer);
        appendFields(locator, buffer, strategy);
        strategy.appendEnd(locator, this, buffer);
        return buffer;
    }

    public StringBuilder appendFields(ObjectLocator locator, StringBuilder buffer, ToStringStrategy strategy) {
        {
            List<MovieType> theMovie;
            theMovie = this.getMovie();
            strategy.appendField(locator, this, "movie", buffer, theMovie);
        }
        return buffer;
    }

}

MovieType.java (xjc with JAXB2 Plugins for 'common' methods)
//
// 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:23:08 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;
import org.jvnet.jaxb2_commons.lang.Equals;
import org.jvnet.jaxb2_commons.lang.EqualsStrategy;
import org.jvnet.jaxb2_commons.lang.HashCode;
import org.jvnet.jaxb2_commons.lang.HashCodeStrategy;
import org.jvnet.jaxb2_commons.lang.JAXBEqualsStrategy;
import org.jvnet.jaxb2_commons.lang.JAXBHashCodeStrategy;
import org.jvnet.jaxb2_commons.lang.JAXBToStringStrategy;
import org.jvnet.jaxb2_commons.lang.ToString;
import org.jvnet.jaxb2_commons.lang.ToStringStrategy;
import org.jvnet.jaxb2_commons.locator.ObjectLocator;
import org.jvnet.jaxb2_commons.locator.util.LocatorUtils;


/**
 * <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
    implements Equals, HashCode, ToString
{

    @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;
    }

    public boolean equals(ObjectLocator thisLocator, ObjectLocator thatLocator, Object object, EqualsStrategy strategy) {
        if (!(object instanceof MovieType)) {
            return false;
        }
        if (this == object) {
            return true;
        }
        final MovieType that = ((MovieType) object);
        {
            String lhsTitle;
            lhsTitle = this.getTitle();
            String rhsTitle;
            rhsTitle = that.getTitle();
            if (!strategy.equals(LocatorUtils.property(thisLocator, "title", lhsTitle), LocatorUtils.property(thatLocator, "title", rhsTitle), lhsTitle, rhsTitle)) {
                return false;
            }
        }
        {
            Long lhsYear;
            lhsYear = this.getYear();
            Long rhsYear;
            rhsYear = that.getYear();
            if (!strategy.equals(LocatorUtils.property(thisLocator, "year", lhsYear), LocatorUtils.property(thatLocator, "year", rhsYear), lhsYear, rhsYear)) {
                return false;
            }
        }
        {
            MovieGenre lhsGenre;
            lhsGenre = this.getGenre();
            MovieGenre rhsGenre;
            rhsGenre = that.getGenre();
            if (!strategy.equals(LocatorUtils.property(thisLocator, "genre", lhsGenre), LocatorUtils.property(thatLocator, "genre", rhsGenre), lhsGenre, rhsGenre)) {
                return false;
            }
        }
        return true;
    }

    public boolean equals(Object object) {
        final EqualsStrategy strategy = JAXBEqualsStrategy.INSTANCE;
        return equals(null, null, object, strategy);
    }

    public int hashCode(ObjectLocator locator, HashCodeStrategy strategy) {
        int currentHashCode = 1;
        {
            String theTitle;
            theTitle = this.getTitle();
            currentHashCode = strategy.hashCode(LocatorUtils.property(locator, "title", theTitle), currentHashCode, theTitle);
        }
        {
            Long theYear;
            theYear = this.getYear();
            currentHashCode = strategy.hashCode(LocatorUtils.property(locator, "year", theYear), currentHashCode, theYear);
        }
        {
            MovieGenre theGenre;
            theGenre = this.getGenre();
            currentHashCode = strategy.hashCode(LocatorUtils.property(locator, "genre", theGenre), currentHashCode, theGenre);
        }
        return currentHashCode;
    }

    public int hashCode() {
        final HashCodeStrategy strategy = JAXBHashCodeStrategy.INSTANCE;
        return this.hashCode(null, strategy);
    }

    public String toString() {
        final ToStringStrategy strategy = JAXBToStringStrategy.INSTANCE;
        final StringBuilder buffer = new StringBuilder();
        append(null, buffer, strategy);
        return buffer.toString();
    }

    public StringBuilder append(ObjectLocator locator, StringBuilder buffer, ToStringStrategy strategy) {
        strategy.appendStart(locator, this, buffer);
        appendFields(locator, buffer, strategy);
        strategy.appendEnd(locator, this, buffer);
        return buffer;
    }

    public StringBuilder appendFields(ObjectLocator locator, StringBuilder buffer, ToStringStrategy strategy) {
        {
            String theTitle;
            theTitle = this.getTitle();
            strategy.appendField(locator, this, "title", buffer, theTitle);
        }
        {
            Long theYear;
            theYear = this.getYear();
            strategy.appendField(locator, this, "year", buffer, theYear);
        }
        {
            MovieGenre theGenre;
            theGenre = this.getGenre();
            strategy.appendField(locator, this, "genre", buffer, theGenre);
        }
        return buffer;
    }

}

When the simple test is executed against these new generated Java classes, the results are more satisfying.


Using the JAXB2 plugins did what we needed and the JAXB-generated objects with same content are now properly considered equal and there is a better-than-nothing (which is what we had before) String representation of the objects' contents. There is some "cost" to this, however. As the generated code above shows, the JAXB-generated classes must implement specific interfaces and implement significant support methods above and beyond the common methods themselves.

This post has only shown three of the JAXB2 Plugins that are available (some of the others require an extra step of specifying binding customization within the source XSD or within a binding file). Several more are available, but it is especially common to want implementations of toString and equals and hashCode and this post has shown how to get those with JAXB-generated classes using JAXB2 Plugins.

1 comment:

Dustin said...

An interesting related blog post is A JAXB (or rather XJC) odyssey, which discusses the JAXB 2 Basic Plugins briefly.