9.10 Creating an Ant XDoclet Task
9.10.1 Problem
You want to create an Ant XDoclet task
and subtask to generate a new type of file.
9.10.2 Solution
Extend xdoclet.DocletTask to create the main task,
if necessary, and extend xdoclet.TemplateSubTask
to create one or more subtasks.
9.10.3 Discussion
This recipe is the first part of a five-part recipe. It describes the
steps needed to create a new Ant XDoclet task and subtask. We are
going to create tasks for the
JUnitPerfDoclet code generator.
|
JUnitPerfDoclet does not need a main XDoclet task. Specifically, we
do not need to extend xdoclet.DocletTask because
no extra functionality needs to be added. All of the functionality
needed by JUnitPerfDoclet lies in the subtask. For completeness, this
recipe shows how to create a Doclet task.
|
|
9.10.3.1 Creating an Ant XDoclet task
The first step is to create the main Ant XDoclet task. This task is
much like the ejbdoclet task, serving as an entry
point into the XDoclet engine.
Here are the steps needed to create an Ant XDoclet task:
Create a new Java source file and give it a name. For this example,
create a YourNewDocletTask.java file and add it
to your project.
Add the following import: import xdoclet.DocletTask;
Extend the DocletTask class: public class YourNewDocletTask extends DocletTask {
Add the necessary public getter and setter methods for attributes
your task defines. For example, if your new task defines an attribute
called validId, then you would have this setter
method: public void setValidId(String id) {
this.id = id;
}
Optionally, you may override the validateOptions(
) method if you need to validate your task. Typically, you override this method to ensure that the user has set
the proper attributes. Here's an example:
protected void validateOptions( ) throws BuildException {
super.validateOptions( );
if (this.id == null || "".equals(this.id)) {
throw new BuildException("You must specify a valid 'id' attribute.");
}
} You should call super.validateOptions( ) to allow
the base class a chance to perform validation, too. If any error
occurs an org.apache.tools.ant.BuildException
should be thrown.
Another interesting feature of XDoclet is the
checkClass(String class) method. This method tries
to load a class specified by the given class
parameter using Class.forName( ), and if the class
is not on the classpath then a nice error message is printed to the
console. The classpath is the one defined for the Ant task
definition. This is a major improvement over earlier versions of
XDoclet, where you were left sifting through Java reflection errors.
Here is an example:
protected void validateOptions( ) throws BuildException {
super.validateOptions( );
if (this.id == null || "".equals(this.id)) {
throw new BuildException("You must specify a valid 'id' attribute.");
}
checkClass("com.oreilly.javaxp.xdoclet.SomeClassThatYouWant");
}
9.10.3.2 Creating the Ant Doclet subtask
Now let's delve into creating the JUnitPerfDoclet
subtask,
JUnitPerfDocletSubTask. This subtask is responsible for the
code generation process.
Create a new Java source file called
JUnitPerfDocletSubTask.java and add it to your
project.
Add the following imports: import xdoclet.TemplateSubTask;
import xdoclet.XDocletException;
Extend the xdoclet.TemplateSubTask class: public class JUnitPerfDocletSubTask extends TemplateSubTask { The TemplateSubTask provides the hooks necessary
for XDoclet to locate a template file and start the code generation
process.
Set up a few constants: public static final String DEFAULT_TEMPLATE =
"/com/oreilly/javaxp/xdoclet/perf/junitperf.j";
private static final String DEFAULT_JUNIT_PERF_PATTERN =
"TestPerf{0}.java"; It is a good idea to set up constants that define default values the
subtask needs. The constants defined above are values specifying the
JUnitPerfDoclet template file and the pattern used for the classname
and filename of the generated code.
Add a default constructor: public JUnitPerfDocletSubTask( ) {
setDestinationFile(DEFAULT_JUNIT_PERF_PATTERN);
setTemplateURL(getClass( ).getResource(DEFAULT_TEMPLATE));
} A constructor should set up any default values. The only two
attributes needed for our subtask are the destination file and
template file. The TemplateSubTask class defines
both of these attributes.
The destination file attribute specifies the name of the generated
file. If this attribute contains the substring
"{0}" then XDoclet generates a new
file for each source file processed. If the substring
"{0}" is omitted, only one output
file is generated for all source files processed.
Let's look at an example.
If the value of the destination file attribute is
TestPerf{0}.java and the current class being
processed is Customer, then XDoclet generates a
new file named TestPerfCustomer.java. The name
of the current class being processed is substituted in place of the
substring "{0}". If you are
familiar with the java.text package, you may have
guessed that XDoclet uses the
java.text.MessageFormat class to achieve the
substitution. The next recipe shows how to use this technique.
The template file attribute specifies where to locate the
.xdt file. JUnitPerfDoclet, by default, loads
the junitperf.xdt template file from the
classpath.
Override the validateOptions( ) method to validate
one or more attributes: public void validateOptions( ) throws XDocletException {
super.validateOptions( );
if (getDestinationFile( ).indexOf("{0}") == -1) {
throw new XDocletException(
"The '" + getSubTaskName( ) +
"' Ant Doclet Subtask attribute 'destinationFile' " +
"must contain the substring '{0}', which serves as a " +
"place holder JUnit Test name.");
}
}
} Here the validateOptions( ) method is overridden
to ensure that the
"destinationFile" attribute
contains the substring "{0}". An
XDocletException is thrown with a friendly message
if the "destinationFile" attribute
does not contain the "{0}"
substring. The subtask validationOptions( ) method
throws an XDocletException not a
BuildException. This allows the main task to
handle all XDocletExceptions before halting the
process.
It is important to call super.validateOptions( ).
It ensures that the base class gets a chance to perform validation it
requires.
The last method to implement is a convenience method for the
JUnitPerf tag handler class (this class is written in the next
recipe): public String getJUnitPerfPattern( ) {
return getDestinationFile( ).
substring(0, getDestinationFile( ).indexOf(".java"));
} This method strips off the file extension, and it is used by the
JUnitPerfTagHandler.className( ) method. The next
recipe examines why this is important.
Example 9-11 shows the
complete example.
Example 9-11. JUnitPerfDocletSubTask
package com.oreilly.javaxp.xdoclet.perf;
import xdoclet.TemplateSubTask;
import xdoclet.XDocletException;
public class JUnitPerfDocletSubTask extends TemplateSubTask {
public static final String DEFAULT_TEMPLATE =
"/com/oreilly/javaxp/xdoclet/perf/junitperf.j";
public static final String DEFAULT_JUNIT_PERF_PATTERN =
"TestPerf{0}.java";
public JUnitPerfDocletSubTask( ) {
setDestinationFile(DEFAULT_JUNIT_PERF_PATTERN);
setTemplateURL(getClass( ).getResource(DEFAULT_TEMPLATE));
}
/**
* Used by {@link JUnitPerfTagHandler} to generate the new class name.
* Before returning the '.java' extension is stripped off.
*
* @return the JUnitPerf file pattern with the '.java' extension removed.
*/
public String getJUnitPerfPattern( ) {
return getDestinationFile( ).
substring(0, getDestinationFile( ).indexOf(".java"));
}
/**
* Overridden to validate the 'destinationFile' attribute. This attribute
* must include a '{0}', which serves as a place holder for the JUnit
* test name.
*/
public void validateOptions( ) throws XDocletException {
super.validateOptions( );
if (getDestinationFile( ).indexOf("{0}") == -1) {
throw new XDocletException(
"The '" + getSubTaskName( ) +
"' Ant Doclet Subtask attribute 'destinationFile' " +
"must contain the substring '{0}', which serves as a " +
"place holder JUnit test name.");
}
}
}
9.10.4 See Also
Recipe 9.11 shows how to create the JUnitPerfDoclet
tag handler class to perform simple logic and generate snippets of
code. Recipe 9.12 shows how to create a custom
template file that uses the JUnitPerfDoclet tag handler. Recipe 9.13 shows how to create an XDoclet
xdoclet.xml file used to define information
about your code generator. Recipe 9.14 shows how to
package JUnitPerfDoclet into a JAR module. Chapter 8 provides information on
the JUnitPerf tool and how to update your Ant
buildfile to invoke JUnitPerfDoclet.
|