ONJava.com -- The Independent Source for Enterprise Java
oreilly.comSafari Books Online.Conferences.

advertisement

AddThis Social Bookmark Button

Developing with JAXB and Ant, Part 1
Pages: 1, 2

Using JAXB

Next, we add the target to use JAXB to generate the .java files from the DTD and XJS files. The classname was determined by examing the XJC shell script that comes JAXB. This Ant buildfile will actually be more portable than the example used with the distribution, as that file doesn't include a .bat script for Windows systems.



<target name="jaxb" depends="init">
<java fork="yes" 
      classname="com.sun.tools.xjc.Main"
      dir="." classpathref="jaxb.class.path">
      <arg value="datadefs/checkbook.dtd"/>
      <arg value="datadefs/checkbook.xjs"/>
      <arg value="-d"/>
      <arg value="gensrc"/>
</java>
</target>

In order for this to work, some properties need to be set up:

<!-- define the home for the JAXB distribution -->
<property name="JAXBHOME"
  value="C:\javalibs\jaxb-1.0-ea"/>
<path id="jaxb.class.path">
  <fileset dir="${JAXBHOME}/lib">
  <include name="**.jar"/></fileset>
</path>
<path id="build.class.path">
  <fileset dir="${JAXBHOME}/lib">
  <include name="jaxb-rt-1.0-ea.jar"/></fileset>
</path>
<path id="run.class.path">
  <fileset dir="${JAXBHOME}/lib">
  <include name="jaxb-rt-1.0-ea.jar"/></fileset>
  <pathelement path="build"/>
</path>

The JAXB classpath needs to include all of the .jar files, since it calls the compiler as well as the runtime. The build.class.path only needs the runtime .jar file, but the run.class.path, which needs to find the .class files of the application to run its tests, needs to also include the build directory. These can, of course, be modified to add COTS and other .jar files needed for the application. JAXB includes a pre-Crimson XML parser in its runtime, so having an XML parser in the classpath is not required. In future releases, it should be expected that JAXB will likely depend on JAXP as a separate download, or JAXB will be included in future releases of the XMLPack.

While the -d and gensrc arguments specify that the gensrc directory will be the root directory of the generation, they do not specifically specify that the generated files will belong to a specific package. This has to be done by editing the checkbook.xjs file and adding a line to the definition, right below the XML-java-binding-schema tag:

<options package="checkbook"/>

The generated files will be put into the directory gensrc/checkbook, and, fortunately, the JAXB system will automatically create the subdirectories as needed, so that the user doesn't have to add them to the mkdir set in the init target.

Related Reading

Java and XML
Solutions to Real-World Problems
By Brett McLaughlin

The three .java files in the src directory ("the developer's files") need to be modified by adding import checkbook.*; to each, so that they can find the data sources in their new package.

Next, the .java files, both the developer's and the generated ones, need to be compiled. This requires using the src child element of the javac task, rather than the srcdir attribute. Separating the task of compiling into two separate tasks or targets doesn't work: the two are interdependent -- compiling the src directory needs the generated java sources, but compiling the generated sources needs the converters in the src directory. It may be possible to separate the task into three compilations (the converters, then the generated files, then finally the application files that use the generated data), but that seems overkill. Fortunately, Ant provides a way out: you can have multiple src child elements to the javac task, and Ant considers them all equal.

<target name="build" depends="jaxb">
  <javac destdir="build" includes="**/*.java"
    includeAntRuntime="false">
    <src path="src"/>
    <src path="gensrc"/>
    <classpath refid="build.class.path"/>
  </javac>
</target>

I choose to turn the Ant runtime off, in case I have different implementations of some of the Ant classes (such as XML parsers) and also to keep Ant from leaking in my default CLASSPATH, which may point to working versions of the current application and keep the system from compiling against the correct versions of COTS or other external .jar files.

