
Implementation[ Abstract ] [ The build cycle of JAK ] [ XJC Plugins or the "Implementation of the Java API for KML" ] [ Create missing Icon class ] [ Add missing @XmlRootElement annotations ] [ Convert boxed primitives to primitive types ] [ Create Coordinate class ] [ Create equals() and hashCode() methods ] [ Create marshal and unmarshal methods ] [ Create the fluent and convenient side of the Java API for KML ] [ Create KMLFactory (and delete JAXB's ObjectFactory) ] [ @Obvious ] [ Documenting the Java API for KML ]
Abstract
JAK is automatically generated by OGC's KML schema specification file ogckml22.xsd [KML22] and Google's Gx extensions file kml22gx.xsd [KML22GX]. Every time the KML schema evolves, an build cycle is initiated and JAK reflects all changes automatically. This chapter is about the implementation details, why things were done as they are, and how a JAXB plugin does all the automatic API creation. The build cycle of JAKTo initiate the JAK's automatic build process, an Ant build target is defined. This build target configures JAXB's build options and invokes the XJC schema compiler.
Listing 1: Part of Maven's pom.xml script that invokes JAXB's schema compiler and runs a Perl script via Maven's ant-run-plugin. The Perl script cleans all occurrences of the JAXBElement. <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-antrun-plugin</artifactId> <executions> <execution> <id>xjc-invocation</id> <phase>generate-sources</phase> <configuration> <tasks> <property name="src.dir" location="src/main" /> <property name="src.dir.gen" location="${src.dir}/java" /> <property name="schema.dir" value="${src.dir}/resources/schema" /> <property name="schema.dir.kml" location="${schema.dir}/ogckml/ogckml22.xsd" /> <property name="schema.dir.kml.binding" value="${schema.dir}/ogckml/JAK_binding.xjb" /> <taskdef name="xjc" classname="com.sun.tools.xjc.XJCTask" classpathref="maven.compile.classpath" /> <description>generate JAK from OGC's KML schema</description> <mkdir dir="${src.dir.gen}/de/micromata/opengis/kml/" /> <delete> <fileset dir="${src.dir.gen}/de/micromata/opengis/kml/" includes="**" /> </delete> <xjc extension="true" binding="${schema.dir.kml.binding}" destdir="${src.dir.gen}" removeOldOutput="yes" schema="${schema.dir}/ogckml/kml22gx.xsd"> <arg value="-XJavaForKmlApi" /> <!-- the XJC plugins that creates JAK --> </xjc> <exec executable="perl" dir="${basedir}"> <!-- remove the damn JAXBElements --> <arg file="scripts/CleanUpGeneratedJAXBSourceFolder.pl" /> <arg file="${src.dir.gen}/de/micromata/opengis/kml" /> </exec> </tasks> </configuration> <goals> <goal>run</goal> </goals> </execution> </executions> </plugin> With the invocation of the build target shown, Listing 1 in the build cycle is started (a graphical presentation of the build cycle can be found in Figure 2). It can be called with the command mvn install. This invokes JAXB's schema compiler (XJC) and generates the Java API for KML. JAXB binds Google's Gx extensions schema kml22gx.xsd to automatically generated classes, which are annotated with an implicit schema definition by the schema compiler. In Listing 1 only the kml22gx.xsd is mentioned. Due to an internal link defined in Google's Gx extensions schema OGC's KML schema is implicitly considered by the build process. As to be expected, JAXB's schema compiler is not able to determine the desired result of the generated Java code from the XML schema by itself. The creation process of Java's data-model has to be tweaked with JAXB's binding customizations, as many name clash errors occur. The binding customizations needed for compiling both KML schemas can be found in the file JAK_binding.xjb. The XJC plugin used is explained below at JAK's XJC Plugins. A Perl script using regular expression is invoked (seen in Figure 2 and in Listing 1) after the POJOs are generated as Java source files. It removes all JAXBElement occurrences in the generated files. XJC creates 132 Java source files from the KML schema with 632 fields encapsulated into JAXBElement. A complete reference of which elements are affected can be found at Figure 1. It is similar to the one shown as KML in the Java world#Figure 1. It shows the KML reference of all types defined in the KML schema which need to be mapped to a Java class. The difference is that the types that could not be resolved correctly are marked in amber. Thus, each time they are referred to in a POJO as a field, they are encapsulated into a JAXBElement. XJC creates 132 classes from the KML schema. With the default binding mode as defined in the JAXB's specification, 632 fields are encapsulated into JAXBElements; even with the simpler and better binding mode, XJC is not able to resolve all types correctly and encapsulates 635 fields into JAXBElements (three more!). Figure 1: Elements/classes marked in amber are not correctly resolved during the code generation process and, as a result, are encapsulated into JAXBElements.Maven automatically compiles the generated POJOS and tests them using predefined use-cases. The use-cases are composed of JUnit and XMLUnit defined in the ATC and the KML reference. The ATC defines 77 use-cases considered to make an OGC conform KML implementation [OGC07D], whereas 73 examples were taken from the KML reference and implemented as use-case [KML09]. These points were modeled as JUnit tests. They serve to check that the API fulfills all requirements. The next three listings show such a use-case. It is an example taken from [KML09] and shows the creation of a Placemark element that contains a Point element: Listing 2: Example taken from KML09. It shows the creation of a Placemark element containing a Point element. <Placemark> <name>Google Earth - New Placemark</name> <description>Some Descriptive text.</description> <LookAt> <longitude>-90.86879847669974</longitude><latitude>48.25330383601299</latitude> <range>440.8</range><tilt>8.3</tilt><heading>2.7</heading> </LookAt> <Point> <coordinates>-90.86948943473118,48.25450093195546,0</coordinates> </Point> </Placemark> All examples taken from the KML reference were defined as shown in Listing 2. The use-cases, labeled as ATC (Abstract Test Case) defined in the ATS are in a more abstract form:
Table 1: Exemplary the 67th test defined in the ATS.These use-cases are now transformed into Java programs as shown next: Listing 3: The KML document shown in Listing 2 is done programmatically with JAK. The Check.placemarkExample() method is called twice. Placemark placemark = new Placemark() .withName("Google Earth - New Placemark") .withDescription("Some Descriptive text."); placemark.createAndSetLookAt() .withLongitude(-90.86879847669974).withLatitude(48.25330383601299) .withRange(440.8).withTilt(8.3).withHeading(2.7); placemark.createAndSetPoint() .addToCoordinates("-90.86948943473118,48.25450093195546,0"); Check.placemarkExample(placemark); Placemark marshalledAndBackAgain = TestUtil.marshalAndUnmarshall(placemark); Check.placemarkExample(marshalledAndBackAgain); The code of Listing 3 should be self-explanatory as its structure is similar to the listings in Usage and KML in the Java world. As before, 1) it creates a Java object model (line 01 to 10), which 2) is marshalled and unmarshalled again with the marshalAndUnmarshall method (line 13). This method is a help-method which does the same as KML in the Java world#Listing 6. Before the data structure is marshalled into a file, a method with several JUnit asserts is invoked. They ensure that the actual result matches the expected results as defined in the use-case. Listing 4: This method defines JUnit asserts for every element in Listing 2. public static void placemarkExample(final Placemark placemark) { Assert.assertEquals("Google Earth - New Placemark", placemark.getName()); Assert.assertEquals("Some Descriptive text.", placemark.getDescription()); LookAt lookat = (LookAt) placemark.getAbstractView(); Assert.assertEquals(-90.86879847669974, lookat.getLongitude(), 0.0001); Assert.assertEquals(48.25330383601299, lookat.getLatitude(), 0.0001); Assert.assertEquals(440.8, lookat.getRange(), 0.0001); Assert.assertEquals(8.3, lookat.getTilt(), 0.0001); Assert.assertEquals(2.7, lookat.getHeading(), 0.0001); Assert.assertEquals(new Coordinate(-90.86948943473118, 48.25450093195546, 0.0), ((Point) placemark.getGeometry()).getCoordinates().get(0)); } If Listing 4 results in a green-colored bar, the data structure is finally marshalled into a file. This file is then marshalled back into an object graph and the object returned is checked again by the Check.placemarkExample() method. In each build iteration cycle three things were constantly checked:
If all tests pass with a green-colored bar and no syntax errors prevent Maven from compiling the generated source files, the result of a build cycle is the Java API for KML. If not, the code generating XJC plugins are modified and the build cycle is iterated again until the desired result is created and all requirements are fulfilled. Figure 2 is a graphical representation of this build cycle described above: Figure 2: The code generation process of JAK. This is a graphical representation of the processes triggered by Listing 1.XJC Plugins or the "Implementation of the Java API for KML"JAXB's plugin mechanism allows accessing the semantic information of the XML schema during the code creation process to determine what kind of classes and properties are going to be generated. Thus, it opens up the possibility of generating the semantic application layer rather than coding it by hand. The Java API for KML is not artificially divided into two layers anymore. They are merged together. As a result, the Generation Gap mentioned by Fowler disappears completely. This procedure has the advantage that each plugin only influences a small part of the API, and is build incrementally in small steps. As mentioned at KML in the Java world the KML standard seems to be stable, but if the KML schema evolves and a new version of the standard is defined, only the schema defining XML files need to be exchanged and a proper semantic model of the KML API is automatically build upon, which reflects all changes. This makes it possible to adapt to changes quickly and in a fully automatic. This chapter acts as a plugin catalog, similar to Gamma's Pattern catalog [GAM95]. All XJC plugins are described in a common format. Each XJC plugin justifies itself why it was created, followed by a short description about its purpose, and how it influences the created code. In most cases, a short of the default code generated followed by a short example the improved code is given. The plugins are released under a BSD license and can be found at: http://code.google.com/p/xjcpluginjavaapiforkml/ Figure 3: Screenshot of the XJC plugins creating the Java API for KML hosted at Google Code.Alternatively in the official dev.java.net Maven 2 repository: Listing 5: JAK at maven2-repository.dev.java.net <dependencies> ... <!-- The XJC Plugin --> <!-- It is able to create the API (only needed if OGC's KML schema changes) --> <dependency> <groupId>de.micromata.jak</groupId> <artifactId>XJCPluginJavaApiforKml</artifactId> <version>1.0.1-SNAPSHOT</version> </dependency> ... </dependencies> <repositories> ... <repository> <id>maven2-repository.dev.java.net</id> <name>Java.net Maven 2 Repository</name> <url>http://download.java.net/maven/2</url> <layout>default</layout> <snapshots> <enabled>true</enabled> </snapshots> </repository> </repositories> Create missing Icon classWHY:The schema compiler does not create the Icon class. It is created with a XJC plugin. DESCRIPTION:OGC's KML standard defines an Icon element [KML09]. According to the standard, the Icon element has the same child elements as the Link element. As the Link element, it defines a required href child element, too.
A missing Icon class result in compile errors and the tests will not even be able to run, nor can be passed with a green-colored bar. Add missing @XmlRootElement annotationsWHY:JAXB's schema compiler is sometimes not able to resolve all elements as desired, hence they are not annotated with the @XmlRootElement annotation and encapsulated into a JAXBElement. These elements are difficult to be used and their KML representation is not as desired. This XJC plugin adds the missing @XmlRootElement annotation. The JAXBElements are removed with a Perl script. DESCRIPTION:Every top level class or enum type that is annotated with the @XmlRootElement annotation can be represented as a KML element in an KML document. Listing 6: Shows the faulty KML representation of an Icon element with missing a @XmlRootElement annotation. <Icon xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="LinkType"> <href>...</href> </Icon> This plugin adds all missing @XmlRootElement annotations to all classes that cannot be resolved correctly by the schema compiler and as a result are encapsulated into a JAXBElement (all amber colored elements in Figure). Exemplary for the Icon class: Listing 7: The @XmlRootElement annotation added to the Icon class. @XmlRootElement(name = "Icon", namespace = "http://www.opengis.net/kml/2.2") public class Icon ... Every top level class or enum type that is annotated with the @XmlRootElement annotation can be represented as KML element in an KML document. The Icon element is now represented as desired and correct in the resulting KML document: Listing 8: Shows the correct KML representation of an Icon element with a @XmlRootElement annotation. <Icon> <href>...</href> </Icon> Convert boxed primitives to primitive typesWHY:JAXB uses boxed primitives wherever possible. This plugin converts them back to primitive types only, to prevent autoboxing. Description:Java has a two-part type system:
Primitives cannot be put into collections. They had to be put in an appropriate wrapper class (which is Integer in the case of int). Before Java 1.5 the conversion between primitive and boxed primitives had to be done by hand, e.g. in the case of Integer unboxed with the intValue() method. The autoboxing and auto-unboxing feature in the Java 1.5 release automates this process. In his book Effective Java [BLO08], Bloch gives at Item 49, the advice to prefer primitives types to boxed primitives types wherever possible. He states that primitive types are simpler to use and faster than boxed types. The code JAXB's schema compiler generates uses boxed primitives by default: Listing 9: The Coordinate class with boxed primitives. public class Coordinate { private Double longitude = 0.0D; private Double latitude = 0.0D; private Double altitude = 0.0D; ... This plugin changes all plugins to primitive types again: Listing 10: The Coordinate class with primitives. public class Coordinate { private double longitude = 0.0D; private double latitude = 0.0D; private double altitude = 0.0D; ... Create Coordinate classWHY:OGC's KML schema specification defines coordinates as a list of strings. A coordinate consists of the three values longitude, latitude, and altitude. This plugin changes the String representation of coordinates to double. They reflect more the real intent of these values. This makes arithmetic operation easier and faster compared to using a String representation. The conversion from String to double is done transparently to the user by JAK. DESCRIPTION:This plugin creates a Coordinate class. It was first introduced in KML in the Java world. The plugin changes the internal coordinate representation from String coordinates to List<Coordinate> coordinates and tells JAXB to use a custom XML adapter. The adapter is able to unmarshal the coordinates from String to List<Coordinate> and to marshal them from List<Coordinate> to String. Figure 4: The coordinate converter. Clients of the API are able to perform arithmetic operation direct with longitude, latitude, and altitude contained in each coordinate and do not have to parse them from their former String representation to double values. This all happens automatically by JAK. Create equals() and hashCode() methodsWHY:The schema compiler does not create equals() and hashCode() methods. They are both needed to compare the semantic comparability between object instances. Hence, they are created with this XJC plugin. DESCRIPTION:In his book Effective Java [BLO08], Bloch gives at Item 9 and Item 10 the advice to provide always correct implementations of equals() and hashCode(). This XJC plugin generates this method pair to each generate class. They are implemented as recommended by Bloch in [BLO08]. Hence, all object instances can be compared to each other. These methods are implicitly needed, e.g. to compare two different coordinate object instances by JUnit's assert method: Listing 11: assertEquals will result in green-colored bar if the equals() and hashCode() method are created, otherwise the test will fail. Coordinate coord1 = new Coordinate("9.444652669565212,51.30473589438118,0"); Coordinate coord2 = new Coordinate("9.444652669565212,51.30473589438118,0"); Assert.assertEquals(coord1, coord2); Create marshal and unmarshal methodsWHY:This plugin creates marshal and unmarshal methods for convenience to the Java API for KML. These methods save a lot of boilerplate code the clients would have to implement on their own otherwise and should satisfy most user requirements. As result, JAXB is hidden from the user, the API is clean, and the entry level for new users is lowered. DESCRIPTION:The marshal and unmarshal methods described here, were previously introduced in KML in the Java world#Figure6. It is still possible to use JAXB's own methods to marshal and unmarshal if needed (or forced).
Additionally, the Kml class offers five unmarshal methods:
Create the fluent and convenient side of the Java API for KMLWHY:The default code created by JAXB seems a bit bumpy and verbose. Additionally it is made easy to forget registering a child element to its parent element. This plugin creates a fluent interface and some convenience methods for the API. DESCRIPTION:The fluent interface and the associated convenience methods (createAndSet, createAndAdd, addTo, ...) created by this plugin, were described in detailed in KML in the Java world. They allow method chaining and create a less verbose, more readable, and, as the name implies, a flowing programming style. Listing 12: The creation of a Placemark element with a Point programmatically with JAK. Kml kml = KmlFactory.createKml(); Placemark placemark = KmlFactory.createPlacemark(); placemark.setName("Java User Group Hessen - JUGH!"); placemark.setVisibility(true); placemark.setOpen(false); placemark.setDescription("die Java User Group Hessen"); placemark.setStyleUrl("styles.kml#jugh_style"); Point point = KmlFactory.createPoint(); point.setExtrude(false); point.setAltitudeMode(AltitudeMode.CLAMP_TO_GROUND); point.getCoordinates().add(new Coordinate("9.444652669565212,51.30473589438118,0")); placemark.setGeometry(point); // <-- point is registered at placemark ownership kml.setFeature(placemark); // <-- placemark is registered at kml ownership. kml.marshal(System.out); // <-- Print the KML structure to the console. The same example with fluent style and convenient methods: Listing 13: Utilizes the fluent and convenience methods provided by JAK. Compared to [Listing 7 Kml kml = KmlFactory.createKml(); kml.createAndSetPlacemark() .withName("Java User Group Hessen - JUGH!") .withVisibility(true) .withOpen(false) .withDescription("die Java User Group Hessen") .withStyleUrl("styles.kml#jugh_style") .createAndSetPoint() .withExtrude(false) .withAltitudeMode(AltitudeMode.CLAMP_TO_GROUND) .addToCoordinates("9.444652669565212,51.30473589438118,0"); kml.marshal(System.out); The fluent and convenience methods peacefully coexist among the other methods created in the POJOs and can be used interchangeable. Create KMLFactory (and delete JAXB's ObjectFactory)WHY:JAXB creates an ObjectFactory in each created package. JAXB's ObjectFactory does not offer any static methods and is the source of many name clash errors during code generation. This plugin deletes JAXB's ObjectFactory and creates a new one, called KmlFactory, with only static factory methods. DESCRIPTION:JAXB creates a separate ObjectFactory file for each package. In the case of JAK, it creates four files. The ObjectFactory is created for historically reasons [KOH05C] and to simplify the creation of objects the schema compiler was not able to resolve correctly and which are encapsulated within JAXBElements. Listing 14: Playlist element returned by JAXB's ObjectFactory. The playlist element is created an immediately returned to the overloaded createPlaylist method, which encapsulates the playlist object into a JAXBElement. de.micromata.opengis.kml.v_2_2_0.gx.ObjectFactory ob
= new de.micromata.opengis.kml.v_2_2_0.gx.ObjectFactory();
JAXBElement<Playlist> p = ob.createPlaylist(ob.createPlaylist());
In his book Effective Java [BLO08], at Item 1 Bloch gives the advice to consider static factory methods instead of constructors. The XJC plugins and the Perl script used to create the Java API for KML remove all JAXBElement occurrences. This makes JAXB's object factories obsolete. They are all deleted and replaced by a single factory class, called KmlFactory. The KmlFactory described here, was previously introduced at KML in the Java world. It assembles static creation methods for all elements defined by OGC's KML standard, Google's GX extensions, xAL.xsd and atom-author-link.xsd. Elements defined in an other package than KML's default package, have its package name put between 'create' an the elements name. Listing 15: A static creation method of the KmlFactory. No object instance of the factory needs to be created and because all JAXBElements were removed at the build process no overloaded create method are needed to encapsulate elements into JAXBElements. Playlist p = KmlFactory.createGxPlaylist(); It is still possible to create object instances with the keyword new, e.g. new Placemark(). @ObviousWHY:Adam Bien writes in his blog and teaches at his workshops that most of the Javadoc comments in projects are worthless [BIEN07]. Existing information from the method's signature is replicated to the Javadoc. This plugin removes all redundant information and annotates the getters and setters with the @Obvious annotation. DESCRIPTION:JAXB's schema compiler generates very verbose and redundant Javadoc comments for each method. Bien states that obvious facts should not be described repeatedly. That it is more important to pick up the 'why' from the obvious 'noise'. This kind of documentation is unnecessary and injures the DRY principle, since every Java developers knows what getters and setters are. Listing 16: Existing information from the method's signature is replicated to the Javadoc. /** * Gets the value of the name property. * * @return * possible object is * {@link String} * */ public String getName() { return name; } /** * Sets the value of the name property. * * @param value * allowed object is * {@link String} * */ public void setName(String value) { this.name = value; } Bien suggests introducing a new Javadoc-tag or annotation to mark such use cases. For example: @Obvious. This plugin removes unnecessary and redundant Javadoc generated by XJC's default implementation as seen in Listing 16 and replaces it through the @Obvious annotation: Listing 17: Same as Listing 16, but only documented with the @Obvious annotation. @Obvious public String getName() { return name; } @Obvious public void setName(String value) { this.name = value; } Documenting the Java API for KMLWHY:Besides the high-level documentation of an API, all exposed API elements need to be documented. This plugin creates the low-level documentation for the Java API for KML. DESCRIPTION:Jeff Atwood states, "If It Isn't Documented, It Doesn't Exist" [COD07] and Adam Bien states that most of the documentation hurts the DRY principle, since it only replicates existing information from the method's signature in the Javadoc [BIEN07]. A difficult and time-consuming task is, to keep the code synchronized with the documentation. The KML reference [KML22] offers a very rich and detailed documentation to every element defined by OGC's KML standard and Google's GX extensions: Figure 5: The Placemark element documented in the KML reference. The Java API for KML is a direct representation of the KML specification. Everything that can be expressed in KML can be expressed with the Java API for KML, too. Why should not JAK's documentation be a direct representation of the KML reference? This plugin parses the KML reference and transform it into Javadoc comments. These comments are automatically attached to all elements generated during the code generation phase. This avoids the previously mentioned difficulties to keep the documentation synchronized with the code, as the documentation is able to build itself. Clients of the API are able to see, the purpose of each element. Further, how and why it is used: Figure 6: Tooltip text with the automatic generated documentation, exemplary for Placemark. Bibliography
|
Java API for KML | @Google
| |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||













is the OpenSource platform of 