Forum Controls
Spotlight Features

The Rich Engineering Heritage Behind Dependency Injection

Andrew McVeigh takes us on a tour of the rich heritage behind dependency injection, what it represents, and tells us why its here to stay.

NetBeans 6: Matisse Updates

NetBeans 6 delivers great updates to the Matisse GUI builder. Spend a few minutes with Roman Strobl and get an expert briefing on what's new and what has changed.

Introduction to Groovy Part 3

In this, the third and final installation of Andres' Introduction to Groovy series, you learn about how Groovy handles variable numbers of arguments, named parameters, currying, and more about Groovy operators. Including, some new operators.

Easier Custom Components with Swing Fuse

Swing Fuse (actually just Fuse), is a framework designed to make it easier to create your own custom desktop components. In this article, Daniel Spiewak shows you how to get started and provides sample source code you can download.

Benchmark Analysis: Guice vs Spring

Willam Louth shows how he uses JXInsight Probes to investigate probable performance issues with code bases that he is not familiar with. He also highlights possible pitfalls in creating a benchmark, as well as in the analysis of results.
Replies: 2 - Pages: 1  
Threads: [ Previous | Next ]
  Click to reply to this thread Reply

Apt: More Compile-Time Annotation Processing with Java

At 5:44 PM on Mar 23, 2005, R.J. Lorimer wrote:

Part 2

Welcome back to my Java Annotation Processing Tool tip-series. If you haven't already, please see my previous tip:

APT: Compile-Time Annotation Processing with Java

Today I'm going to spend more time detailing how you can traverse elements in the mirror API to derive when they have annotations, not to mention what other elements they may have associated with them.

Declarations vs. Types

There are two core APIs in apt that seem remarkably similar - the Types API and the Declaration API. The distinction between declarations and types can be hard to understand, but is vitally important to understand when processing with apt. All of the documentation on apt is very careful to note the difference between declarations and types, and it would be a pity if I didn't do the same here. The mirror API has these two concepts (declaration and type) that seem quite similar. Declarations are the lion's share of the API, and represent the actual source being processed by apt. If you write this code:

public class HelloWorld {
 public java.util.Set<java.lang.String> getStrings() { ... }
}

... apt is going to provide a com.sun.mirror.apt.declaration.ClassDeclaration object for that 'HelloWorld' declaration. There would also be a com.sun.mirror.apt.declaration.MethodDeclaration for the getStrings() method (available via ClassDeclaration.getMethods() ). Both of these interfaces extend com.sun.mirror.apt.declaration.Declaration . However, the return type of the method getStrings() is not an instance of Declaration - instead it is an instance of com.sun.mirror.apt.type.TypeMirror . So, what is the difference? For one thing, types simply do not always correlate to a declaration - some examples include arrays, primitives, the void return type, and now that we have them, we can't forget Generics. After all, as far as Java developers are concerned, there is no declaration for the int type, or for the void return type. Generics complicate matters even futher, because the usage of a type that has a declaration differs from the declaration itself. In our example above, java.util.Set<java.lang.String> doesn't relate directly to the java.util.Set class declaration - after all, we are defining an additional piece of information in this case - the type parameters for the generics. The way the documentation describes it, a TypeMirror represents the use of some construct in the code. Being more specific for the case where you are dealing with classes referring to other classes (or interfaces) - a com.sun.mirror.apt.type.DeclaredType represents a single use (such as a method return type) of a com.sun.mirror.apt.declaration.TypeDeclaration . Relying on that piece of information, here is a block of code that walks the object hierarchy for our little pseudo-class above (note, I am using some less that attractive coding practices simply so I can show the expected values for this particular case):

