0

我正在使用 jdk.compiler 模块和启用了预览功能的 Java 15 编写 Java 编译器插件。根据 Oracle 的说法,Java 9 中引入的 jdk.compiler 模块包含我们过去在 java 8 的 tools.jar 中拥有的所有 API,除了 com.sun.tools.javac.tree 包。我想要做的是找到所有用我的注释注释的类并获取 value() 属性,在这种情况下为 Class<?> 类型。

插件代码:

public class ExtensionMethodPlugin implements Plugin{
  @Override
  public String getName() {
    return "ExtensionMethodPlugin";
  }

  @Override
  public void init(JavacTask task, String... args) {
    task.addTaskListener(new TaskListener() {
      @Override
      public void started(TaskEvent event) {

      }

      @Override
      public void finished(TaskEvent event) {
        if (event.getKind() != TaskEvent.Kind.PARSE) {
          return;
        }

        event.getCompilationUnit().accept(new TreeScanner<Void, Void>() {
          @Override
          public Void visitClass(ClassTree node, Void aVoid) {
            var trees = Trees.instance(task);
            var targetClass = InstrumentationUtils.findExtensionTargetForClass(node, trees, event.getCompilationUnit());
            if (targetClass.isEmpty()) {
              return super.visitClass(node, aVoid);
            }

            var methods = node
              .getMembers()
              .stream()
              .filter(e -> e instanceof MethodTree)
              .map(e -> (MethodTree) e)
              .filter(tree -> tree.getReturnType() != null)
              .filter(e -> InstrumentationUtils.shouldInstrumentMethod(e, targetClass.get()))
              .collect(Collectors.toList());

            throw new RuntimeException("Not implemented");
          }
        }, null);
      }
    });
  }
}

InstrumentationUtils 的代码:

@UtilityClass
public class InstrumentationUtils {
  public Optional<Class<?>> findExtensionTargetForClass(ClassTree node, Trees trees, CompilationUnitTree tree) {
    return node
      .getModifiers()
      .getAnnotations()
      .stream()
      .filter(a -> Extension.class.getSimpleName().equals(a.getAnnotationType().toString()))
      .findFirst()
      .map(e -> InstrumentationUtils.findConstantTargetClassInAnnotationTree(e, trees, tree));
  }

  private Class<?> findConstantTargetClassInAnnotationTree(AnnotationTree tree, Trees trees, CompilationUnitTree compilationUnitTree) {
    return tree
      .getArguments()
      .stream()
      .filter(entry -> entry.getKind() == Tree.Kind.ASSIGNMENT)
      .findFirst()
      .map(e -> (AssignmentTree) e)
      .map(e -> classForName(e, trees, compilationUnitTree))
      .orElseThrow(() -> new RuntimeException("Cannot compile: Missing constant class target in annotation!"));
  }

  private Class<?> classForName(AssignmentTree tree, Trees trees, CompilationUnitTree compilationUnitTree){
    //Don't know how to move from here
  }
}

注释的代码:

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.SOURCE)
public @interface Extension {
  Class<?> value();
}

我发现的唯一解决方案涉及使用 JCTree 类,但正如我之前所说,这些不再可用。我知道我可以很容易地强制在 jdk.compiler 模块中导出该包,但我更愿意找到更好的方法。我尝试使用 TreeScanner,但这无济于事:

 tree.accept(new TreeScanner<Class<?>, Void>() {
      // b.class
      @Override
      public Class<?> visitAssignment(AssignmentTree node, Void unused) {
        return node.getExpression().accept(new TreeScanner<Class<?>, Void>(){
          //.class
          @Override
          public Class<?> visitMemberSelect(MemberSelectTree node, Void unused) {
            return node.getExpression().accept(new TreeScanner<Class<?>, Void>(){
              // class ???
              @Override
              public Class<?> visitIdentifier(IdentifierTree node, Void unused) {
                trees.printMessage(Diagnostic.Kind.WARNING, node.getName(),  tree, compilationUnitTree);
                return super.visitIdentifier(node, unused);
              }
            }, null);
          }
        }, null);
      }
    }, null);

提前致谢!

4

1 回答 1

3

当您只需要“找到所有使用我的注解注解的类并获取 value() 属性”时,您就根本不需要jdk.compiler模块中的树 API,注解处理器 API 包含在该java.compiler模块足够且易于处理。

这是一个独立的示例:

static final String EXAMPLE_CODE =
    "import java.lang.annotation.*;\n" +
    "\n" +
    "@Target({ElementType.TYPE})\n" +
    "@Retention(RetentionPolicy.SOURCE)\n" +
    "@interface Extension {\n" +
    "  Class<?> value();\n" +
    "}\n" +
    "\n" +
    "@Extension(Runnable.class)\n" +
    "public class SomeClass {\n" +
    "    public static void aMethod() {\n" +
    "    }\n" +
    "\n" +
    "    @Extension(SomeClass.class)\n" +
    "    class NestedClassToMakeItMoreExciting {\n" +
    "        \n" +
    "    }\n" +
    "}";

public static void main(String[] args) throws IOException {
    JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();

    StandardJavaFileManager fileManager=compiler.getStandardFileManager(null,null,null);
    Path tmp = Files.createTempDirectory("compile-test-");
    fileManager.setLocation(StandardLocation.CLASS_OUTPUT, Set.of(tmp.toFile()));

    Set<JavaFileObject> srcObj = Set.of(new SimpleJavaFileObject(
        URI.create("string:///SomeClass.java"), JavaFileObject.Kind.SOURCE) {
            @Override
            public CharSequence getCharContent(boolean ignoreEncodingErrors) {
                return EXAMPLE_CODE;
            }
        });

    JavaCompiler.CompilationTask task
        = compiler.getTask(null, fileManager, null, null, null, srcObj);
    task.setProcessors(Set.of(new MyProcessor()));

    task.call();
}

@SupportedAnnotationTypes("Extension")
@SupportedSourceVersion(SourceVersion.RELEASE_14)
static class MyProcessor extends AbstractProcessor {
    @Override
    public boolean process(
        Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {

        TypeElement ext = processingEnv.getElementUtils().getTypeElement("Extension");

        for(Element annotatedElement: roundEnv.getElementsAnnotatedWith(ext)) {
            System.out.println(annotatedElement);

            for(AnnotationMirror am: annotatedElement.getAnnotationMirrors()) {
                if(am.getAnnotationType().asElement() == ext) {
                    am.getElementValues().forEach((e,v) ->
                        System.out.println("\t"+e.getSimpleName()+" = "+v.getValue()));
                }
            }
        }
        return true;
    }
}

这打印

SomeClass
    value = java.lang.Runnable
SomeClass.NestedClassToMakeItMoreExciting
    value = SomeClass

当然,此功能可以jdk.compiler在需要时与扩展程序中的功能相结合。

于 2020-12-16T17:10:12.470 回答