9

假設一個類定義了一個恆定域:訪問恆定場

public class Foo { 
    public static final int CONSTANT_FIELD = 3; 
} 

並假設的註釋界面被聲明如下所示:

public @interface Something { 
    int value(); 
} 

最後,假設註釋被用作如下:

@Something(Foo.CONSTANT_FIELD) 

問題:在批註處理器中,如何從設置@Something的值中獲取CONSTANT_FIELD的元素?


編輯:在問題本身包含一個具體的例子。

我有被使用這樣的註釋:

@RuleDependency(recognizer = BQLParser.class, 
       rule = BQLParser.RULE_statement, 
       version = 0) 

註釋處理器需要知道RULE_statement處於BQLParser類中定義的常數。如果我可以直接通過設置註釋的rule屬性訪問ElementBQLParser.RULE_statement,那麼它將消除對recognizer屬性的需要。此註釋在實際應用中使用數千次,並且recognizer總是只是rule常數的聲明類型。解決這個問題,將簡化註解的使用,只是這樣的:

@RuleDependency(rule = BQLParser.RULE_statement, version = 0) 

回答

3

我能夠使用Compiler Trees API實現此功能。

  1. 更新的pom.xml包括以下配置文件,以確保的tools.jar上它默認是不參考系統被引用。

    <profiles> 
        <profile> 
         <!-- Java 6 and earlier have java.vendor set to "Sun Microsystems Inc." --> 
         <id>default-tools-6.jar</id> 
         <activation> 
          <property> 
           <name>java.vendor</name> 
           <value>Sun Microsystems Inc.</value> 
          </property> 
         </activation> 
         <dependencies> 
          <dependency> 
           <groupId>com.sun</groupId> 
           <artifactId>tools</artifactId> 
           <version>1.6</version> 
           <scope>system</scope> 
           <systemPath>${java.home}/../lib/tools.jar</systemPath> 
          </dependency> 
         </dependencies> 
        </profile> 
    
        <profile> 
         <!-- Java 7 and later have java.vendor set to "Oracle Corporation" --> 
         <id>default-tools.jar</id> 
         <activation> 
          <property> 
           <name>java.vendor</name> 
           <value>Oracle Corporation</value> 
          </property> 
         </activation> 
         <dependencies> 
          <dependency> 
           <groupId>com.sun</groupId> 
           <artifactId>tools</artifactId> 
           <version>1.6</version> 
           <scope>system</scope> 
           <systemPath>${java.home}/../lib/tools.jar</systemPath> 
          </dependency> 
         </dependencies> 
        </profile> 
    </profiles> 
    
  2. 覆蓋Processor.init獲得的Trees一個實例。

    @Override 
    public void init(ProcessingEnvironment processingEnv) { 
        super.init(processingEnv); 
        this.trees = Trees.instance(processingEnv); 
    } 
    
  3. 實現一個TreePathScanner<TypeMirror, Void>,其用於獲得在一個註釋分配給rule屬性聲明類型的TypeMirror

    private class AnnotationVisitor extends TreePathScanner<TypeMirror, Void> { 
        @Override 
        public TypeMirror visitAnnotation(AnnotationTree node, Void p) { 
         for (ExpressionTree expressionTree : node.getArguments()) { 
          if (!(expressionTree instanceof AssignmentTree)) { 
           continue; 
          } 
    
          AssignmentTree assignmentTree = (AssignmentTree)expressionTree; 
          ExpressionTree variable = assignmentTree.getVariable(); 
          if (!(variable instanceof IdentifierTree) || !((IdentifierTree)variable).getName().contentEquals("rule")) { 
           continue; 
          } 
    
          return scan(expressionTree, p); 
         } 
    
         return null; 
        } 
    
        @Override 
        public TypeMirror visitAssignment(AssignmentTree at, Void p) { 
         return scan(at.getExpression(), p); 
        } 
    
        @Override 
        public TypeMirror visitMemberSelect(MemberSelectTree mst, Void p) { 
         return scan(mst.getExpression(), p); 
        } 
    
        @Override 
        public TypeMirror visitIdentifier(IdentifierTree it, Void p) { 
         return trees.getTypeMirror(this.getCurrentPath()); 
        } 
    } 
    
  4. 提供的recognizer屬性的默認值。我想這可能是null但Java明確禁止的是...

    /** 
    * Gets the recognizer class where the dependent parser rules are defined. 
    * This may reference the generated parser class directly, or for simplicity 
    * in certain cases, any class derived from it. 
    * <p> 
    * If this value is not specified, the default value {@link Parser} 
    * indicates that the declaring type of the constant value specified for 
    * {@link #rule} should be used as the recognizer type. 
    * </p> 
    */ 
    Class<? extends Recognizer<?, ?>> recognizer() default Parser.class; 
    
  5. 更新收集有關大約在代碼中第一次嘗試訪問recognizer屬性適用於特定Element實例RuleDependency註釋信息的代碼,如果未指定,請改用rule屬性中的常量的聲明類型。爲簡潔起見,此代碼示例中省略了錯誤處理。

    RuleDependency dependency = element.getAnnotation(RuleDependency.class); 
    
    // first try to get the parser type from the annotation 
    TypeMirror recognizerType = getRecognizerType(dependency); 
    if (recognizerType != null && !recognizerType.toString().equals(Parser.class.getName())) { 
        result.add(new Triple<RuleDependency, TypeMirror, Element>(dependency, recognizerType, element)); 
        continue; 
    } 
    
    // fallback to compiler tree API 
    AnnotationMirror annotationMirror = null; 
    for (AnnotationMirror mirror : element.getAnnotationMirrors()) { 
        if (processingEnv.getTypeUtils().isSameType(ruleDependencyTypeElement.asType(), mirror.getAnnotationType())) { 
         annotationMirror = mirror; 
         break; 
        } 
    } 
    
    AnnotationValue annotationValue = null; 
    for (Map.Entry<? extends ExecutableElement, ? extends AnnotationValue> entry : annotationMirror.getElementValues().entrySet()) { 
        if (entry.getKey().getSimpleName().contentEquals("rule")) { 
         annotationValue = entry.getValue(); 
         break; 
        } 
    } 
    
    TreePath treePath = trees.getPath(element, annotationMirror, annotationValue); 
    AnnotationVisitor visitor = new AnnotationVisitor(); 
    recognizerType = visitor.scan(treePath, null); 
    
    result.add(new Triple<RuleDependency, TypeMirror, Element>(dependency, recognizerType, element)); 
    
