8

我正在编写一个应该生成 java 代码的 AnnotationProcessor。它应该从某些现有接口生成派生接口。

为此,我需要找到原始输入代码的导入语句,以便将其输出到生成的 java 文件中。

如何才能做到这一点?

4

3 回答 3

9

您无法使用注释处理器获取导入语句。但是,您可以获得的是该类使用的类型,这甚至更好。

源代码中的导入语句不足以分析类中使用了哪些类型,因为并非所有使用的类型都有导入语句。如果你真的只需要实际的语句,你可以直接阅读源文件

如果你只看陈述,会有一些问题:

  • 完全限定的类名,例如属性java.util.Date date;
  • 从同一个包中导入没有明确的导入语句
  • 为文件中的所有类声明导入语句
  • 未使用的导入语句可能会导致额外的混乱

使用注解处理器和Mirror API,您可以获得属性类型、方法参数、方法返回类型等 - 基本上是每个不在方法或块中的声明的类型。这应该足够好了。

您应该分析类的每个元素并将其类型存储在 Set 中。有一些实用程序类可以帮助完成这项任务。您可以忽略java.lang包中的任何类型,因为它始终是隐式导入的。

最小的注释处理器可能如下所示:

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

}

此处的 Scanner 扩展ElementScanner7了它基于访问者模式。我们只实现了一些访问者方法并按种类过滤元素,因为并非所有元素实际上都可以包含可导入类型。

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

}

此扫描器返回一组类型作为完全限定的路径。还有一些事情需要考虑和一些事情要实施:

  • 该集合包含java.lang来自同一包的元素和类型
  • 该集合包含泛型,例如java.util.List<String>
  • TypeKind.DECLARED不是唯一一种可导入类型的元素。还要检查TypeKind.ARRAY并获取它的实际声明类型。可以使用代替添加另一个else if(e.asType().getKind() == TypeKind.ARRAY) // ...类来代替TypeKindVisitor7
  • 根据用例,可能会发现更多类型。例如,注释可以包含类作为参数。
  • 对于 Java 1.6,使用各自的ElementScanner6, TypeKindVisitor6etc. 实现。
于 2013-09-13T02:17:17.797 回答
4

看起来没有办法从标准 SDK 类中获取导入语句(至少使用 SDK 5-6-7)。

不过,您可以使用来自 SUN/Oracle 的 tools.jar 中的一些类。

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

通过 maven 获取 Java 工具的 jar,参考这个线程

应该有使用 TreePathScanner 的替代方法(也来自 tools.jar),但我从未触发过 visitImport 方法。

于 2013-09-12T23:45:01.683 回答
1
<dependency>
    <groupId>org.jvnet.sorcerer</groupId>
    <artifactId>sorcerer-javac</artifactId>
    <version>0.8</version>
</dependency>

使用这个依赖解决了这个问题,但不幸的是这次它支持到 Java 1.7,你不能正确编译 Java 1.8 源代码。我的解决方案有点 hack,但它可以在不使用此依赖项和 Java 1.8 源的情况下工作

public final class SorcererJavacUtils {
private static final Pattern IMPORT = Pattern.compile("import\\s+(?<path>[\\w\\\\.]+\\*?)\\s*;");

// com.sun.tools.javac.model.JavacElements
public static Set<String> getImports(Element element, ProcessingEnvironment processingEnv) {
    Elements elements = processingEnv.getElementUtils();
    Class<?> cls = elements.getClass();

    try {
        Method getTreeAndTopLevel = cls.getDeclaredMethod("getTreeAndTopLevel", Element.class);
        getTreeAndTopLevel.setAccessible(true);
        // Pair<JCTree, JCCompilationUnit>
        Object treeTop = getTreeAndTopLevel.invoke(elements, element);

        if (treeTop == null)
            return Collections.emptySet();

        // JCCompilationUnit
        Object toplevel = getFieldValue("snd", treeTop);

        return SorcererJavacUtils.<List<Object>>getFieldValue("defs", toplevel).stream()
                                                                               .map(Object::toString)
                                                                               .map(IMPORT::matcher)
                                                                               .filter(Matcher::find)
                                                                               .map(matcher -> matcher.group("path"))
                                                                               .collect(Collectors.toSet());
    } catch(Exception ignored) {
        return Collections.emptySet();
    }
}

private static <T> T getFieldValue(String name, Object obj) throws IllegalAccessException, NoSuchFieldException {
    Field field = obj.getClass().getDeclaredField(name);
    field.setAccessible(true);
    return (T)field.get(obj);
}

private SorcererJavacUtils() {
}

}

于 2016-05-20T06:08:02.713 回答