AnnotationProcessorEnvironment environment = // ... get from somewhere
// get class declaration for 'HelloWorld' class.
ClassDeclaration classDecl = (ClassDeclaration)environment.getTypeDeclaration("HelloWorld");
// get the first method declaration - 'getStrings()' declaration.
MethodDeclaration decl = classDecl.getMethods().iterator().next();
TypeMirror returnTypeMirror = decl.getReturnType();
// in this case we KNOW that it's a ClassType (for Set<String>)
ClassType setType = (ClassType)returnTypeMirror;
// get the DECLARATION of the set type (correlates to java/util/Set.class)
ClassDeclaration setDeclaration = setType.getDeclaration();
 
Collection<TypeMirror> typeArgs = classType.getActualTypeArguments();
// get the first type argument - 'String' in this case
TypeMirror firstTypeArg = typeArgs.iterator().next();
ClassType stringType = (ClassType) firstTypeArg;
ClassDeclaration stringDeclaration = stringType.getDeclaration();

Incidentally, the getSuperclass() method on ClassDeclaration returns a ClassType . This may seem confusing at first, but the more you think about it, the more it makes sense. Here is an example where superclass semantics require a type, and not simply a declaration:

public class MyGenericsSuperclass<E> { ... }
public class MyGenericsSubclass extends MyGenericsSuperclass<String> { ... }

Notice how the superclass definition is given particular type parameters.

Declaration API

The declaration API is fairly hefty - and rather than go to all the trouble of trying to describe each part here - I'll use the 'picture worth 1000 words' excuse, and SHOW you the API.

As can be seen by the above diagram, the API is very rich and granular. While some of it is self-explanatory, there are a few interesting structures I thought merited descriptions.

It can be confusing at first glance that nearly everything extends MemberDeclaration (excluding packages, parameters and type parameters) - after all MemberDeclarations as the terminology suggests, represent a member of a declared type. The thought would be this would include methods, constructors, fields and the like. It turns out, however, that types being member declarations themselves is correct, since we have inner-classes. It just so happens that the method TypeDeclaration getDeclaringType() will return null if the MemberDeclaration it is called on is a top-level TypeDeclaration itself.

Note that there is a ParameterDeclaration and a TypeParameterDeclaration - the distinction is simply for generics - the former is a method declaration parameter (or constructor declaration parameter), the latter is a generic type parameter. Because they are different in nature, they have different declaration APIs.

Declarations Util

The com.sun.mirror.util.Declarations API has two handy methods for determining the context of declarations when dealing with the world of inheritence. The two methods are boolean hides(MemberDeclaration, MemberDeclaration) and boolean overrides(MethodDeclaration, MethodDeclaration) . Both of these methods take a first parameter (the sub-declaration) to compare to the second parameter (the super-declaration). The former is a general-case where method overriding or field-masking can cause a certain member to be hidden. The latter is specifically involves determining method overrides.

The Declarations utils are available through the AnnotationProcessorEnvironment.getDeclarationUtils method.

Types API

The types API is similar to the declaration API - but try not to think of them as parallel. Remember - the types API is specifically designed for the usage of types in another declaration. Here is another 1000 word picture:

You can already see that the hierarchy is much smaller. It turns out the problem domain is smaller. In this case there are only four different types of type mirror - primitive types (references to short, int, byte, char, etc.), reference types (references to other classes, arrays, etc.), void types (only applies to void return types), and wildcard types (only applies to the '?' element for generics).

Types Util

