Java Annotations and apt

Java 5.0 introduces "annotations" that integrate metadata technology directly into the language.


July 01, 2005
URL:http://www.drdobbs.com/jvm/java-annotations-and-apt/184406144

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.

javac -Xlint:unchecked
UncheckedExample.java

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.

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:

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

July, 2005: Java Annotations and apt

/** Deprecating a method using the annotation. @author J. Benton */
public class DeprecateExample {
  // if you use this method, you'll get a warning that it is deprecated.
  @Deprecated
  public void ohNoIAmDeprecated() {
     System.out.println("I am deprecated!");
  }
}

Example 1: Deprecating a method using annotation.

July, 2005: Java Annotations and apt

import java.util.ArrayList;
/** Suppressing warnings. @author J. Benton */
public class UncheckedExample {
  public void uncheckedGenerics() {
    ArrayList blahblah = new ArrayList();
    // warning, warning, warning!!  unchecked call below!
    blahblah.add(2);
  }
}

Example 2: Generating a warning.

July, 2005: Java Annotations and apt

import java.util.ArrayList;

/** Suppressing warnings. @author J. Benton */
// You can use so-called single-valued annotations
// just like regular annotations if you want (note
// how I am specifying the method name here, though
// it is unnecessary).
@SuppressWarnings(value={"unchecked"})
public class UncheckedExample {
  public void uncheckedGenerics() {
    ArrayList blahblah = new ArrayList();
    // no warning!  I suppressed it!
    blahblah.add(2);
  }
}

Example 3: Using the @SuppressWarnings annotation.

July, 2005: Java Annotations and apt

package com.ddj;
import java.util.*;

/** Using the @Override annotation. @author J. Benton */
public class OverrideExample implements Collection {
  // ...
  /** This won't work!  I'm not overriding anything here!
   * This is a new method--not one being overridden.
   * @param index The index of the thing to get.
   * @see java.util.List#get(int)
   */
  @Override
  public Object get(int index) {
    Object value = null;
    // code to get stuff.
    return value;
  }
  // ...
}

Example 4: Using the @Override annotation.

July, 2005: Java Annotations and apt

Annotation Description Example Syntax
Marker Takes no values @MarkerAnnotation
Single valued Takes one value @SingleValuedAnnotation("VALUE")
Regular Takes more than one value @RegularAnnotation(name="John Q.", bugValue=1)

Table 1: Annotations get addressed differently depending upon the number of values they take.

July, 2005: Java Annotations and apt

Options Description
none Give no warnings.
unchecked Give details on unchecked conversions of types.
path Check for a nonexistent path in environment paths (such as classpath).
serial Check that a serialVersionUID is given in serializable classes.
finally Check that finally classes can be completed normally.
fallthrough Make sure you break after each case statement that would otherwise fall through to the next case statement in a switch.
depreciation Check for use of depreciated items.

Table 2: Options that can be given the -Xlint in javac.

July, 2005: Java Annotations and apt

com.sun.mirror.apt Provides classes that interact directly with the tool and its data.
com.sun.mirror.declaration Defines interfaces to declarations.
com.sun.mirror.type Defines interfaces to types defined within the source code.
com.sun.mirror.util Contains utilities for processing types and declarations.

Table 3: Descriptions of the apt mirror API.

Terms of Service | Privacy Statement | Copyright © 2024 UBM Tech, All rights reserved.