简体   繁体   中英

How to capture an Enum from an AnnotationValue in an Annotation Processor

I am trying to read the value of an enum in an annotation using an annotation processor and annotation mirror, but I am getting back null. I think this has to do with the AnnotationValue wrapping an Enum as a VariableElement. The doc for VariableElement#getConstantValue() says "Returns the value of this variable if this is a final field initialized to a compile-time constant." Okay, but final isn't a valid modifier for an annotation member. Also of note is I have no trouble reading other annotation values, just Enums.

I've done some sleuthing an it appears the AnnotationValue is instantiated as a Symbol.VarSymbol at run-time, but Symbol.VarSymbol#getConstantValue() looks like it should just return the object.

Finally if I do a toString() on the AnnotationValue I get the proper value.

The Annotation:

package annotation;
public @interface AnAnnotation
{
    String value();
    Behavior defaultBehavior() default Behavior.NEW;

    public static enum Behavior
    {
        NEW, NULL;
    }
}

Part of my Processor and nested inside a plethora of loops to get at the proper AnnotaionMirror:

Map<? extends ExecutableElement, ? extends AnnotationValue> annotationValues = elemUtils.getElementValuesWithDefaults(annotationMirror);
for (ExecutableElement method : annotationValues.keySet())
{
    ...
    else if ("defaultBehavior".equals(method.getSimpleName().toString()))
    {

        defaultBehavior = (Behavior)( (VariableElement)annotationValues.get(method).getValue()).getConstantValue();

        // This prints "NEW" or "NULL" correctly
        processingEnv.getMessager().printMessage(Diagnostic.Kind.NOTE,annotationValues.get(method).toString());
        // This prints null incorrectly (expect "NEW" or "NULL")
        processingEnv.getMessager().printMessage(Diagnostic.Kind.NOTE, defaultBehavior + "");

    }
    ...
}

EDIT: a more complete version of the Processor.

package annotation.processor;

import java.util.*;

import javax.annotation.processing.*;
import javax.lang.model.element.*;
import javax.lang.model.type.*;
import javax.lang.model.util.*;
import javax.tools.*;

import annotation.AnAnnotation;
import annotation.AnAnnotation.Behavior;

@SupportedAnnotationTypes("annotation.AnAnnotation")
public class AnAnnotationProcessor extends AbstractProcessor
{
    Types typeUtils;
    Elements elemUtils;

    @Override
    public void init(ProcessingEnvironment processingEnv)
    {
        super.init(processingEnv);
        typeUtils = processingEnv.getTypeUtils();
        elemUtils = processingEnv.getElementUtils();
    }

    @Override
    public boolean process(Set<? extends TypeElement> annotations,
                           RoundEnvironment roundEnv)
    {
        processingEnv.getMessager().printMessage(Diagnostic.Kind.NOTE,
            "Entering AnnotationNullableClassProcessor");

        /****** Iterate over all annotaions being processed (only AnAnnotation) ******/
        for (TypeElement annotation : annotations)
        {
            /****** Iterate over all elements that are annotated with the annotation ******/
            for (Element element : roundEnv.getElementsAnnotatedWith(annotation))
            {
                /****** Iterate over all the declared annotations of the element ******/
                for (AnnotationMirror annotationMirror :  element.getAnnotationMirrors())
                {
                    final String annotationTypeName = annotationMirror.getAnnotationType().toString();

                    // Process annotations of type AnAnnotation
                    if (annotationTypeName.equals(AnAnnotation.class.getName()))
                    {
                        Map<? extends ExecutableElement, ? extends AnnotationValue> annotationValues = elemUtils.getElementValuesWithDefaults(annotationMirror);

                        /****** Iterate over the annotation's values. ******/
                        for (ExecutableElement method : accessorValues.keySet())
                        {
                            if ("defaultBehavior".equals(method.getSimpleName().toString()))
                            {
                                Behavior defaultBehavior = (Behavior)( (VariableElement)annotationValues.get(method).getValue()).getConstantValue();

                                // This prints "NEW" or "NULL" correctly
                                processingEnv.getMessager().printMessage(Diagnostic.Kind.NOTE,annotationValues.get(method).toString());
                                // This prints null incorrectly (expect "NEW" or "NULL")
                                processingEnv.getMessager().printMessage(Diagnostic.Kind.NOTE, defaultBehavior + "");
                            }
                        }
                    }
                }
            }
        }

        return true;
    }
}

From the documentation for getConstantValue:

"In particular, enum constants are not considered to be compile-time constants. To have a constant value, a field's type must be either a primitive type or String."

http://java.sun.com/javase/6/docs/api/javax/lang/model/element/VariableElement.html#getConstantValue()

To get the value of the enum constant either use the getAnnotation API or use an AnnotationValueVisitor.

I had a similar problem to yours (except I wasn't dealing with enums, I needed the value of a non-String/non-primitive constant) and got it solved by accessing the source code via the Compiler Tree API .

Here's the general recipe:

1. Create a custom TreePathScanner:

private static class CodeAnalyzerTreeScanner extends TreePathScanner<Object, Trees> {

private String fieldName;

private String fieldInitializer;

public void setFieldName(String fieldName) {
    this.fieldName = fieldName;
}

public String getFieldInitializer() {
    return this.fieldInitializer;
}

@Override
public Object visitVariable(VariableTree variableTree, Trees trees) {
    if (variableTree.getName().toString().equals(this.fieldName)) {
        this.fieldInitializer = variableTree.getInitializer().toString();
    }

    return super.visitVariable(variableTree, trees);
}

2. In your AbstractProcessor, save a reference to the current compilation tree by overriding the init method:

@Override
public void init(ProcessingEnvironment pe) {
    super.init(pe);
    this.trees = Trees.instance(pe);
}

3. Get the initialization source code for the VariableElement (in your case an enum):

// assuming theClass is a javax.lang.model.element.Element reference
// assuming theField is a javax.lang.model.element.VariableElement reference
String fieldName = theField.getSimpleName().toString();
CodeAnalyzerTreeScanner codeScanner = new CodeAnalyzerTreeScanner();
TreePath tp = this.trees.getPath(theClass);

codeScanner.setFieldName(fieldName);
codeScanner.scan(tp, this.trees);
String fieldInitializer = codeScanner.getFieldInitializer();

And that's it! With that you should be able to get to the initialization value, for an annotated field, that you can't normally get using VariableElement.getContantValue (ie any "constant" that's not a String or primitive).

For more reading and examples read this article: Source Code Analysis Using Java 6 APIs .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM