I am trying to access the actual original source code of a type from within a Java Annotation Processor. Is this possible somehow? Thanks!
Annotations provide information to a program at compile time or at runtime based on which the program can take further action. An annotation processor processes these annotations at compile time or runtime to provide functionality such as code generation, error checking, etc. The java.
1 Answer. Show activity on this post. Settings -> Build, Execution, Deployment -> Compiler -> Annotation Processors -> Enable Annotation Processing.
Annotation processing takes place at compile time (compile time = the time when the java compiler compiles your java source code). Annotation processing is a tool build in javac for scanning and processing annotations at compile time. You can register your own annotation processor for certain annotations.
Annotations don't execute; they're notes or markers that are read by various tools. Some are read by your compiler, like @Override ; others are embedded in the class files and read by tools like Hibernate at runtime.
I had a problem where I had to access some source code (the initializer code for 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! In the end the fieldInitiliazer variable is going to contain the exact line(s) of code used to initialize my constant. With some tweaking you should be able to use the same recipe to access the source code of other element types in the source tree (i.e. methods, package declarations, etc)
For more reading and examples read this article: Source Code Analysis Using Java 6 APIs.
Simple adaptation of @AdrianoNobre's answer. It reflects the intended use of visitor pattern bit better.
private Trees trees; @Override public synchronized void init(ProcessingEnvironment processingEnv) { super.init(processingEnv); trees = Trees.instance(processingEnv); }
private static class MethodScanner extends TreePathScanner<List<MethodTree>, Trees> { private List<MethodTree> methodTrees = new ArrayList<>(); public MethodTree scan(ExecutableElement methodElement, Trees trees) { assert methodElement.getKind() == ElementKind.METHOD; List<MethodTree> methodTrees = this.scan(trees.getPath(methodElement), trees); assert methodTrees.size() == 1; return methodTrees.get(0); } @Override public List<MethodTree> scan(TreePath treePath, Trees trees) { super.scan(treePath, trees); return this.methodTrees; } @Override public List<MethodTree> visitMethod(MethodTree methodTree, Trees trees) { this.methodTrees.add(methodTree); return super.visitMethod(methodTree, trees); } }
MethodScanner methodScanner = new MethodScanner(); MethodTree methodTree = methodScanner.scan(methodElement, this.trees); methodTree.getBody();
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