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.