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 check TypeKind.ARRAY and get the actual declared type of it. Instead of adding another to else if(e.asType().getKind() == TypeKind.ARRAY) // ... the class TypeKindVisitor7 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.