The next code listing shows a JAXB-generated class (see my previous post for the XSD used to generate this class) that has been generated with the default settings of the JAXB 2 RI xjc binding compiler without use of JAXB2 Basic Plugins. In other words, there is no "common" method defined for this class such as
toString()
, hashCode()
, or equals(Object)
.If one does not have access to the source schema or for some reason cannot regenerate the JAXB classes using a framework like JAXB2 Basic Plugins to automatically add these common methods to the generated classes, another approach that can be used is to have a totally distinct class or piece of code perform the same functionality on these classes. The following is a simple Java class that performs equals, hashCode, and toString functionality for a JAXB-generated class that does not have its own overridden implementations of these.
MovieTypeCommons.java
package dustin.examples; import java.util.Objects; /** * <p>Class that compares a JAXB-generated MovieType class to add common methods * {@code toString()}, {@code equals(Object)}, and {@code hashCode()} that a * class normally overrides from {@link java.lang.Object}, but which are not * overridden in the JAXB-generated class.</p> * * <p>This class requires Java 7 for compiling as it uses the new * {@link java.lang.Objects} class.</p> * * <p>This class could be used as-is in Java or Groovy to provide surrogate * "common" methods for instances of {@link dustin.examples.MovieType} or * could be used as a Groovy Category.</p> */ public class MovieTypeCommons { /** * Performs equality comparison on two provided instances of the * {@link dustin.examples.MovieType} class. * * @param movie1 First instance to be compared. * @param movie2 Second instance to be compared. * @return {@code true} if the two provided instances are equal or * {@code false} if the two instances are not equal or if either provided * parameter is null. */ public static boolean doEquals(final MovieType movie1, final MovieType movie2) { if (movie1 == null || movie2 == null) { return false; } if (movie1.equals(movie2)) { return true; } if ( !Objects.equals(movie1.title, movie2.title) || !Objects.equals(movie1.year, movie2.year) || movie1.genre != movie2.genre) { return false; } return true; } /** * Provide hash code for provided instance of {@link dustin.examples.MovieType}. * * @param movie Instance for which hash code is desired. * @return The hash code for the provided object; will be null if provided * MovieType is null. */ public static int doHashCode(final MovieType movie) { return movie != null ? Objects.hash(movie.title, movie.year, movie.genre) : null; } /** * Provide String representation for provided instance of * {@link dustin.examples.MovieType}. * * @param movie Movie Instance for which String representation is desired. * @return String representation of provided instance; will be "null" if * provided MovieType is null. */ public static String doToString(final MovieType movie) { return movie != null ? movie.title + " [" + movie.genre + "/" + movie.year + "]" : "null"; } }
The above code accepts instances of the JAXB-generated class
MovieType
and performs the common functionality on them. It is worth a quick diversion here to point out that handiness of Java 7's sparkling new Objects class in implementing these methods in a null-safe fashion.The next code listing shows how a simple Java client can use this commons-providing class to handle the JAXB-generated
MovieType
class.Main2.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 a * separate and distinct class. */ public class Main2 { 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 Main2 me = new Main2(); final Movies movies1 = me.getContentsOfMoviesFile("movies1.xml"); final Movies movies2 = me.getContentsOfMoviesFile("movies1.xml"); for (final MovieType movie : movies1.getMovie()) { boolean matchFound = false; for (final MovieType otherMovie : movies2.getMovie()) { if (MovieTypeCommons.doEquals(movie, otherMovie)) { matchFound = true; break; } } if (matchFound) { out.println("Match FOUND for " + MovieTypeCommons.doToString(movie)); } else { out.println("NO match found for " + MovieTypeCommons.doToString(movie)); } } } }
When the above class's
main
method is executed, we see both MovieTypeCommons.doEquals(MovieType,MovieType)
and MovieTypeCommons.doToString(MovieType)
in action and working.As the example above indicates, using a separate and distinct class to perform common functionality is a viable solution for working with JAXB-generated classes that don't define these themselves. However, there are some disadvantages. First, the code must be generated separately and cannot be done in a simple step as was done with JAXB2 Basic Plugins. Every time something changes, the multiple steps are required. The second problem is related in that most changes to the JAXB-generated classes require corresponding changes to the separate class that works on their instances. Even with these disadvantages in mind, it still may be a practical solution in some cases.
We can use the class defined above (
MovieTypeCommons
) even easier in Groovy. In fact, in Groovy, its use can be made to look very similar to what the calling code would look like if we were operating on the JAXB-generated classes directly. This can be implemented using Groovy's Objective-C-inspired Category support. As the documentation states, a Groovy category satisfies the "many situations where you might find that it would be useful if a class not under your control had additional methods that you define."Before demonstrating use of Groovy Categories, I first "port" the
Main2
simple Jav client from above to a fairly close Groovy equivalent.compareMovieTypes.groovy
import dustin.examples.MovieType import dustin.examples.MovieTypeCommons import dustin.examples.Movies import javax.xml.bind.Unmarshaller; import javax.xml.bind.JAXBContext; import javax.xml.bind.JAXBException; def movies1 = getContentsOfMoviesFile("movies1.xml") def movies2 = getContentsOfMoviesFile("movies1.xml") movies1.movie.each { movie -> boolean matchFound = false movies2.movie.each { otherMovie -> if (MovieTypeCommons.doEquals(movie, otherMovie)) { matchFound = true } } if (matchFound) { println "Match FOUND for " + MovieTypeCommons.doToString(movie) } else { println "NO match found for " + MovieTypeCommons.doToString(movie) } } def Movies getContentsOfMoviesFile(String xmlFileName) { def jc = JAXBContext.newInstance("dustin.examples") def u = jc.createUnmarshaller() def movies = (Movies) u.unmarshal(new File(xmlFileName)) return movies }
This first Groovy script does NOT use Categories and calls the separate and distinct class
MovieTypeCommons
just as its Java equivalent did with the static methods access. Categories make this code a little cleaner and make it look as if the methods were being called directly on the instances of MovieType
rather than on the separate class. The next Groovy script shows Groovy code using Categories (note the use
keyword) to make it appear as if the methods were being called on the MovieType
instance rather than on the separate MovieTypeCommons
static methods.compareMovieTypes2.groovy
import dustin.examples.MovieType import dustin.examples.MovieTypeCommons import dustin.examples.Movies import javax.xml.bind.Unmarshaller; import javax.xml.bind.JAXBContext; import javax.xml.bind.JAXBException; use (MovieTypeCommons) { def movies1 = getContentsOfMoviesFile("movies1.xml") def movies2 = getContentsOfMoviesFile("movies1.xml") movies1.movie.each { movie -> boolean matchFound = false movies2.movie.each { otherMovie -> if (movie.doEquals(otherMovie)) { matchFound = true } } if (matchFound) { println "Match FOUND for " + movie.doToString() } else { println "NO match found for " + movie.doToString() } } } def Movies getContentsOfMoviesFile(String xmlFileName) { def jc = JAXBContext.newInstance("dustin.examples") def u = jc.createUnmarshaller() def movies = (Movies) u.unmarshal(new File(xmlFileName)) return movies; }
In the last Groovy code listing, the code appears to call
doEquals(MovieType)
and doToString(MovieType>
on the MovieType
instance itself! This looks more like what we'd normally see when a class implements its own "equals" and "toString" methods.Conclusion
This post has shown how a separate and distinct Java class might be used to implement common functionality for JAXB classes that don't have these common methods themselves. Groovy Categories were demonstrated as an easy approach to making it appear as if Groovy scripts were calling the common functionality methods on the instances of the JAXB-generated classes themselves. Along the way, Java 7's Objects class was used to simplify implementation of the common functionality.
No comments:
Post a Comment