Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Accessing source code from Java Annotation Processor

Tags:

I am trying to access the actual original source code of a type from within a Java Annotation Processor. Is this possible somehow? Thanks!

like image 769
Eric Avatar asked Jun 16 '11 14:06

Eric


People also ask

What is a Java annotation processor?

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.

How do I run an annotation processor?

1 Answer. Show activity on this post. Settings -> Build, Execution, Deployment -> Compiler -> Annotation Processors -> Enable Annotation Processing.

How does annotation processing work?

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.

How are annotations executed in java?

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.


2 Answers

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.

like image 161
Adriano Nobre Oliveira Avatar answered Oct 09 '22 05:10

Adriano Nobre Oliveira


Simple adaptation of @AdrianoNobre's answer. It reflects the intended use of visitor pattern bit better.

AbstractProcessor init:

private Trees trees;  @Override public synchronized void init(ProcessingEnvironment processingEnv) {     super.init(processingEnv);     trees = Trees.instance(processingEnv); } 

MethodScanner

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);     } } 

Using the scanner to get method body:

MethodScanner methodScanner = new MethodScanner(); MethodTree methodTree = methodScanner.scan(methodElement, this.trees); methodTree.getBody(); 
like image 22
Vlastimil Ovčáčík Avatar answered Oct 09 '22 07:10

Vlastimil Ovčáčík