2010-06-21 12 views
5

Estoy tratando de leer el valor de una enumeración en una anotación usando un procesador de anotación y un espejo de anotación, pero me estoy volviendo nulo. Creo que esto tiene que ver con AnnotationValue envolviendo un Enum como un elemento variable. El documento para VariableElement # getConstantValue() dice "Devuelve el valor de esta variable si se trata de un campo final inicializado a una constante en tiempo de compilación". Está bien, pero el final no es un modificador válido para un miembro de anotación. También es de destacar que no tengo problemas para leer otros valores de anotación, solo Enumeraciones.Cómo capturar un Enum de un AnnotationValue en un procesador de anotaciones

He investigado un poco y parece que AnnotationValue está instanciado como Symbol.VarSymbol en tiempo de ejecución, pero Symbol.VarSymbol # getConstantValue() parece que debería devolver el objeto.

Finalmente, si hago un toString() en AnnotationValue obtengo el valor correcto.

la anotación:

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

    public static enum Behavior 
    { 
     NEW, NULL; 
    } 
} 

Parte de mi procesador y anidado dentro de un gran número de bucles para llegar a la AnnotaionMirror adecuada:

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: una versión más completa del procesador.

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; 
    } 
} 
+0

Olvidé mencionar que esta es la versión Java SE 6 del proceso de anotación. –

Respuesta

3

De la documentación para getConstantValue:.

"En particular, las constantes de enumeración no son considerados como de tiempo de compilación constantes para tener un valor constante, el tipo de un campo debe ser de un tipo primitivo o Cadena ".

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

para obtener el valor de la constante de enumeración o bien utilizar la API getAnnotation o utilizar un AnnotationValueVisitor.

+0

Es extraño entonces que las enumeraciones se pongan en VariableElement; de AnnotationValue: "VariableElement (que representa una constante de enumeración)" De todos modos, ¿podría explicar en AnnotationValueVisitor? He intentado lo siguiente: AnnotationValueVisitorImpl clase privada se extiende SimpleAnnotationValueVisitor6 {@ Override protegida defaultAction Objeto (Object o, objeto p) {\t retorno o; \t} } Sin embargo, cuando lo paso al método de aceptación de AnnotationValue obtengo un Symbol.VarSymbol y no la enumeración que estaba buscando. –

+0

FYI @ joe-darcy escribió la API de anotación de Java –

1

Tuve un problema similar al tuyo (excepto que no estaba tratando con enums, necesitaba el valor de una constante no-String/no primitiva) y lo resolví accediendo al código fuente a través del Compiler Tree API.

Aquí está la receta general:

1. Crear un TreePathScanner personalizado:

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. En su AbstractProcessor, Parada referencia al árbol compilación actual reemplazando el método init:

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

3. obtener el código fuente de inicialización para el VariableElement (en su caso una enumeración):

// 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(); 

Y eso es todo! Con eso deberías poder llegar al valor de inicialización, para un campo anotado, que normalmente no puedes obtener usando VariableElement.getContantValue (es decir, cualquier "constante" que no sea una Cadena o primitiva).

Para obtener más lecturas y ejemplos, lea este article: Source Code Analysis Using Java 6 APIs.

Cuestiones relacionadas