It would also be possible to specify the two (or more) source directories in the srcdir attribute (src="src:gensrc") by separating them with colons, but one of the points of XML, and by extention, Ant's build.xml files, is to make human-readable build files that are computer-processable, and let Ant deal with the complex syntax involved in sending multiple parameters to the javac command line instead of the user. So my general recommendation is to use child attributes or separate path specifications as much as possible over putting every specification in the task's attribute lists. The reader may also note the specification of .java files in the includes attribute; if you leave this off, Ant may attempt to compile all files in the src directories, causing unnecessary failures.

If you attempt to compile now, you'll run into a failure that unfortunately can't be worked around in an ideal way. The TransDate class "can't be found" by the generated .java files. This seems odd, since you know TransDate.java is right there in src, so you might be concerned that the multiple-src feature of Ant isn't working (or something to that effect). In fact, it's a side effect of Java's package feature. When a class is declared to be in a package, it cannot reference a class in the "root" package (i.e., a class not in a package) without a specific import statement to that class (and import *; doesn't work). JAXB gives no means of adding that kind of import to the generated file, and it's rather unefficient (and tricky) to try to come up with some way of adding that line to each generated .java file.

So the alternative, and in fact the better design, is to move the TransDate class into its own package, checkbook.convertors. While we're at it, we'll go ahead and move the other two Checkbook files to the package checkbook.demos (remembering that we need to both move the file and add the package declaration).

Since TransDate is now in another package, we have to go back and edit the checkbook.xjs file again, changing

<conversion name="TransDate" 
  type="java.util.Date"
  parse="TransDate.parseDate"
  print="TransDate.printDate"/>

to

<conversion name="TransDate"
  type="java.util.Date"
  parse="checkbook.convertors.TransDate.parseDate"
  print="checkbook.convertors.TransDate.printDate"/>

Finally, this change requires editing CheckbookApp.java, which references TransDate, to add the import checkbook.convertors.*; line.

Now, all the files should compile, and we can add a target to run the demo, with a simple Java task.

<target name="test" depends="build">
<java fork="yes"
  classname="checkbook.demos.CheckbookApp"
  dir="." classpathref="run.class.path">
</java>
</target>

Note how this is using the run.class.path, which makes sure that the built .class files are included in the .classpath. I almost always use fork in my Java tasks, in case of accidental uses of System.exit().

At this point, the task is done, with the possible exception of adding targets to make distribution .jar files or the Javadocs. The former is trivial, but making Javadocs when the sources are coming from more than one directory is less so, since this uses a different (though more standard) syntax than the src elements of javac. (I expect that in Ant 2.0, javac's syntax will be modified to match and bring all tasks into conformity.) The Javadoc task uses Ant's path syntax, the same that was used in specifying the classpath references above.

<path id="src.path.list">
<pathelement path="src"/>
<pathelement path="gensrc"/>
</path>

With that in place, we can reference it with the Javadoc task:

<target name="javadoc" depends="build">
<javadoc sourcepathref="src.path.list"
  packagenames="checkbook,checkbook.convertors,checkbook.demos"
  windowtitle="Checkbook Example API Documentation"
  destdir="docs">
</javadoc>
</target>

It would be useful, in a larger environment, to specify the package names by some other means than listing them there (or as a property), or in an external text file, but Ant doesn't currently provide that feature. When running this target, a large number of warnings will show up, because Javadoc can't find the JAXB runtime sources or Javadocs. These can be ignored, or you can add a link attribute to your own Web page that has a copy of the files. Javasoft.com does not publish the Javadocs themselves so, unlike with the JDK, you can't link to a copy there. This may change when the package moves out of early access.

In the next article of this series, we will look at ways of embedding JAXB into the build process more deeply by installing it into the Ant lib and writing new Ant tasks and mappers to keep Ant from regenerating the .java files on every run. Ant provides default mappers to specify one-to-one and many-to-one relationships between source and target files, but does not address the issue of one-to-many relationships that JAXB uses. That article may also address the idea of making a task that creates the packagelist file automatically by analyzing the directories under the source tree.

Resources

Joseph Shelby is Software Engineer, ISX Corporation, Arlington, VA.


Return to ONJava.com.