我正在编写一个应该生成 java 代码的 AnnotationProcessor。它应该从某些现有接口生成派生接口。
为此,我需要找到原始输入代码的导入语句,以便将其输出到生成的 java 文件中。
如何才能做到这一点?
我正在编写一个应该生成 java 代码的 AnnotationProcessor。它应该从某些现有接口生成派生接口。
为此,我需要找到原始输入代码的导入语句,以便将其输出到生成的 java 文件中。
如何才能做到这一点?
您无法使用注释处理器获取导入语句。但是,您可以获得的是该类使用的类型,这甚至更好。
源代码中的导入语句不足以分析类中使用了哪些类型,因为并非所有使用的类型都有导入语句。如果你真的只需要实际的语句,你可以直接阅读源文件。
如果你只看陈述,会有一些问题:
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
ElementScanner6
, TypeKindVisitor6
etc. 实现。看起来没有办法从标准 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 方法。
<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() {
}
}