+1

不錯!還要注意的是,當擴展AbstractProcessor時,您總是可以通過受保護的字段「processingEnv」訪問ProcessingEnvironment - 無需重寫'Processor.init'。 – Balder

+0

爲什麼你想要做這一切?當然'enum'是一個更好的解決方案。 –

+0

@LanceJava我無法控制代碼生成的其他部分(例如有關常量的聲明)。所以要回答你的問題,「因爲我想讓它工作,而不是不工作。」 :) –

1

如果ruleint,它不會是可能的標註處理,找出那個int定義。但是,您可以使用enum而不是int作爲rule字段,並將您的規則與其解析器的引用進行分組。也許是這樣的:

Parser接口:

public interface RuleParser { 
} 

實現示例:

public class RuleParserImpl implements RuleParser { 
} 

規則枚舉:

public enum Rule { 

    RULE_STATEMENT(RuleParserImpl.class); 

    private final Class<? extends RuleParser> ruleParserClass; 

    private Rule(Class<? extends RuleParser> ruleParser) { 
     this.ruleParserClass = ruleParser; 
    } 

    public Class<? extends RuleParser> getRuleParserClass() { 
     return ruleParserClass; 
    } 
} 

註釋與枚舉,而不是INT領域:

@Retention(RetentionPolicy.RUNTIME) 
public @interface RuleDependency { 
    Rule rule(); 
} 

用例:

@RuleDependency(rule = Rule.RULE_STATEMENT) 
public class RuleProcessor { 

    public static void main(String[] args) { 
     RuleDependency ruleDependency = RuleProcessor.class.getAnnotation(RuleDependency.class); 
     Rule rule = ruleDependency.rule(); 
     Class<? extends RuleParser> ruleParserClass = rule.getRuleParserClass(); 
     System.out.println(ruleParserClass); //Prints "class RuleParserImpl" 
    } 
} 

希望給你一些想法。

+0

這是不可能改變這些常量的聲明。是否有可能從註釋處理器訪問編譯器的源文件內部樹表示形式,或者通過正常註釋處理器用例之外的方法導出信息? –