This section is for those who want to develop new sub-tasks for XDoclet. End users who just use the tool do not need to read section but it's must read for those who want to write custom sub-tasks or want to contribute.
Currently XDoclet consists of different parts that hand in hand make the "Continuous Re-Configuration" a reality and make XDoclet a flexible tool (or framework) for code/file generation. These parts are:
You normally create an Ant build.xml file and run it to do the whole build process, including compiling source files, packaging them, running tests and deploying them. XDoclet just adds another step to the tasks performed during such a build to generate code/files. We could have provided it without depending it to Ant (as it was in EJBDoclet pre v1.0 days) but the whole idea of XDoclet is the continuous reconfiguration! And Ant does it best. It also makes it extremely easy for user to pass parameters to XDoclet by simply putting there some XML elements and attributes. But it's still perfectly possible to not use Ant at all because dependency on Any is isolated to only a single base class, xdoclet.DocletTask, that derives from org.apache.tools.ant.Task and acts as a proxy to Ant.
A very simple and easy to use template authoring system is provided, it's something like JSP but there's no scriptlet support and you write template tags which are similar to JSP tag libraries but the main deference is that XDoclet's template tags are very lightweight compared to a JSP tag library, there's no deployment descriptor for a template tag, there no command-pattern-style mechanism like JSP that makes you define a new class for each tag you write. For example a template tag like <XDtClass:classTagValue tagName="ejb:bean" paramName="name"/> is simply implemented as a "public String classTagValue( Properties attributes ) throws XDocletException" method in xdoclet.tags.ClassTagsHandler class. Many templates tags could be defined in a single class, that single class is like a tag library in JSP all in itself! Note that it does not mean that template tags in XDoclet are stateless, you can store state in the handler class as normal attributes of that class and refer to them in different tag handler methods or even you can ask other tags in other tag handlers (tag libraries in JSP language) very easily from your tags. So it makes it really easy to throw away scriptlets and write template tag handlers. It's worth to mention here that XDoclet's template system is not a competitor to other template systems (such as Velocity) because it doesn't have many bells and whistles and is not optimized or generalized for general template usage but we think that it's amazingly simple and enough for XDoclet's needs.
XDoclet has a heavy dependency on Javadoc's Doclet API. It's an API Sun added to Javadoc in JDK 1.2 to let developers write their own tools based on it that accept the class hierarchy structure of source files with all javadoc comments in it and generate something based on it. XDoclet uses Doclet API for analyzing source code (for example in ejbdoclet to find classes that inherit from javax.ejb.EntityBean and generate PK class for it for example) and access the javadoc comments user put into source codes. Note that we may create an extended Javadoc Doclet API of our own for later version because Doclet API proves to be limited for our ever-expanding needs (for example work is under way to create a GUI tools for XDoclet and it needs a Doclet API that permits it to add javdoc comments to existing source codes on the fly and Doclet API is read only with no mutator methods).
At the core of XDoclet's extensibily are the template files. No specific language is used for writing these files, normal XML is used, like use of JSP tag libraries. Here are two examples. This one shows a template that generates parts of a java source file:
<XDtEjbIntf:ifRemoteEjb> // obtain remote home interface public static synchronized <XDtEjbHome:homeInterface type="remote"/> getHome() throws NamingException { if(home == null) { Object objRef = getIC().lookup(<XDtEjbHome:homeInterface type="remote"/>.COMP_NAME); home = (<XDtEjbHome:homeInterface type="remote"/>)PortableRemoteObject.narrow(objRef, <XDtEjbHome:homeInterface type="remote"/>.class); } return home; } </XDtEjbIntf:ifRemoteEjb>
And this one shows a template file that generates parts of an XML file:
<ejb-class><XDtEjb:concreteFullClassName/></ejb-class> <XDtType:ifIsOfType type="javax.ejb.SessionBean"> <session-type><XDtClass:classTagValue tagName="ejb:bean" paramName="type" values="Stateful,Stateless" default="Stateless"/></session-type> <transaction-type><XDtClass:classTagValue tagName="ejb:bean" paramName="transaction-type" values="Container,Bean" default="Container"/></transaction-type> </XDtType:ifIsOfType>
Basically template tags are divided into to categories: content tags and block tags.
<XDtEjbIntf:ifRemoteEjb></XDtEjbIntf:ifRemoteEjb> is a block tag, it evaluates its body if current class is a remote EJB.
<XDtEjbHome:homeInterface type="remote"/> is a content tag. homeInterface returns an string and adds it to exactly where it's defined in template file.
So those tags with separate <blabla> and <blabla/> head and tails are block tags while those like <blabla/> are content tags. It's like normal XML.
Template tags are placed inside namespaces, <XDtType:ifIsOfType> is defined in Type namespace. XDt is always prefixed and it's not part of the namespace name.
And finally by convention template files have .j extension.
You may wonder where <XDtClass:classTagValue> for example is actually implemented and whether these tags are fixed or you can write your own. The following section describes what's going on in XDoclet to access the template tag handler classes and call them and how you can define template tag handlers.
Basically we can break XDoclet framework into three hierarchies:
Here is a simplified class diagram showing the most important classes in this regard with some examples of concrete tag handler classes:
As it's obvious from the diagram all template tag handler classes derive from abstract base class xdoclet.TemplateTagHandler. It acts as a support class that provides tag handler access to an instance of TemplateEngine and TemplateContext. Template tag handlers use TemplateContext to communicate with the outer world. XDocletTagSupport acts as a support class for all tag handlers that deal with Doclet API. Note that the template engine does not have any dependency to Doclet API or any other part of XDoclet framework. XDocletTagSupport lets template tag handlers share some common tasks (like keeping track of what the current method is while iterating over methods of a class) in a base class.
Some examples of concrete template tag handler classes are provided in the above diagram. For example, ClassTagHandler defines all template tags that work on current class such as fullClassName which return full qualified name of the class under processing, or ifIsClassAbstract which is a block tag and its implementation evaluates the body of that tag when current class is an abstract one.
If you look closely you see some naming and signature patterns in those method definitions. For example a content tag like fullClassName return String, while a block tag like ifIsClassAbstract return void and accepts an String, or block tag forAllClasses accepts another Properties object as method parameter.
This is the naming/signature of template tag implementation methods:
All template tag implementation methods throw XDocletException, which is itself derived from TemplateException.
The following code snippet shows how a content tag className is implemented in xdoclet.tags.ClassTagsHandler:
/** * Returns the not-full-qualified name of the current class without the * package name. * * @return Description of the Returned Value * @exception XDocletException Description of Exception * @doc:tag type="content" */ public String className() throws XDocletException { return getCurrentClass().name(); }
It uses getCurrentClass() method of XDocletTagSupport base class to get an instance of com.sun.javadoc.ClassDoc (the current class, the one XDoclet is processing) and use name() method to return short class name of current class.
The following code snippet shows how a block tag ifIsClassAbstract is implemented in xdoclet.tags.ClassTagsHandler:
/** * Evaluate the body block if current class is abstract. * * @param template The body of the block tag * @exception XDocletException Description of Exception * @see #ifIsClassNotAbstract(java.lang.String) * @doc:tag type="block" */ public void ifIsClassAbstract( String template ) throws XDocletException { if( getCurrentClass().isAbstract() ) { generate( template ); } }
I think you can easily guess what's going on. The only thing needing explanation if "generate( template );". generate() is one of those support methods defined in XDocletTagSupport. When in a block tag you call it it means the body of that template tag should be processed, if you don't it's not processed. So if current class is abstract the body is evaluated, if not nothing happens and control returns to containing elements.
The following code snippet shows how a block tag ifHasClassTag is implemented in xdoclet.tags.ClassTagsHandler:
/** * Evaluates the body if current class has at least one tag with the specified * name. * * @param template The body of the block tag * @param attributes The attributes of the template tag * @exception XDocletException Description of Exception * @doc:tag type="block" * @doc:param name="tagName" optional="false" * description="The tag name." * @doc:param name="paramName" description="The parameter * name. If not specified, then the raw content of the tag is returned." * @doc:param name="paramNum" description="The zero-based * parameter number. It's used if the user used the space-separated format * for specifying parameters." * @doc:param name="superclasses" values="true,false" * description="If true then traverse superclasses also, otherwise look up * the tag in current concrete class only." * @doc:param name="error" description="Show this error * message if no tag found." */ public void ifHasClassTag( String template, Properties attributes ) throws XDocletException { if( ifHasTag_Impl( template, attributes, true ) ) { generate( template ); } else { String error = attributes.getProperty( "error" ); if( error != null ) { getEngine().print( error ); } } }
This one shows the usage of the second Properties parameters. The value of error attribute of <XDtClass:ifHasClassTag error="Error!"> element is retrieved. Because all attributes are strings you should do type conversion to convert it to java type, for example if the value can be "true" or "false" literal and you want a java boolean variable, then you need to convert it from String to boolean. XDoclet has a TypeConversionUtil utility class with a stringToBoolean() static method that does the trick for you, so type conversion is not a big deal.
An interesting thing to note is @doc:tag type="content" comment. XDoclet uses its own capabilities to document itself! When packaging XDoclet for release we run a template file on all tag handler classes and generate an html documentation from method comment and @doc:tag comments. You can also document all attributes an element may have using @doc:param comment. /docs/template.html file is the file generated from these @doc tags.
Here is a simplified class diagram showing the most important classes in this regard with some examples of concrete tag handler classes:
It shows two main classes: TemplateEngine and PrettyPrintWriter.
TemplateEngine, as the name shows, is where the dirty task of processing template files and calling template tag handler methods is performed. You can can use it separate from the recent of XDoclet like a javabean, just set templateFile and output attributes and call start(), the specified template file will be processed and output written to output!
But how TemplateEngine know when it sees a <XDtClass:className/> it should call className() method of xdoclet.tags.ClassTagsHandler?
It's easy, you register a instance of type TemplateTagHandler with TemplateEngine to handler all tags of a namesapce and it's done using setTagHandlerFor/getTagHandlerFor methods. So somewhere in code you should see an statement like engine.setTagHandlerFor( "Class", new ClassTagsHandler). But if you search you won't see! That's because XDoclet also has simpler way of defining tag handlers. You can use a properties file to map namespace names to handler class names, currently it's called tagmappings.properties and is placed in XDolcet's jar root. Here is how it looks like:
#General namespaces Type=xdoclet.tags.TypeTagsHandler Class=xdoclet.tags.ClassTagsHandler
And easily Class is mapped to xdoclet.tags.ClassTagsHandler. Note that TemplateTagHandler classes should have a no argument public constructor, so that TemplateEngine be able to create an instance of it.
The rest is rather easy to guess, TemplateEngine extracts the namespace name, looks up the tag handler, and using reflection calls the method with the same name in that class using invokeBlockMethod and invokeContentMethod utility methods.
Now what if you don't want to touch tagmappings.proepties file of XDoclet jar file and simply want to give XDoclet an external template file and have it process it?
To do so you can use <template/> element and specify the path to the template file in file system. Here is an example:
<taskdef name="templatedoclet" classname="xdoclet.DocletTask" classpath="${xdoclet.jar.path};${log4j.jar.path};${ant.jar.path}"/>
<templatedoclet sourcepath="${java.dir}" destdir="${output.dir}" classpathref="project.class.path">
<fileset dir="${java.dir}">
<include name="test/ejb/*Bean.java"
/>
</fileset>
<template templateFile="D:\XDocletProject\xdoclet\core\samples\script\report.j"
destinationFile="report-for-{0}.html"
ofType="javax.ejb.SessionBean,javax.ejb.EntityBean"
extent="hierarchy" havingClassTag="ejb:bean"/>
</templatedoclet>
What it does is iterate on all *Bean.java files in test/ejb directory and run report.j template for each class if the class is derived from EntityBean or SessionBean and has a ejb:bean tag somewhere in its class comments or class comments of its parent. Refer to documentation for template sub-tasks for more details.
This way you can simply write templates of your own, but what if you want to write template tag handlers of your own too, without touching tagmappings.properties? You can, by simply putting a tagDef in first line of your template file like this:
<XDtTagDef:tagDef namespace="MyNS" handler="com.foo.MyNSTagsHandler"/>
Just remember you should put com.foo.MyNSTagsHandler in classpath of XDoclet (the classpath or classpathref attribute of templatedoclet task).
All tasks (ejbdoclet, webdoclet, etc) can have <template/> nested elements.
Here is a simplified class diagram showing the most important classes in this regard with some examples of concrete task and sub-task classes:
In case you were wondering where webdoclet or templatedoclet and its sub-tasks like jsptaglib are implemented, this section will clarify it for you.
Tasks such as EjbDocletTask derive from DocletTask base class. Some attributes common to all tasks are defined there, for example sourcepath and classpath. It follows Ant's convention for defining attributes, so to let Ant do the dirty job of setting sourcepath attribute for us, we define a createSourcepath() method that returns a Path object. To let Ant know that nutiple <fileset/> elements are possible we add a addFileSet method and for simple single attributes destDir get/setDestDir is defined. DocletTask does have any sub-tasks, but it can have many <template/> sub-task elements. WebDocletTask on the other hand has <deploymentdescriptor/> and <jsptaglib/> sub-tasks for example. Like tasks, sub-tasks are also initialized by Ant, so taglibversion attribute of JspTaglibSubTask is automatically set by Ant via the setTaglibversion method.
xdoclet.SubTask is abstact base class for all sub-tasks. SubTasks return the name they are registered via getSubTaskName() method, for ClassTagsHandler it is "Class" for example. DocletTask calls init() method to initialize SubTask, validateOptions to let subtask validate it and throw XDocletException if an attribute in invalid and finally execute is called to run it.
SubTask has no knowledge of template files, TemplateSubTask is a handy class that knows about template files, and finally XmlSubTask is another handy class for sub-tasks that generate xml files, it offers the options of validating the generated xml file against a DTD file.
SubTasks are solely used for setting configuration parameters and controlling whether a class should be processed or not. If your custom template needs some configuration parameters then you'll have to write your own SubTask class and define attributes of your sub-task there.
Hopefully you now have a good understanding of how XDoclet works and how you can enhance it with your own template/tasks/sub-tasks. Just remember the best guide that will help you in doing it is looking at XDoclet's source for various parts as an example. Have fun hacking around it!
The XDoclet Team.