Reputation: 99889
Suppose a class defines a constant field:
public class Foo {
public static final int CONSTANT_FIELD = 3;
}
And suppose an annotation interface is declared like the following:
public @interface Something {
int value();
}
Finally, suppose the annotation is used as follows:
@Something(Foo.CONSTANT_FIELD)
Question: In an annotation processor, how can I get the element for CONSTANT_FIELD
from its use in setting the value of @Something
?
Edit: Including a concrete example in the question itself.
I have an annotation that gets used like this:
@RuleDependency(recognizer = BQLParser.class,
rule = BQLParser.RULE_statement,
version = 0)
The annotation processor needs to know that RULE_statement
is a constant defined in the BQLParser
class. If I could access the Element
for BQLParser.RULE_statement
directly from setting the rule
property of the annotation, it would eliminate the need for the recognizer
property. This annotation is used thousands of times within real applications, and the recognizer
is always just the declaring type of the rule
constant. Resolving this question would simplify the annotation usage to just this:
@RuleDependency(rule = BQLParser.RULE_statement, version = 0)
Upvotes: 9
Views: 1431
Reputation: 99889
I was able to implement this feature using the Compiler Trees API.
Update the pom.xml to include the following profiles to make sure tools.jar is referenced on systems where it is not referenced by default.
<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>
Override Processor.init
to get an instance of Trees
.
@Override
public void init(ProcessingEnvironment processingEnv) {
super.init(processingEnv);
this.trees = Trees.instance(processingEnv);
}
Implement a TreePathScanner<TypeMirror, Void>
, which is used to obtain the TypeMirror
of the declaring type assigned to the rule
property in an annotation.
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());
}
}
Provide a default value for the recognizer
property. I wish this could be null
but Java explicitly forbids that...
/**
* 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;
Update the code that gathers information about about the RuleDependency
annotations applied to particular Element
instances in the code to first try accessing the recognizer
property, and if it's not specified, use the declaring type of the constant in the rule
property instead. Error handling is omitted from this code sample for brevity.
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));
Upvotes: 3
Reputation: 2548
If rule
is an int
, it's not going to be possible for the annotation processor to find out where that int
was defined. However, you could use an enum
instead of an int
for the rule
field and group your rules in there with references to their parsers. Maybe something like this:
Parser interface:
public interface RuleParser {
}
Example implementation:
public class RuleParserImpl implements RuleParser {
}
Rule enum:
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;
}
}
Annotation with enum instead of int field:
@Retention(RetentionPolicy.RUNTIME)
public @interface RuleDependency {
Rule rule();
}
Usage example:
@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"
}
}
Hope that gives you some ideas.
Upvotes: 1