The JAXB-generated
MovieType
class defined in my earlier post does not have the desired common methods. However, the next code listing shows how it can be extended to provide these methods in the child class on behalf of the parent class. This new class contains no attributes of its own but instead bases its common methods on the attributes inherited from its parent class.MovieTypeExtended.java
package dustin.examples; import java.util.Objects; /** * This class extends {@link dustin.examples.MovieType} to add explicitly * overridden <code>toString()</code>, <code>equals(Object)</code>, and * <code>hashCode()</code> methods. */ public class MovieTypeExtended extends MovieType { public MovieTypeExtended(final MovieType parentSource) { this.title = parentSource.title; this.year = parentSource.year; this.genre = parentSource.genre; } @Override public String toString() { return this.title + " [" + this.year + "/" + this.genre + "]"; } @Override public boolean equals(final Object obj) { if (obj == null) { return false; } if (getClass() != obj.getClass()) { return false; } final MovieTypeExtended other = (MovieTypeExtended) obj; return Objects.equals(this.title, other.title) && Objects.equals(this.year, other.year) && Objects.equals(this.genre, other.genre); } @Override public int hashCode() { return Objects.hash(this.title, this.year, this.genre); } }
The above class is fairly simple thanks to use of JDK 7's new Objects class. The client or test code that needs to use the common functionality of
MovieType
can get this behavior from this extended class. Example code is shown next.Main3.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 via * extended classes that provide those common methods for the JAXB-generated * parents. */ public class Main3 { 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; } /** * Simple example of using separate and distinct class to perform common * functionality on JAXB-generated classes that cannot provide these functions * for themselves. * * @param arguments Command line arguments; none expected. */ public static void main(String[] arguments) { final Main3 me = new Main3(); final Movies movies1 = me.getContentsOfMoviesFile("movies1.xml"); final Movies movies2 = me.getContentsOfMoviesFile("movies1.xml"); for (final MovieType movieSrc : movies1.getMovie()) { final MovieTypeExtended movie = new MovieTypeExtended(movieSrc); boolean matchFound = false; for (final MovieType otherMovieSrc : movies2.getMovie()) { final MovieTypeExtended otherMovie = new MovieTypeExtended(otherMovieSrc); if (movie.equals(otherMovie)) { matchFound = true; break; } } if (matchFound) { out.println("Match FOUND for " + movie); } else { out.println("NO match found for " + movie); } } } }
The extended class does not have to be written in Java. The next example is an class that extends
MovieType
and is implemented in Groovy.MovieTypeGroovyExtended.groovy
package dustin.examples /** * Using @Canonical or @EqualsAndHashCode or @ToString does not work here because * attributes of interest are not actually declared at this class's level. */ class MovieTypeGroovyExtended extends MovieType { public MovieTypeGroovyExtended(MovieType parentSource) { this.title = parentSource.title this.year = parentSource.year this.genre = parentSource.genre } @Override public boolean equals(Object object) { if (object == null) { return false } if (this.getClass() != object.getClass()) { return false } def other = (MovieTypeGroovyExtended) object return Objects.equals(this.title, other.title) && Objects.equals(this.year, other.year) && Objects.equals(this.genre, other.genre) } @Override public String toString() { return "${this.title} [${this.year}/${this.genre}]" } }
The above example is fairly similar to the Java version, but with a little more concise Groovy syntax. It would have been really slick to be able to use the Groovy 1.8 provided annotations @ToString, @EqualsAndHashCode, and @Canonical annotations I covered in my posts (here and here), but these won't work here because they work against the attributes of a particular class rather than the attributes of its parent class. They support calling their parent's version of the same methods, but that is not helpful when the parent class doesn't implement those methods.
This is the fourth approach I have shown for dealing with lack of common methods in JAXB-generated classes. This is also my least favorite approach of the four because it relies on implementation inheritance of a concrete class by an otherwise "empty" class solely providing "common" methods. It seems odd to implement an "empty" class to perform functions on its parent class. It is easier to accept it for tests and simple uses, but I prefer the three other approaches (build JAXB classes with common methods via JAXB2 Plugins, use separate class to perform functionality, and Groovy method interception) over this approach for general use. The other approaches all feel and look "closer" to the actual JAXB-generated classes than this one from client code/script perspective.
No comments:
Post a Comment