Dr. Dobb's is part of the Informa Tech Division of Informa PLC

This site is operated by a business or businesses owned by Informa PLC and all copyright resides with them. Informa PLC's registered office is 5 Howick Place, London SW1P 1WG. Registered in England and Wales. Number 8860726.


Channels ▼
RSS

JVM Languages

Java Annotations and apt


July, 2005: Java Annotations and apt

J. Benton is a professional Java programmer and computer science graduate student with research interest in AI planning. He can be reached at [email protected].


Java developers have long been able to add metadata to code using comment-eating tools such as Javadoc and XDoclet. However, Java 5.0 introduces a language facility called "annotations" that integrates a metadata technology directly into the language, providing a standard for giving attributes to declarations (see JSR-175, (http://www.jcp.org/en/jsr/detail?id=175). To support annotations, Sun Microsystems released apt, an "Annotation Processing Tool" that lets you write your own annotation processors (http://java.sun.com/j2se/1.5.0/docs/guide/apt/GettingStarted.html). This makes it possible for you to build programs that act upon annotations for analyzing Java source files and automatically generating boilerplate code. It does this by providing the mirror API, which provides functionality similar to the reflection API built into the Standard Java libraries. In this article, I examine annotations and show how you use apt to analyze Java source files and generate boilerplate code.

You use annotations just as you would any modifying keyword (final, static, transient, and the like), except you put "@" in front of it. Each annotation has a set of intended declaration targets. That is, an annotation is intended to be placed before a package, type (including class, enum, and interface), field (including an enum constant), constructor, method, parameter, local variable declaration, or any combination of these. Three types of annotations exist within Java, each with slightly different syntax. Table 1 describes each type.

java.lang Annotations

Syntax always gets clearer as you see examples of real usage. Happily, J2SE 5.0 supplies annotations built into the API to explore. Three are intended to be used by Java compilers and defined in java.lang, so no import is required to use them.

  • @Deprecated. As a standard practice with Java, any declaration that has become obsolete or should not be used for some reason, whether replaced or not, becomes deprecated. Before this annotation, the only way to mark a deprecated declaration was to use Javadoc. This annotation helps compilers and IDEs tell you when you're using a deprecated declaration. It's a marker annotation (no values required). You can use this to annotate any item; see Example 1.
  • @SuppressWarnings. Compilers warn you when your code is syntactically legal but potentially problematic. Sun's javac compiler gives you the ability to turn on warnings using the -Xlint option, which is very much connected with the @SuppressWarnings annotation. However, each compiler can define its own warnings (although there's a request from Sun that compiler writers work to make the warning values compatible).
  • To compile Example 2, you'd enter:

javac -Xlint:unchecked
UncheckedExample.java

  • This returns a warning. When you add @SuppressWarnings(value={"unchecked"}) as in Example 3, you get no warning.
  • You can suppress more than one warning by putting more into the value array; for instance, @SuppressWarnings(value={"unchecked", "finally"}). Table 2 lists the possible options that can be given to -Xlint (given to javac in the form of -Xlint:<option>). If you give no options to -Xlint, it enables all warnings. You can also disable warnings through -Xlint by doing -Xlint:-<option>. Again, including the extra minus disables a warning.

  • @Override. If you think you're overriding a method, use the @Override annotation, which lets the compiler catch the common problem of thinking you're overriding something when you aren't. This is especially useful to keep track of class hierarchy. For instance, if I try to implement java.util.Collection, then try to override the method Object get(int index), I get a compiler error. If I neglected to use the annotation, the compiler would just think I'm creating a new method. Example 4 fails to compile because the List interface—not the Collection interface—defines get(). List extends Collection, which is occasionally a source of confusion for those who are used to exclusively using Lists.

Meta Annotations

An annotation type defines annotations, just as an interface defines an object. In fact, annotation types exist as interfaces and act like them in some respects. They automatically extend the java.lang.annotation.Annotation interface and have methods with return types. Their methods can only return primitive types, strings, enums, another annotations, and arrays of those things.

In addition to the three previous annotation classes in the java.lang package, four are defined in java.lang.annotation. All the annotations in that package are meta annotations—annotations that annotate annotation types.

  • @Retention. Annotations can have one of three retention policies in Java 5.0. Annotations having "source" retention (RetentionPolicy.SOURCE) exist only in source code and don't get compiled into bytecode. Annotations with "class" retention (RetentionPolicy.CLASS) are intended to be read at the bytecode level, after compilation and before runtime. Finally, runtime retention (RetentionPolicy.RUNTIME) indicates that an annotation can be accessed through reflection at runtime. I focus on annotations with "source" retention because apt is a source-level processing tool.
  • @Target. I previously described the type of targets. You can specify a single target using a single value or multiple ones using an array. Also, giving an empty array to an annotation of this type holds special value. It forces you to use annotation types only as a return type within other annotation types. The apt examples show why this is sometimes necessary when you want multiple annotations of the same type to be given on a single item.
  • @Documented. The @Documented meta annotation is intended for use by document-generation tools like javadoc. Javadoc generates documentation for an annotation type if, and only if, this meta annotation is applied to it.
  • @Inherited. As meta annotations go, @Inherited probably ranks as the most interesting (although it is also excluded from the apt examples for simplicity). It lets annotations be inherited by subclasses of the annotated class. If an annotation type uses this meta annotation, any type (including classes, interfaces, and enums) that it annotates lets this annotation be inherited to its subtypes. This is an "if and only if" condition—if you don't want subtypes to inherit this annotation, then don't use it. (Annotations are inherited on the annotation types themselves. Overridden methods will not inherit annotations.)

Boilerplate Code and apt

In their short history, Java annotations have become widely known to be useful for generating boilerplate code. I present an example of this by showing an annotation processor using apt. The apt annotation processor generates new exception classes based upon an annotation given to a type. The exception is slightly different depending upon the values given to the annotation. Listing One contains one of two annotation types required for this example.

Annotations give the "default" keyword a second meaning (the first being the "default case" in the switch statement). You use it in annotation type definitions to specify the default value that a method in an annotation returns if the value is not specified.

In Listing One, @Target contains an empty array. The Java compiler won't let you use the same annotation type more than once on the same item. To get around this, you have to create an annotation that takes an array of ApplicationException annotations as its value. The only way you can use an annotation without a target is within another annotation, so defining an annotation in this way ensures proper semantics for the annotation processor. Listing Two shows ApplicationExceptions, the annotation type containing an array of ApplicationException. ApplicationExceptions targets types.

The real power of apt comes from the way it processes annotations in Java source files. The tool uses annotations to:

  • Zero in on the files it needs to process.
  • Analyze the files appropriately and produce boilerplate code or external configuration files.

When apt generates a source file, it processes and compiles it in turn. ApplicationExceptionsApf.java (available electronically; see "Resource Center," page 3) shows the annotation processor factory and class for creating the boilerplate code based upon the ApplicationExceptions annotation. Notice the structure of the factory class:It extends com.sun.mirror.apt.AnnotationProcessorFactory and overrides "AnnotationProcessor getProcessorFor(Set, AnnotationProcessorEnvironment)". You give a list of annotations that the factory in ApplicationExceptionsApf.java has processors to handle with the variable supportedAnnotations. The provided list can sometimes look like Java's import facility, but don't be fooled—it acts differently. Unlike package imports, com.ddj.annotations.* means all annotations defined in the package com.ddj.annotations and all of its subpackages. Also, you can specify "*" to mean all annotations can be supported by this factory (this has sort of a double meaning, as it denotes this annotation processor can process any file—even ones containing no annotations). If you want to define more than one package, include them in the array.

You can also define your own, user-defined options for annotation processors. To do this, you let the method supportedOptions() return a collection of options beginning with -A. For instance, say you want the processor to have the ability to override the additional information given in the example, so you add -AIgnoreAddedInfo as a supported option. The list only adds the convenience of checking the "supported options" against the options provided to apt. All of apt's own options and those starting with an -A, whether supported or not, get passed to the processor through environment.

In the example, the method getFactoryFor() works in a straightforward manner. It checks that the given supported options match those given to apt. After that, it goes through the annotations that apt has found that the annotation processor also claims to support (given through the atds set). Once it finds com.ddj.annotations.ApplicationExceptions, it returns the proper annotation processor. If it fails to find the suitable annotation, it returns a NO_OP annotation. Use NO_OP whenever you want to return an annotation processor that does nothing.

Also in ApplicationExceptionsApf.java is the annotation processor itself. It goes into the processor factory class as a static inner class. If you're familiar with Java reflection, you recognize the methods used to analyze the code. Sun's mirror API works much like reflection, except it refers to source code rather than loaded classes and, therefore, works on types and declarations. Table 3 shows each package in the mirror API and a short description of each.

The processor is simple. It first looks at each type declaration given to the apt environment. It then gets the ApplicationExceptions annotation and processes each ApplicationException value within, generating a .java file containing the appropriate exception definition.

Next, you'll need to use this annotation processor. apt provides two ways of doing this: You can either use apt's built-in annotation processor factory discovery facility, or specify the processor on a command line. You'd use the discovery procedure when you have more than one annotation processor factory. Because only one annotation processor factory exists here, I stick to the command line. You can use the discovery procedure by putting a file called "META-INF/services/com.sun.mirror.apt.AnnotationProcessorFactory" in a .jar file that lists each annotation processor factory that you created. The .jar file also needs to contain all factories listed and their processors. You then can put the .jar file in your class path and apt tries to choose the proper factory for each .java file you're processing, based upon supported annotations you list within each factory.

Listing Three is an example of using the ApplicationExceptions class. Again, I use a command-line option to generate and execute the annotation processor. Before executing the following command, make sure the tools.jar file is in your classpath and that you've compiled the annotation processor factory:

apt -factory com.ddj.apt.processors.
ApplicationExceptionsApf com\ddj\*.java

This should have generated two files: TestException.java contains an extra integer and AppException.java exists as a plain extension to Exception.

Conclusion

In this article, I introduced the annotations built into J2SE 5.0 and looked at how you can use apt to generate boilerplate exception code. I encourage you to expand upon the code. It's important to note, however, that apt's days may be numbered. The recently approved JSR-269, the Pluggable Annotation Processing API, expressly states that it is intended to supersede Sun's mirror API and apt. If it stays on schedule, it will likely ship with Java 6.0 (code-named "Mustang").

DDJ



Listing One

/* ApplicationException.java * Created on Jan 2, 2005  */
package com.ddj.annotations;

import java.lang.annotation.*;

/** This annotation is used by the annotation processing tool (apt)  
* to generate an exception. * @author J. Benton */
@Retention(RetentionPolicy.SOURCE)
// I give an empty array to "@Target" to say that this annotation
// type should be used only as an element of other annotations
// (you cannot use this to annotate anything directly).
@Target({})
public @interface ApplicationException {
  /** What I want to call this exception (sans the word "Exception".*/
  String exceptionName();
  /** I must add extra information to this exception to make it useful. */
  String addedInformationType() default "";
  /** I should call the "extra information" something that is recongizable if 
   * you try to read the generated exception class. */
  String addedInformationVariableName() default "";
}
Back to article


Listing Two
/* ApplicationExceptions.java * Created on Jan 2, 2005 */
package com.ddj.annotations;

import java.lang.annotation.*;
/** This was made so I can generate a set of exceptions for an application 
 * on the main application class. * @author J. Benton */
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.SOURCE)
@Documented
public @interface ApplicationExceptions {
  /** An array of ApplicationExceptions. I do things this way because we can't 
   * have more than one instance of the same annotation type on any item. */
  ApplicationException[] applicationExceptions();
}
Back to article


Listing Three
/* ExceptionAnnotationTest.java * Created on Jan 3, 2005 */
package com.ddj;

import com.ddj.annotations.*;

/** Use this to test the annotations. * @author J. Benton  */
@ApplicationExceptions(
  applicationExceptions={@ApplicationException(exceptionName="Test",
      addedInformationType="int",
      addedInformationVariableName="status"), 
   @ApplicationException(exceptionName="App")})
public class ExceptionAnnotationTest {
}
Back to article


Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.