Sun has recently released Annotation Processing Tool (apt)
along with beta2 of JDK1.5. This tool greatly helps in using the annotations
feature that is introduced in JDK1.5. In this article, I will walk you through
the steps required to create, use and process annotations, with the help of an
example.
This is not an article on annotations feature in JDK1.5, but
an article on how to process annotations using apt. One can refer to the article
“Declarative Programming in Java”
at OnJava to get a very good overview of annotations
and what they have to offer.
“Apt” is a command line utility that helps in processing
annotations on the source code. Instead of delving into the details on what
“apt” offers, I will straight away dive into an example, which can help one
appreciate the “power” of apt.
During software development, it is very common for
developers to use markers such as “TODO”, “FIXME” to mark the work that is not
yet complete in all aspects. Usually these will be present inside the comments.
Usually the developer searches the source code regularly to figure out if there
are still any more incomplete tasks. Let us use annotations in this scenario,
for the purpose of understanding the annotations feature and understanding how
to the processing them using the apt. The objective is to process the annotated
source code and get list of places where the code has been marked as “ToDo”.
Before proceeding further, ensure that you have JDK1.5 beta
2 with you. You can download it from here. Also, as you are
aware, JDK1.5 comes with host of other additions to the language, such as
Generics, enhanced for loop, static imports. I will refrain from using the new
features in the listings below to the best possible extent, so that the focus
of the reader is not diverted to these new features.
Declaring an annotation is very simple. Following is the
listing of the ToDo annotation. This annotation does
not have any members.
|
Listing 1: ToDo.java package
com.rajs.jdk1_5; public
@interface ToDo { } |
Notice the special “@” tag before the interface declaration.
This indicates that this is not a regular interface, but an annotation.
Annotations can be at package level, class level, field
level, method level and even at local variable level. Let us create two classes
one which uses the annotation at a class level and another at method level for
the sake of the example.
|
Listing 2: IncompleteClass.java package
com.rajs.jdk1_5; @ToDo public class
IncompleteClass { } |
|
Listing 3: PartiallyCompleteClass.java package
com.rajs.jdk1_5; public
class PartiallyCompleteClass { private int age
= 20; @ToDo private String name; public PartiallyCompleteClass() { } public int getAge() { return age; } @ ToDo public void incompleteMethod() { } } |
Notice the way the class, method and attribute are annotated
by simply putting the “@<AnnotationName> before
declaring them.
Now we have code which can compile in JDK1.5. However there
is no use of this code as we are not doing anything with the annotations. Let
us now process these annotations so that we get a report of the source code
that is incomplete (annotated as ToDo).
The first thing that we need to do to process the
annotations is to create an annotation processor factory. An annotation processor
factory is responsible for creating an annotation processor for one or more
annotation types. JDK1.5 beta2 comes with new classes that are part of the
tools.jar. These new classes must be used by developers intending to use apt.
There is an interface com.sun.mirror.apt.AnnotationProcessorFactory
as a part of these new classes that must be implemented by us.
Here is the listing for our factory.
|
Listing 4: MyAPF.java package
com.rajs.jdk1_5; import java.util.Collection; import java.util.Set; import java.util.Vector; import com.sun.mirror.apt.AnnotationProcessor; import com.sun.mirror.apt.AnnotationProcessorEnvironment; import com.sun.mirror.apt.AnnotationProcessorFactory; public
class MyAPF implements AnnotationProcessorFactory { Vector anntTypes
= new Vector(); public MyAPF() {
//all annotations are processed by this factory.
anntTypes.addElement("*"); } public Collection<String> supportedOptions() {
//no options are supported. return new Vector(); } public Collection<String> supportedAnnotationTypes() { return anntTypes; } public AnnotationProcessor
getProcessorFor( Set <com.sun.mirror.declaration.AnnotationTypeDeclaration>
set, AnnotationProcessorEnvironment
env) { //return an instance of ToDoAnnotation Processor. return new ToDoAP(env); } } |
In the above listing, we have declared a class that
implements the com.sun.mirror.apt.AnnotationProcessorFactory
interface. There are three methods in the factory.
The first method supportedOptions() returns if there any options that
are supported by this annotation processing factory. For beginners like us, at
this moment, we can ignore this and return an empty collection.
The second method supportedAnnotationTypes() returns the collection of
annotation types that can be processed by this factory. Since we have just one
annotation type here, we can either give that name here or simply return a “*”,
indicating all annotations are processed by this class.
The third and the most important method getProcessorFor()
returns the AnnotationProcessor that actually
processes the annotations. Here we are returning an instance of ToDoAP (discussed in detail below). This method will be
invoked by the apt. The tool shall pass the context of the processing using the
AnnotationProcessorEnvironment parameter. The AnnotationProcessingEnvironment has methods that provide
the state of processing the source code which can be used by the AnnotationProcessor.
The annotation processor is the class that actually
processes the annotations. This is the class that has the ability to create new
source files, configuration files (deployment descriptors, for example), class
files, or data files. Here is the listing for ToDoAP.java
|
Listing 5: ToDoAP.java package
com.rajs.jdk1_5; import java.io.File; import java.io.PrintWriter; import java.util.Collection; import java.util.Iterator; import com.sun.mirror.apt.AnnotationProcessor; import com.sun.mirror.apt.AnnotationProcessorEnvironment; import com.sun.mirror.declaration.AnnotationTypeDeclaration; import com.sun.mirror.declaration.Declaration; public
class ToDoAP implements AnnotationProcessor { AnnotationProcessorEnvironment
ape; PrintWriter pw; public ToDoAP(AnnotationProcessorEnvironment ape) { this.ape
= ape; } /** * @see com.sun.mirror.apt.AnnotationProcessor#process() */ public void process() {
AnnotationTypeDeclaration todoAtd = (AnnotationTypeDeclaration) ape.getTypeDeclaration("com.rajs.jdk1_5.ToDo"); Collection c = ape.getDeclarationsAnnotatedWith(todoAtd); for(Iterator
I = c.iterator(); i.hasNext();) { Declaration typeDecl
= (Declaration)i.next(); System.out.println(typeDecl.getPosition()); } } } |
In the above code, the method of significance is
“process()”. In this method, the first line returns the declaration that
represents the “com.rajs.jdk1_5.ToDo” annotation. The next line is the most
important line – this provides the collection of Declarations† that are annotated with
“com.rajs.jdk1_5.ToDo”. This is achieved by calling the method “getDeclarationsAnnotatedWith()” method on the AnnotationProcessorEnvionment.
The resulting collection of Declaration instances is
iterated and the position of the annotation is printed by calling the getPosition() method on each instance of Declaration.
As
mentioned earlier, to compile this code, one needs to have JDK1.5 Beta 2. You
can download the same here.
Ensure that
the classpath contains “%JAVA_HOME%\lib\tools.jar”.
Ensure the
path contains “%JAVA_HOME%\bin”.
Use the
“–source 1.5” option along with javac, as we are compiling code containing JDK1.5 specific
features.
|
javac –cp %CLASSPATH% -d bin –source 1.5
src/com/rajs/jdk1_5/*.java |
Finally, we are now ready to run the annotation processor
tool.
Apt first processes the annotations in the specified source
code. Apt recursively processes any new source code that is generated during
the earlier processing, till no more source code is there to be processed..
Finally, all the source code will be compiled using javac
by apt.
Apt has a default inbuilt mechanism by which it identifies
the AnnotationProcessorFactories that needs to be
used during its execution. However, in this article, we will use a much simpler
way of specifying the apt the factory class to be used by passing the same as a
command line parameter to apt.
Following is the invocation of apt and the output
|
>apt -cp %CLASSPATH% -d bin -s src -source 1.5 -factory com.rajs.jdk1_5.MyAPF
src/com/rajs/jdk1_5/*.java >src/com/rajs/jdk1_5/PartiallyCompleteClass.java:4 >src/com/rajs/jdk1_5/PartiallyCompleteClass.java:20 >src/com/rajs/jdk1_5/IncompleteClass.java:9 |
As you see the output contains the list of java files and
approximate line number where the ToDo annotations
are present.
The article explained in detail the steps involved in
executing the new “annotation processing tool” from Sun. Needless to say, we
have just touched the waters of annotation. There are lots of advanced features
such as DeclarationVisitors, Filer, Messagers that are not covered in this introductory article
to limit the scope. Also it is worthwhile mentioning that while “apt” is
intended to use for processing annotations, it can be used where there is a
need to parse source code. Interested readers can always get into the depth
from the references available in the Resources section.
Please post your comments on this article here.
† A Declaration is something which represents the declaration of a program element such as package, class, interface, method, field in the source code