java - 如何解析 Java 注释处理器中的 SwitchTree 表达式类型?

标签 java javac java-bytecode-asm annotation-processing jvm-bytecode

让我们考虑以下代码:

switch ( <em>switchTreeExpression</em> ) {
    <em>cases</em>
}

我想知道 switchTreeExpression 的类型是什么。

我有以下代码草稿:

...
MethodTree methodTree = trees.getTree(method);
BlockTree blockTree = methodTree.getBody();

for (StatementTree statementTree : blockTree.getStatements()) {
    if (statementTree.getKind() == Tree.Kind.SWITCH) {
        SwitchTree switchTree = (SwitchTree) statementTree;
        ExpressionTree switchTreeExpression = switchTree.getExpression();
        // I need to get the type of *switchTreeExpression* here
    }
}

有趣的是,我可以从 .class 文件中获取 switchTreeExpression 的类型。然而,在注释处理的这个阶段似乎没有办法获取当前类的字节码(如果我错了,我很乐意获取字节码并使用 ObjectWeb ASM 库进行分析).

最佳答案

可能的解决方案

注解处理器

让我们考虑一个用于类型注释的注释处理器 (@Target(ElementType.TYPE))。

限制:Processor.process()方法:没有方法体

Processing Code :

Annotation processing occurs at a specific point in the timeline of a compilation, after all source files and classes specified on the command line have been read, and analyzed for the types and members they contain, but before the contents of any method bodies have been analyzed.

克服限制:使用com.sun.source.util.TaskListener

想法是处理类型元素分析完成事件。

  • Processor.init() 方法:注册一个任务监听器并使用捕获的带注释的类型元素处理类型元素分析完成事件。
  • Processor.process() 方法:捕获带注释的类型元素。

一些相关引用:

关于实现方法的说明

一些第三方依赖项(库和框架)可用于实现注释处理器。

比如前面提到的Checker Framework。

一些相关引用:

请注意,Checker Framework 处理器使用 @SupportedAnnotationTypes("*")

实现草案

让我们考虑一个实现草案,它不使用“实现方法注意事项”部分中提到的第三方依赖项。

注解处理器项目

Maven 项目
<properties>
    <auto-service.version>1.0.1</auto-service.version>
</properties>
<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-compiler-plugin</artifactId>
    <configuration>
        <annotationProcessorPaths>
            <path>
                <groupId>com.google.auto.service</groupId>
                <artifactId>auto-service</artifactId>
                <version>${auto-service.version}</version>
            </path>
        </annotationProcessorPaths>
    </configuration>
</plugin>
<dependency>
    <groupId>com.google.auto.service</groupId>
    <artifactId>auto-service-annotations</artifactId>
    <version>${auto-service.version}</version>
</dependency>
AbstractTypeProcessor 类:基类

让我们介绍具有以下抽象方法的基类:

public abstract void processType(Trees trees, TypeElement typeElement, TreePath treePath);
import com.sun.source.util.JavacTask;
import com.sun.source.util.TaskEvent;
import com.sun.source.util.TaskListener;
import com.sun.source.util.TreePath;
import com.sun.source.util.Trees;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.ProcessingEnvironment;
import javax.annotation.processing.RoundEnvironment;
import javax.lang.model.element.Element;
import javax.lang.model.element.Name;
import javax.lang.model.element.TypeElement;
import javax.lang.model.util.ElementFilter;

// NOTE: It is designed to work only with `@Target(ElementType.TYPE)` annotations!
public abstract class AbstractTypeProcessor extends AbstractProcessor {
    private final AnalyzeTaskListener analyzeTaskListener = new AnalyzeTaskListener(this);
    protected final Set<Name> remainingTypeElementNames = new HashSet<>();
    private Trees trees;

    protected AbstractTypeProcessor() {
    }

    @Override
    public synchronized void init(final ProcessingEnvironment processingEnv) {
        super.init(processingEnv);
        trees = Trees.instance(processingEnv);
        JavacTask.instance(processingEnv).addTaskListener(analyzeTaskListener);
    }

    @Override
    public boolean process(final Set<? extends TypeElement> annotations, final RoundEnvironment roundEnv) {
        for (final TypeElement annotation : annotations) {
            final Set<? extends Element> annotatedElements = roundEnv.getElementsAnnotatedWith(annotation);
            final Set<TypeElement> typeElements = ElementFilter.typesIn(annotatedElements);
            final List<Name> typeElementNames = typeElements.stream()
                .map(TypeElement::getQualifiedName)
                .toList();
            remainingTypeElementNames.addAll(typeElementNames);
        }
        System.out.println(
            String.format("Remaining type element names: %s", remainingTypeElementNames)
        );
        return false;
    }

    public abstract void processType(Trees trees, TypeElement typeElement, TreePath treePath);