The types utility library com.sun.mirror.util.Types is much less trivial from the Declarations utility class. While some of it is in fact similar in nature to the Declarations class ( boolean isSubtype(TypeMirror, TypeMirror) ), boolean isAssignable(TypeMirror, TypeMirror) - much of the class acts like a 'TypeMirrorFactory'. These 'factory' style methods are important because it is often handy to check methods/fields/constructors/generics/whatever-else for equality to certain types. Some examples:

  • ArrayType getArrayType(TypeMirror) - Gets an array that holds elements represented by the TypeMirror parameter. So, if TypeMirror represents the String class, then it would return an ArrayType representing the String[] construct.
  • VoidType getVoidType() - The special void return type.
  • PrimitiveType getPrimitiveType(PrimitiveType.Kind) - Returns the PrimitiveType object representing the selected primitive type from the PrimitiveType.Kind enumeration.
  • TypeMirror getErasure(TypeMirror) - This gets the runtime resolution of a type mirror reference once the generics have been 'erased' by the compiler.
  • DeclaredType getDeclaredType(...) - The two getDeclaredType() methods are convenient for producing DeclaredType references. For instance, the example they give in the documentation produces a Set<String> DeclaredType (which can be used for equality checks later), and it does so in multiple passes:
    TypeDeclaration setDecl = environment.getTypeDeclaration("java.util.Set");
    TypeDeclaration stringDecl = environment.getTypeDeclaration("java.lang.String");
    DeclaredType stringType = Types.getDeclaredType(stringDecl, null); // String
    DeclaredType setOfStrings = Types.getDeclaredType(setDecl, stringType); // Set<String>
    

I can't give the best examples for using all of these utility methods - simply knowing that they are there should give you the leverage you need when it comes time to check some method/field for the right type when performing annotation processing.

The types utilities are available through the AnnotationProcessorEnvironment.getTypesUtils() method.

Annotation Mirrors

Because apt is a tool specifically designed for annotation processing, it comes with some special facilities for annotations - namely the com.sun.mirror.declaration.AnnotationMirror class. This annotation mirror effectively represents an 'instance' of an annotation. The usage of an annotation on a particular element, values and all. I don't plan on re-hashing the annotation mirror element here, as I showed it briefly in my previous tip. Just be aware that annotation mirrors can be retrieved for any class that implements com.sun.mirror.declaration.Declaration .

Processing with Visitors

Most of the processing in the world of apt is done through the magic of the visitor pattern. Up-to-this-point I have avoided using it because I didn't want to introduce any unnecessary complexities to the code. No longer. The power of the visitor API in apt is two-fold. First, the algorithm for traversing nodes (declarations and types) is abstracted away (you don't have to write it) - and second, you don't have to perform any casting in your code - it does all of the introspection for you with the magic of polymorphism. For instance, let's start small with the TypeVisitor API. The com.sun.mirror.util.TypeVisitor interface defines a ton of 'visit' methods that are invoked by a TypeMirror when it is 'visited'. You can (and should) subclass the com.sun.mirror.util.SimpleTypeVisitor if you want to implement your own visitor. First, this class stubs all of the interface methods, making the implementation for you much simpler. Second, some of the 'visit' methods don't relate directly to a concrete type (e.g. visitDeclaredType(DeclaredType t)) - these methods can be handy if you want to visit all elements that are a DeclaredType. The SimpleTypeVisitor API delegates up the type-hierarchy - for example, visitClassType(ClassType t) does nothing but delegate up to visitDeclaredType(DeclaredType t). This delegation is not part of the TypeMirror elements themselves. To invoke a type visitor given an unknown type mirror, simply call the accept method:

TypeMirror someType = someMethod.getReturnType();
someType.accept(yourTypeVisitor);

The type mirror in question will then invoke the right 'visit' method, and you can process accordingly.

Declarations are similar to types, although they potentially involve more digging (since classes contain methods/fields/constructors/other classes). There is a special static utility class - com.sun.mirror.util.DeclarationVisitors that can help with this process. The DeclarationVisitiors class can create what is called a DeclarationScanner which is really just a special implementation of the DeclarationVisitor API that will respond to a type visit by iterating it's child declarations (e.g. members) and invoking the correct visit method for each on its contained visitor. In other words, it is a processing algorithm for iterating a declaration hierarchy. In the previous tip, I used a special method on the AnnotationProcessorEnvironment class - getDeclarationsAnnotatedWith(AnnotationTypeDeclaration) . Let's say for the moment that we didn't have this special method. Here is how we could implement the exact same behavior using the visitor pattern:

package com.javalobby.tnt.apt;
 
import java.util.*;
 
import com.sun.mirror.apt.*;
import com.sun.mirror.declaration.*;
import com.sun.mirror.type.AnnotationType;
import com.sun.mirror.type.TypeMirror;
import com.sun.mirror.util.*;
 
