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)
An annotation processor processes these annotations at compile time or runtime to provide functionality such as code generation, error checking, etc. The java. lang package provides some core annotations and also gives us the capability to create our custom annotations that can be processed with annotation processors.
The JDT-APT project provides plugins that add Java 5 annotation processing support to Eclipse. A Java annotation processor is a compiler plug-in that can gather information about source code as it is being compiled, generate additional Java types or other resource files, and post warnings and errors.
Annotations are used to provide supplemental information about a program. Annotations start with '@'. Annotations do not change the action of a compiled program. Annotations help to associate metadata (information) to the program elements i.e. instance variables, constructors, methods, classes, etc.
Add annotations to your project To enable annotations in your project, add the support-annotations dependency to your library or app. Any annotations you add then get checked when you run a code inspection or lint task.
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));
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With