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





(C) Copyright Liu Zehua 2004-2005.
All rights reserved.
Last Updated Nov 2005.