public class NoteAnnotationProcessor implements AnnotationProcessor {
 
 private AnnotationProcessorEnvironment environment;
 
 private TypeDeclaration noteDeclaration;
 
 private DeclarationVisitor declarationVisitor;
 
 public NoteAnnotationProcessor(AnnotationProcessorEnvironment env) {
  environment = env;
  // get the annotation type declaration for our 'Note' annotation.
  noteDeclaration = environment
    .getTypeDeclaration("com.javalobby.tnt.annotation.Note");
 
  declarationVisitor = new AllDeclarationsVisitor();
 
 }
 
 public void process() {
  Collection<TypeDeclaration> declarations = environment.getTypeDeclarations();
  // Note here we use a helper method to create a declaration scanner for our 
  // visitor, and a no-op visitor.
  DeclarationVisitor scanner = DeclarationVisitors
    .getSourceOrderDeclarationScanner(declarationVisitor,
      DeclarationVisitors.NO_OP);
  for (TypeDeclaration declaration : declarations) {
   declaration.accept(scanner); // invokes the processing on the scanner.
  }
 }
 
 private class AllDeclarationsVisitor extends SimpleDeclarationVisitor {
  @Override
  public void visitDeclaration(Declaration arg0) {
   Collection<AnnotationMirror> annotations = arg0.getAnnotationMirrors();
   for (AnnotationMirror mirror : annotations) {
    // if the mirror in this iteration is for our note declaration...
    if (mirror.getAnnotationType().getDeclaration().equals(noteDeclaration)) {
     // print out the goodies.
     SourcePosition position = mirror.getPosition();
     Map<AnnotationTypeElementDeclaration, AnnotationValue> values = mirror
       .getElementValues();
 
     System.out.println("Declaration: " + noteDeclaration.toString());
     System.out.println("Position: " + position);
     System.out.println("Values:");
     for (Map.Entry<AnnotationTypeElementDeclaration, AnnotationValue> entry : values
       .entrySet()) {
      AnnotationTypeElementDeclaration elemDecl = entry.getKey();
      AnnotationValue value = entry.getValue();
      System.out.println("    " + elemDecl + "=" + value);
     }
    }
 
   }
  }
 }
}

So now you have some idea on how to get started understanding the iteration/processing patterns when dealing with the Mirror API. The final step is to understand how to properly respond to annotations when processing. Next time I will introduce the com.sun.mirror.apt.Messager , the com.sun.mirror.apt.Filer , the concept of Apt rounds, and then, as I usually do, I'll pick up some left-overs to add food-for-thought.

Until next time,

R.J. Lorimer
rj -at- javalobby.org
http://www.coffee-bytes.com

1 . At 5:00 PM on Mar 25, 2005, Dion Almaer wrote:
  Click to reply to this thread Reply

Re: Apt: More Compile-Time Annotation Processing with Java

Nice article.

I also like to use AOP to work with annotations a la:

http://www.almaer.com/blog/archives/000832.html

Cheers,

Dion Almaer
http://www.almaer.com/blog
-- Dion Almaer (blog) Founder and CTO: Adigio, Inc http://www.adigio.com Injecting Dependencies where ever we go
2 . At 4:30 AM on Apr 2, 2005, Annie wrote:
  Click to reply to this thread Reply

Re: Apt: More Compile-Time Annotation Processing with Java

which API can i use to extract the annotation Type "Romote", "Remove" out of the following code

package org.jboss.tutorial.stateful.bean;

import javax.ejb.Remote;
import javax.ejb.Remove;

import java.util.HashMap;

@Remote
public interface ShoppingCart
{
void buy(String product, int quantity);

HashMap getCartContents();

@Remove void checkout();
}

and also i have no idea how to use the method:
A getAnnotation(Class annotationType)
Returns the annotation of this declaration having the specified type.

thanks in advance

thread.rss_message