Java 6 annotation processing -- getting a class from an annotation

Solution 1:

I came here to ask the EXACT same question. ... and found the same blog link posted by Ralph.

It's a long article, but very rich. Summary of the story -- there's two ways to do it, the easy way, and the "more right" way.

This is the easy way:

private static TypeMirror getMyValue1(MyAnnotation annotation) {
    try
    {
        annotation.myValue(); // this should throw
    }
    catch( MirroredTypeException mte )
    {
        return mte.getTypeMirror();
    }
    return null; // can this ever happen ??
}

The other more tedious way (without exceptions):

private static AnnotationMirror getAnnotationMirror(TypeElement typeElement, Class<?> clazz) {
    String clazzName = clazz.getName();
    for(AnnotationMirror m : typeElement.getAnnotationMirrors()) {
        if(m.getAnnotationType().toString().equals(clazzName)) {
            return m;
        }
    }
    return null;
}

private static AnnotationValue getAnnotationValue(AnnotationMirror annotationMirror, String key) {
    for(Entry<? extends ExecutableElement, ? extends AnnotationValue> entry : annotationMirror.getElementValues().entrySet() ) {
        if(entry.getKey().getSimpleName().toString().equals(key)) {
            return entry.getValue();
        }
    }
    return null;
}


public TypeMirror getMyValue2(TypeElement foo) {
    AnnotationMirror am = getAnnotationMirror(foo, MyAnnotation.class);
    if(am == null) {
        return null;
    }
    AnnotationValue av = getAnnotationValue(am, "myValue");
    if(av == null) {
        return null;
    } else {
        return (TypeMirror)av.getValue();
    }
}

Of course, once you get a TypeMirror, you (at least in my experience) pretty much always want a TypeElement instead:

private TypeElement asTypeElement(TypeMirror typeMirror) {
    Types TypeUtils = this.processingEnv.getTypeUtils();
    return (TypeElement)TypeUtils.asElement(typeMirror);
}

... that last little non-obvious bit took me an hour of hair pulling before I sorted it out the first time. These annotation processors are actually not that hard to write at all, the API's are just super confusing at first and mindbendingly verbose. I'm tempted to put out a helper class that makes all the basic operations obvious ... but that's a story for another day (msg me if you want it).

Solution 2:

Have you read this article: http://blog.retep.org/2009/02/13/getting-class-values-from-annotations-in-an-annotationprocessor/ ?

There the trick is to actually use getAnnotation() and catch the MirroredTypeException. Surprisingly the exception then provides the TypeMirror of the required class.

I don't know if this is a good solution, but it is one. In my personal opinion I would try to get the type behind the MirroredType, but I don't know if this is possible.

Solution 3:

My answer is basically the same as that from Dave Dopson, only the code was changed a bit:

public default Optional<? extends AnnotationMirror> getAnnotationMirror( final Element element, final Class<? extends Annotation> annotationClass )
{
    final var annotationClassName = annotationClass.getName();
    final Optional<? extends AnnotationMirror> retValue = element.getAnnotationMirrors().stream()
        .filter( m -> m.getAnnotationType().toString().equals( annotationClassName ) )
        .findFirst();

    return retValue;
}

His code used TypeElement as the type for the first argument of getAnnotationMirror(), limiting the use of the method for classes only. Changing that to Element makes it useable for any annotated element. Using an implementation based on streams instead of the original for loop is just a matter of taste.

public default Optional<? extends AnnotationValue> getAnnotationValue( final AnnotationMirror annotationMirror, final String name )
{
    final Elements elementUtils = this.processingEnv.getElementUtils();
    final Map<? extends ExecutableElement, ? extends AnnotationValue> elementValues = elementUtils.getElementValuesWithDefaults( annotationMirror );
    final Optional<? extends AnnotationValue> retValue = elementValues.keySet().stream()
        .filter( k -> k.getSimpleName().toString().equals( name ) )
        .map( k -> elementValues.get( k ) )
        .findAny();

    return retValue;
}

Dave used Element.getElementValues() to get the annotation values, but this returns only those values that were explicitly applied to the concrete annotation. The defaults will be omitted. The method Elements.getElementValuesWithDefaults() will return also the defaults (as you may have guessed already from the name of it).

Solution 4:

This is my trick:

public class APUtils {

  @FunctionalInterface
  public interface GetClassValue {
    void execute() throws MirroredTypeException, MirroredTypesException;
  }

  public static List<? extends TypeMirror> getTypeMirrorFromAnnotationValue(GetClassValue c) {
    try {
      c.execute();
    }
    catch(MirroredTypesException ex) {
      return ex.getTypeMirrors();
    }
    return null;
  }

}

Then I can use with any Annotation value:

public @interface MyAnnotationType {
  public Class<?>[] value() default {};
}

Example

@MyAnnotationType(String.class)
public class Test { ... }

Usage in annotation processor

MyAnnotationType a = element.getAnnotation(MyAnnotationType.class);
List<? extends TypeMirror> types = APUtils.getTypeMirrorFromAnnotationValue(() -> a.value());

Tested with JDK 1.8.0_192