Java annotation processing API accessing import statements
You can't get import statements with an annotation processor. What you can get though, are the types used by that class, which is even better.
Import statements from the source code are not enough for analyzing what types are used in a class, because not all used types have import statements. If you really only need the actual statements, you could read the source file directly.
There are some issues if you only look at the statements:
- fully qualified class name, e.g. a property
java.util.Date date;
- imports from the same package don't have explicit import statements
- imports statements are declared for all classes in a file
- unused import statements could cause additional confusion
With the annotation processor and the Mirror API, you can get the types of properties, method parameters, method return types, etc. - basically the types of every declaration that is not in a method or block. This should be good enough.
You should analyse every element of the class and store it's type in a Set. There are some utility classes that help with this task. You can ignore any type in the java.lang
package since it is always implicitly imported.
A minimal annotation processor may look like this:
import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.RoundEnvironment;
import javax.annotation.processing.SupportedAnnotationTypes;
import javax.annotation.processing.SupportedSourceVersion;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.TypeElement;
@SupportedSourceVersion(SourceVersion.RELEASE_7)
@SupportedAnnotationTypes("*")
public class Processor extends AbstractProcessor {
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
ImportScanner scanner = new ImportScanner();
scanner.scan(roundEnv.getRootElements(), null);
Set<String> importedTypes = scanner.getImportedTypes();
// do something with the types
return false;
}
}
The Scanner here extends ElementScanner7
which is based on a visitor pattern. We only implement a few visitor methods and filter elements by kind because not all elements can actually contain importable types.
import java.util.HashSet;
import java.util.Set;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.TypeParameterElement;
import javax.lang.model.element.VariableElement;
import javax.lang.model.type.TypeKind;
import javax.lang.model.util.ElementScanner7;
public class ImportScanner extends ElementScanner7<Void, Void> {
private Set<String> types = new HashSet<>();
public Set<String> getImportedTypes() {
return types;
}
@Override
public Void visitType(TypeElement e, Void p) {
for(TypeMirror interfaceType : e.getInterfaces()) {
types.add(interfaceType.toString());
}
types.add(e.getSuperclass().toString());
return super.visitType(e, p);
}
@Override
public Void visitExecutable(ExecutableElement e, Void p) {
if(e.getReturnType().getKind() == TypeKind.DECLARED) {
types.add(e.getReturnType().toString());
}
return super.visitExecutable(e, p);
}
@Override
public Void visitTypeParameter(TypeParameterElement e, Void p) {
if(e.asType().getKind() == TypeKind.DECLARED) {
types.add(e.asType().toString());
}
return super.visitTypeParameter(e, p);
}
@Override
public Void visitVariable(VariableElement e, Void p) {
if(e.asType().getKind() == TypeKind.DECLARED) {
types.add(e.asType().toString());
}
return super.visitVariable(e, p);
}
}
This scanner returns a set of types as fully qualified paths. There are still a few things to consider and some things to implement:
- The set contains elements from
java.lang
and also types from the same package - The set contains generics, like
java.util.List<String>
TypeKind.DECLARED
is not the only kind of elements that is an importable type. Also checkTypeKind.ARRAY
and get the actual declared type of it. Instead of adding another toelse if(e.asType().getKind() == TypeKind.ARRAY) // ...
the classTypeKindVisitor7
could be used instead- Depending on the use case there may be even more types to be discovered. For example, annotations can contain classes as arguments.
- For Java 1.6 use the respective
ElementScanner6
,TypeKindVisitor6
etc. implementations.
It looks like there is no way to get import statements from the standard SDK classes (at least with SDK 5-6-7).
Nevertheless, you can use some classes inside tools.jar from SUN/Oracle.
import com.sun.source.util.TreePath;
import com.sun.source.util.Trees;
public class MyProcessor extends AbstractProcessor {
@Override
public void init(ProcessingEnvironment env) {
tree = Trees.instance(env);
}
@Override
public boolean process(final Set<? extends TypeElement> annotations, final RoundEnvironment roundEnvironment) {
for( Element rootElement : roundEnvironment.getRootElements() ) {
TreePath path = tree.getPath(rootElement);
System.out.println( "root element "+rootElement.toString() +" "+path.getCompilationUnit().getImports().size() );
}
....
To get the jar of Java tools via maven, refer to this thread.
There should be an alternative using a TreePathScanner (from tools.jar as well) but the visitImport method was never triggered for me.