    private void handleAnalyzedType(final TypeElement typeElement) {
        System.out.println(
            String.format("Handling analyzed type element: %s", typeElement)
        );
        if (!remainingTypeElementNames.remove(typeElement.getQualifiedName())) {
            return;
        }

        final TreePath treePath = trees.getPath(typeElement);
        processType(trees, typeElement, treePath);
    }

    private static final class AnalyzeTaskListener implements TaskListener {
        private final AbstractTypeProcessor processor;

        public AnalyzeTaskListener(final AbstractTypeProcessor processor) {
            this.processor = processor;
        }

        @Override
        public void finished(final TaskEvent e) {
            if (e.getKind() != TaskEvent.Kind.ANALYZE) {
                return;
            }

            processor.handleAnalyzedType(e.getTypeElement());
        }
    }
}
CheckMethodBodies类:注解类
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.SOURCE)
public @interface CheckMethodBodies {
}
CheckMethodBodiesProcessor 类:注解处理器
import com.google.auto.service.AutoService;
import com.sun.source.tree.BlockTree;
import com.sun.source.tree.CompilationUnitTree;
import com.sun.source.tree.ExpressionTree;
import com.sun.source.tree.MethodTree;
import com.sun.source.tree.StatementTree;
import com.sun.source.tree.SwitchTree;
import com.sun.source.tree.Tree;
import com.sun.source.util.TreePath;
import com.sun.source.util.TreePathScanner;
import com.sun.source.util.Trees;
import javax.annotation.processing.Processor;
import javax.annotation.processing.SupportedAnnotationTypes;
import javax.annotation.processing.SupportedSourceVersion;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.TypeElement;
import javax.lang.model.type.TypeMirror;

@SupportedAnnotationTypes("org.example.annotation.processor.CheckMethodBodies")
@SupportedSourceVersion(SourceVersion.RELEASE_8)
@AutoService(Processor.class)
public final class CheckMethodBodiesProcessor extends AbstractTypeProcessor {
    @Override
    public void processType(final Trees trees, final TypeElement typeElement, final TreePath treePath) {
        final CompilationUnitTree compilationUnitTree = treePath.getCompilationUnit();
        final TestMethodTreePathScanner treePathScanner = new TestMethodTreePathScanner(trees, compilationUnitTree);
        treePathScanner.scan(compilationUnitTree, null);
    }

    private static final class TestMethodTreePathScanner extends TreePathScanner<Void, Void> {
        private final Trees trees;
        private final CompilationUnitTree compilationUnitTree;

        public TestMethodTreePathScanner(
            final Trees trees,
            final CompilationUnitTree compilationUnitTree
        ) {
            this.trees = trees;
            this.compilationUnitTree = compilationUnitTree;
        }

        @Override
        public Void visitMethod(final MethodTree node, final Void unused) {
            System.out.println(
                String.format("Visiting method: %s", node.getName())
            );

            final BlockTree blockTree = node.getBody();
            for (final StatementTree statementTree : blockTree.getStatements()) {
                if (statementTree.getKind() != Tree.Kind.SWITCH) {
                    continue;
                }

                final SwitchTree switchTree = (SwitchTree) statementTree;
                final ExpressionTree switchTreeExpression = switchTree.getExpression();
                System.out.println(
                    String.format("Switch tree expression: %s", switchTreeExpression)
                );

                final TreePath treePath = TreePath.getPath(compilationUnitTree, switchTreeExpression);
                final TypeMirror typeMirror = trees.getTypeMirror(treePath);
                System.out.println(
                    String.format("Tree mirror: %s", typeMirror)
                );
            }
            return null;
        }
    }
}

测试项目

Maven 项目
<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-compiler-plugin</artifactId>
    <configuration>
        <annotationProcessorPaths>
            <path>
                <groupId>org.example</groupId>
                <artifactId>annotation-processor</artifactId>
                <version>1.0.0-SNAPSHOT</version>
            </path>
        </annotationProcessorPaths>
        <showWarnings>true</showWarnings>
    </configuration>
</plugin>

为了能够使用注解类:

<dependency>
    <groupId>org.example</groupId>
    <artifactId>annotation-processor</artifactId>
    <version>1.0.0-SNAPSHOT</version>
</dependency>
Switcher 类:使用注解
import org.example.annotation.processor.CheckMethodBodies;

@CheckMethodBodies
public final class Switcher {
    public void theMethod() {
        final Integer value = 1;
        switch (value.toString() + "0" + "0") {
            case "100":
                System.out.println("Hundred!");
            default:
                System.out.println("Not hundred!");
        }
    }
}

测试

执行注释处理器项目的命令:

mvn clean install

执行测试项目的命令:

mvn clean compile

观察注解处理器的输出:

Remaining type element names: [org.example.annotation.processor.test.Switcher]
Remaining type element names: [org.example.annotation.processor.test.Switcher]
Handling analyzed type element: org.example.annotation.processor.test.Switcher
Visiting method: <init>
Visiting method: theMethod
Switch tree expression: (value.toString() + "00")
Tree mirror: java.lang.String

独立程序

可以在独立程序中使用 javac 功能。

