XDoclet Tutorial: A Short Tutorial on XDoclet Templates
Liu Zehua
XDoclet is a code generation engine that allows attribute-oriented programming in Java.
This short tutorial aims at giving readers a quick idea of how XDoclet could be used to
simplify programming. It focuses on the use of XDoclet in general attribute-oriented and
template-based programming, rather than the "typical" usage of the whole bunch of predefined
XDoclet tags/tasks. A simple utility Java application for supporting command line arguments
handling is presented as a running example.
Introduction
So XDoclet has been out there
for quite a while and you probably might have heard of it quite a few times, too.
Now that you want to use it for your project, and what you would like to have is probably
a quick tutorial that shows, step-by-step, how you could use it, and even better, one that comes
with some runnable codes that are ready for your copy-and-paste.
That was exactly what I wanted to look for (and expected to see) in the
XDoclet Web site,
when I thought that my project could make use of attributed-oriented programming.
The Web site has several good introductory materials for users who want to use
it for its "originally intended usage", i.e., for generating EJBs and web.xml's.
However, although XDoclet could be used as a general-purpose code generation engine
(which seems to be the new goal of the project), there is not much documentation in this area.
I thought I have to code a customized XDoclet subclass for my task, and some TagHanlder.
Then I spent some time reading the documentation distributed around the site to realize
that I only need to come out with a template in certain syntax. After some struggling,
I was finally able to get it running and producing some nice code generation.
To prevent others from having the same confusion and wasting the same efforts, I
decided to write this short tutorial to fill in the space.
The goal of this tutorial is to be short and simple yet able to give readers a quick
idea of how XDoclet works in general and to enable them to jump start their use of XDoclet.
What this tutorial is NOT about
It's not about <ejbdoclet>, <webdoclet> or any other predefined
<xxxdoclet> in XDoclet.
It does not teach you how to write customized XDoclet tasks, either.
Prerequisites
Targeted readers are expected to have knowledge about Java and Ant.
Knowledge about template engines such as Velocity might help, but not essential.
A motivating example
I will use a simple example to illustrate the use of XDoclet for attribute-oriented
and template-based programming in general.
When we write a Java application, very often we need to take in command line arguments
as parameters to the application. Below is a typical scenario of how the arguments
are handled:
private static void printUsage() {
System.out.println("Usage: java myapp.MyApp [-inputurl|-u input_url] [-outputtype|-o output_type]");
System.exit(-1);
}
public static void main(String[] args) {
String inputURL = null, outputType=null;
for (int i=0; i<args.length; i++) {
if (args[i].equals("-inputurl") || args[i].equals("-u")) {
if (i+1<args.length) {
inputURL = args[i+1];
i++;
} else {
printUsage();
}
} else if (args[i].equals("-outputtype") || args[i].equals("-o")) {
if (i+1<args.length) {
outputType = args[i+1];
i++;
} else {
printUsage();
}
} else {
System.err.println("unknown option '"+args[i]+"'");
printUsage();
}
}
...
}
If we adopt this approach frequent enough, we might want to put these lines of codes as common
utility functions or a utility class, for reusability. However, as the
arguments for each application are likely to be different, the codes are actually
NOT that reusable. That is, we will have to modify
the argument names such as "inputurl" and "outputtype" and variable names
such as inputURL and maybe add some
new "else if" statements and declaring more variables to support more arguments.
And we have to be rather careful during the modification because once we change something, say
"inputurl", several places in the code need to be updated as well.
Each time we create a new application, we copy and paste the above codes and do the modification.
This is just one example of the kind of repetitive and tedious work that programmers
are forced to do from time to time.
The copy-and-paste and modification of such codes might sound easy. But it could quickly
take up a considerable amount of your precious time.
How XDoclet helps
The pattern of handling such command line arguments is quite obvious - each argument
has a short and long name followed by its value and it takes one if statement
to handle one argument. It is then natural to ask whether we could have some tools
that smartly follow these patterns
to generate the codes for us.
This is how XDoclet comes into the picture. To have such kind of automated code generation,
we need machenisms to specify the parameters and to specify how the code should be generated
according to the parameters. XDoclet provides both - to specify parameters,
we annotate our Java files with name-value pairs;
to dictates code generation, we supply a code template.
Annotating data fields
As discussed earlier, for each argument, what we need to specify is a long and short label
and a short descriptive name for printing the "usage" message. In XDoclet, these parameters
could be
attached to the corresponding field (or variable) as anntation tags, as shown in the following
code segments:
public class CLArguments {
... ...
/**
* @clarguments.argname longname="inputurl" shortname="u" shortdesc="input_url"
*/
protected String inputURL = null;
/**
* @clarguments.argname
* longname="outputtype"
* shortname="o"
* shortdesc="output_type"
*/
protected String outputType = null;
... ...
}
Each @clarguments.argname that is attached to a field is called a field tag.
Each field tag could have multiple parameters, listed right after the field tag
in the same line or
in separate lines for readability.
This way of specifying field tag parameters is nothing new to us Java programmers,
as it resembles the familiar javadoc
tags. In fact, the implementation of XDoclet is based on the
XJavaDoc engine,
a complete rewrite of Sun's JavaDoc engine.
In the tag name clarguments.argname, the prefix clarguments serves as the namespace
of all tags that we might use for this particular XDoclet application. This is to
avoid naming conflict with other standard and/or customized XDoclet tags.
It should be noted that not only fields can be annotated with tags, other entities
such as class and functions can also have tags.
Templating code generation
Now we have the parameters specified. We need to tell XDoclet what kinds of codes
that we would like it to generate using these parameters.
This is accomplished by writing an XDoclet template.
If you have experiences with
template engines such as Velocity,
you should find XDoclet templates rather familiar.
We create a template file composed of Java codes and special XDoclet instructions in the form of
XML tags. These XDoclet instructions allow conditionals (if) and loops (for),
thus, in effect, provides us with an expressive power close to a programming language.
In the template file in our example, we would like to first create an array that
contains the long and short labels and other information of each argument,
as shown in the following template segment:
public class <XDtClass:classOf><XDtClass:className/>Impl</XDtClass:classOf>
extends <XDtClass:classOf><XDtClass:className/></XDtClass:classOf> {
public static String[ ][ ] argumentNames = new String[ ][ ] {
<XDtField:forAllFields>
<XDtField:ifHasFieldTag tagName="clarguments.argname">
{
"<XDtField:fieldName/>",
"<XDtField:fieldTagValue tagName="clarguments.argname" paramName="longname"/>",
"<XDtField:fieldTagValue tagName="clarguments.argname" paramName="shortname"/>",
"<XDtField:fieldTagValue tagName="clarguments.argname" paramName="shortdesc"/>"
},
</XDtField:ifHasFieldTag>
</XDtField:forAllFields>
};
... ...
The first two lines declare a class with a name xxxImpl that extends the class xxx.
The XDoclet template tag XDtClass:className denotes the name of the class in the annotated
Java file. All
standard XDoclet template tags
have a namespace starting with XDt. XDoclet also allows us to create customized template tags.
However, such discussion is beyond the scope of this tutorial.
As we shall demonstrate later in this tutorial, XDoclet enables us to do really powerful
code generation without writing a single line of codes to customize it.
The rest of the template segment above iterates through all fields of a class, using
XDtField:forAllFields.
For each field that has a field tag named clarguments.argname
(checked using XDtField:ifHasFieldTag),
it will create a sub-array of String's using the various values
obtained from the field tag parameters.
XDtField:fieldName gives the name of the field; while
XDtField:fieldTagValue retrieves the value of a given field tag parameter.
Any characters that are not part of some
XDoclet template tags are directly copied into the generated codes.
The code segment generated by XDoclet using the annotated fields and
the above template segment is:
public class CLArgumentsImpl
extends CLArguments {
public static String[ ][ ] argumentNames = new String[ ][ ] {
{
"inputURL",
"inputurl",
"u",
"input_url"
},
{
"outputType",
"outputtype",
"o",
"output_type"
},
};
... ...
Similarly, we could also generate the getter and setter methods
for each field, using the following template segment:
<XDtField:forAllFields>
<XDtField:ifHasFieldTag tagName="clarguments.argname">
public <XDtField:fieldType/> get<XDtField:fieldName/>() {
return <XDtField:fieldName/>;
}
public void set<XDtField:fieldName/>(String value) {
setValue("<XDtField:fieldName/>", value);
}
</XDtField:ifHasFieldTag>
</XDtField:forAllFields>
This translates to the following generated codes:
public java.lang.String getinputURL() {
return inputURL;
}
public void setinputURL(String value) {
setValue("inputURL", value);
}
public java.lang.String getoutputType() {
return outputType;
}
public void setoutputType(String value) {
setValue("outputType", value);
}
Note that due to the lack of string manipulation functions,
such as a "capitalize()" function, we are not able to produce method names
that conform to the common JavaBeans code convention for getter and setter methods.
Such kind of more complex manipulation is possible, if we define our own
customized XDoclet task. Documentation on how this could be done can be found
in the XDoclet Web site.
Associating template with annotated data
With the annotated Java code and the template, we are ready to configure XDoclet
to perform the automated code generation task for us. This is done using
Apache Ant. It should be noted that the
use of Ant with XDoclet is a must.
To associate a template with a
Java source file with XDoclet annotations, we define a customized Ant task named
"xdoclet" (could be any name you like), associating it with the class
xdoclet.DocletTask. Be sure that the jar files of XDoclet, XJavaDoc, etc. are
included in the classpathref.
<target name="prepare">
... ...
<taskdef
name="xdoclet"
classname="xdoclet.DocletTask"
classpathref="xdoclet.class.path"
/>
... ...
</target>
With this user-defined task, we can now specify which Java files to supply to XDoclet
and what template file(s) it should use. As shown in the listing below,
the fileset specifies the Java files which are annotated with attributes;
and the template subtask defines the filename of the generated file
(the destinationFile attribute)
and which template file to use (the templateFile attribute).
<target name="gensrc" depends="prepare">
<xdoclet
destdir="${xdoclet.gen-src.dir}"
excludedtags="@version,@author,@todo"
force="${xdoclet.force}"
verbose="true"
>
<fileset dir="${xdoclet.src.dir}">
<include name="**/CLArguments.java"/>
</fileset>
<template
destinationFile="{0}Impl.java"
templateFile="${xdoclet.template.dir}/clarguments_impl.xdt"
subTaskName="Command line arguments"
>
</template>
</xdoclet>
</target>
In the destinationFile attribute above, we specify the filename
using a parameter {0} concatenated with "Impl.java". {0} here
denotes the name of the Java class of the annotated file.
This way of code generation is called per-class because
for each template subtask, each annotated Java file could only produce one
output file.
Using this per-class method also causes the classname of the generated Java
class, which is the same
as the filename, to be hardcoded in the Ant build file. Recall that we use
<XDtClass:className>Impl in the template file to assign the name
of the generated class.
In case we want to rename the class, we would have to change both the template
file and the build file.
That is not so good. But we leave it to the XDoclet develpers. (or is there
some way to avoid that?)
To generate the codes, we only need to type the command "ant gensrc",
which, I hope, is rather obvious.
Putting everything together
With the generated class, we can now handle command line arguments in
a more generic manner. Below is one method of the CLArguments class.
Compare it with the code listing at the very beginning.
public static CLArgumentsImpl processArguments(String[] args) {
final String msgPrefix = "CLArguments.processArguments ";
CLArgumentsImpl clargs = new CLArgumentsImpl();
clargs.setInitialized(false);
for (i=0; i<args.length; i++) {
if (args[i].startsWith("-")) {
if (args[i].length()>1) {
String argName = args[i].substring(1);
String paramName = getParameterName(argName);
if (paramName == null) {
System.err.println(msgPrefix+" unknown option "+argName);
break;
} else {
if (i+1>=args.length) {
System.err.println(msgPrefix+" option "+argName+ " has no value");
break;
}
clargs.setValue(paramName, args[i+1]);
i++;
}
} else {
System.err.println(msgPrefix+" invalid option "+args[i]);
break;
}
} else {
clargs.setInitialized(true);
break;
}
}
... ...
}
private static String getParameterName(String argName) {
for (int i=0; i<CLArgumentsImpl.argumentNames.length; i++) {
if (argName.equals(CLArgumentsImpl.argumentNames[i][1]) || // compare with long label
argName.equals(CLArgumentsImpl.argumentNames[i][2])) { // compare with short label
return CLArgumentsImpl.argumentNames[i][0];
}
}
return null;
}
The class CLArguments contains other utiltiy methods. We do not list
them here, as the source codes are available for download.
With all the work being done by CLArguments and the generated CLArgumentsImpl,
the processing of command line arguments now becomes rather simple, as illustrated in the code
below:
private static void printUsage() {
System.out.print("Usage: java myapp.MyApp" + CLArguments.getUsageString());
}
public static void main(String[] args) {
CLArgumentsImpl clargs = CLArguments.processArguments(args);
if (!clargs.isInitialized()) {
printUsage();
} else {
if (clargs.getinputURL() != null) {
// do something with input url
}
if (clargs.getoutputType() != null) {
// do something with output type
}
// now do the real work
}
}
Summary
XDoclet provides a powerful mechanism for attribute-oriented and template-based code generation.
As illustrated in this tutorial, it could be used to help reduce the amount of copy-and-paste
work (or sometimes called "no-brain" work) that programmers have to perform from time to time.
When you discover that you have to repeatedly copy some existing codes and do minor modification,
there is an opportunitiy that you could do something to automate and facilitate this process
such that the next time you need to do it, it becomes a simple case of specifying a few parameters.
At this point in time, you should think of XDoclet.
However, we should bear in mind that it's sometimes rather tricky to come out with the
generalization of codes, which forms the gist of the code template.
It also takes time to realize that the codes that you have been repeatedly
writing could be generalized and generated automatically.
Finally, I hope that this tutorial really serve its purpose.
Please feel free to send your feedback and suggestions to me.
Download Source Code
|