看来还是需要先获取树路径再获取类型镜像:

final CompilationUnitTree compilationUnitTree = <…>;
final ExpressionTree switchTreeExpression = <…>;

final TreePath treePath = TreePath.getPath(compilationUnitTree, switchTreeExpression);
final TypeMirror typeMirror = trees.getTypeMirror(treePath);

文档摘录:TypeMirror (Java Platform SE 8 ) :

public interface TypeMirror extends AnnotatedConstruct

Represents a type in the Java programming language. Types include primitive types, declared types (class and interface types), array types, type variables, and the null type. Also represented are wildcard type arguments, the signature and return types of executables, and pseudo-types corresponding to packages and to the keyword void.

实现草案

输入文件:Switcher

public final class Switcher {
    public void theMethod() {
        final Integer value = 1;
        switch (value.toString() + "0" + "0") {
            case "100":
                System.out.println("Hundred!");
            default:
                System.out.println("Not hundred!");
        }
    }
}

程序

请将 "/path/to/Switcher.java" 文件路径值替换为实际文件路径值。

import com.sun.source.tree.BlockTree;
import com.sun.source.tree.CompilationUnitTree;
import com.sun.source.tree.ExpressionTree;
import com.sun.source.tree.MethodTree;
import com.sun.source.tree.StatementTree;
import com.sun.source.tree.SwitchTree;
import com.sun.source.tree.Tree;
import com.sun.source.util.JavacTask;
import com.sun.source.util.TreePath;
import com.sun.source.util.TreePathScanner;
import com.sun.source.util.Trees;
import java.io.IOException;
import java.net.URI;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;
import javax.lang.model.type.TypeMirror;
import javax.tools.JavaCompiler;
import javax.tools.JavaFileObject;
import javax.tools.SimpleJavaFileObject;
import javax.tools.ToolProvider;

public final class Program {
    public static void main(final String[] args) throws IOException {
        final JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
        final JavacTask task = (JavacTask) compiler.getTask(
            null,
            null,
            null,
            null,
            null,
            List.of(new TestFileObject())
        );
        final Iterable<? extends CompilationUnitTree> compilationUnitTrees = task.parse();
        task.analyze();
        final Trees trees = Trees.instance(task);

        for (final CompilationUnitTree compilationUnitTree : compilationUnitTrees) {
            final TestMethodTreePathScanner treePathScanner = new TestMethodTreePathScanner(trees, compilationUnitTree);
            treePathScanner.scan(compilationUnitTree, null);
        }
    }

    private static final class TestFileObject extends SimpleJavaFileObject {
        public TestFileObject() {
            super(URI.create("myfo:/Switcher.java"), JavaFileObject.Kind.SOURCE);
        }

        @Override
        public CharSequence getCharContent(final boolean ignoreEncodingErrors) throws IOException {
            return Files.readString(
                Path.of("/path/to/Switcher.java"),
                StandardCharsets.UTF_8
            );
        }
    }

    private static final class TestMethodTreePathScanner extends TreePathScanner<Void, Void> {
        private final Trees trees;
        private final CompilationUnitTree compilationUnitTree;

        public TestMethodTreePathScanner(
            final Trees trees,
            final CompilationUnitTree compilationUnitTree
        ) {
            this.trees = trees;
            this.compilationUnitTree = compilationUnitTree;
        }

        @Override
        public Void visitMethod(final MethodTree node, final Void unused) {
            final BlockTree blockTree = node.getBody();
            for (final StatementTree statementTree : blockTree.getStatements()) {
                if (statementTree.getKind() != Tree.Kind.SWITCH) {
                    continue;
                }

                final SwitchTree switchTree = (SwitchTree) statementTree;
                final ExpressionTree switchTreeExpression = switchTree.getExpression();
                System.out.println(
                    String.format("Switch tree expression: %s", switchTreeExpression)
                );

                final TreePath treePath = TreePath.getPath(compilationUnitTree, switchTreeExpression);
                final TypeMirror typeMirror = trees.getTypeMirror(treePath);
                System.out.println(
                    String.format("Tree mirror: %s", typeMirror)
                );
            }
            return null;
        }
    }
}

程序输出:

Switch tree expression: (value.toString() + "00")
Tree mirror: java.lang.String

关于java - 如何解析 Java 注释处理器中的 SwitchTree 表达式类型?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/72765952/

相关文章:

java - 是否可以使用 ByteBuddy 更改注释的值?

java - ASM - 分支目标处的堆栈图帧不一致

java - 通过 Java 中的标准输出更快的输出?

java - 如何从 API 中提取值(value)

Java无法编译运行类

eclipse - Gradle : How to compile Java by eclipse ECJ (JDT core) by running Gradle 4.1 task

java - 使用ASM创建方法

java - 仅单击的项目可见,并隐藏 RecyclerView 中之前的项目

java - JBoss实例可以通信吗?

java - Apache 公共(public)资源 : ClassNotFoundException