How to add new AST tree node for JavacTask when task event kind is ENTER

问题内容:

Java8 provides a powerful functionality: Java Plugin. It allowes developer to change the target byte code very fast during compilation

Please see: http://www.baeldung.com/java-build-compiler-plugin

(BTW, In the past, another classic solution is to develop a maven plugin to change the bytecode by some frmeworks such as ‘http://asm.ow2.org/‘ after bytecode is generated, like https://github.com/electronicarts/ea-async, but this is a slow & heavy way, and it’s not friendly for IDE too. That’s why I research this new way)

I created three projects:

  1. API-library project

Declare an annotation:

package testpkg.api;

import java.lang.annotation.*;

@Documented
@Retention(RetentionPolicy.SOURCE)
@Target(ElementType.TYPE)
public @interface MyAnnontation {}
  1. Javac Compiler-Plugin project

2.1 Java code

package testpkg.compiler;

import com.sun.source.tree.AnnotationTree;
import com.sun.source.tree.ClassTree;
import com.sun.source.util.*;
import com.sun.source.util.TreeScanner;
import com.sun.tools.javac.api.BasicJavacTask;
import com.sun.tools.javac.code.*;
import com.sun.tools.javac.tree.*;
import com.sun.tools.javac.util.List;
import com.sun.tools.javac.util.Names;

public class MyPlugin implements Plugin {

    private static final boolean AFTER_IMPORT_RESOLVED = false;

    @Override
    public String getName() {
        return MyPlugin.class.getSimpleName();
    }

    @Override
    public void init(JavacTask javacTask, String... strings) {
        javacTask.addTaskListener(new TaskListener() {
            @Override
            public void started(TaskEvent taskEvent) {
            }

            @Override
            public void finished(TaskEvent taskEvent) {
                TaskEvent.Kind kind = AFTER_IMPORT_RESOLVED ? TaskEvent.Kind.ENTER : TaskEvent.Kind.PARSE;
                if (taskEvent.getKind() == kind) {
                    taskEvent.getCompilationUnit().accept(new ScannerImpl(javacTask), null);
                }
            }
        });
    }

    private static final AnnotationTree annotationTree(
            String annotationTypeName,
            java.util.List<? extends AnnotationTree> annotationTrees) {
        if (annotationTrees == null || annotationTrees.isEmpty()) {
            return null;
        }
        for (AnnotationTree annotationTree : annotationTrees) {
            Type type = ((JCTree.JCAnnotation) ((JCTree.JCAnnotation)annotationTree)).type;
            if (type != null) { // Beautiful style: check full name of annotation when AFTER_IMPORT_RESOLVED is true
                if (type.tsym.getQualifiedName().contentEquals(annotationTypeName)) {
                    return annotationTree;
                }
            } else { // Ugly style: check simple name or full name of annotation when AFTER_IMPORT_RESOLVED is true
                if (((JCTree.JCIdent) ((JCTree.JCAnnotation) annotationTree).annotationType).name.contentEquals(annotationTypeName)) {
                    return annotationTree;
                }
            }
        }
        return null;
    }

    private static class ScannerImpl extends TreeScanner<Void, Void> {

        private JavacTask javacTask;

        private JCTree.Factory factory;

        private Names names;

        ScannerImpl(JavacTask javacTask) {
            this.javacTask = javacTask;
            this.factory = TreeMaker.instance(((BasicJavacTask)javacTask).getContext());
            this.names = Names.instance(((BasicJavacTask)javacTask).getContext());
        }

        @Override
        public Void visitClass(ClassTree classTree, Void v) {
            JCTree.JCClassDecl classDecl = (JCTree.JCClassDecl)classTree;
            AnnotationTree annotationTree = annotationTree(
                    "testpkg.api.MyAnnotation",
                    classTree.getModifiers().getAnnotations()
            );
            if (annotationTree == null) { // For ugly style(AFTER_IMPORT_RESOLVED is false), find the annotation again by simple name
                annotationTree = annotationTree(
                        "MyAnnotation",
                        classTree.getModifiers().getAnnotations()
                );
            }
            if (annotationTree != null) {
                JCTree.JCVariableDecl newFieldDecl = factory.VarDef(
                        factory.Modifiers(Flags.PRIVATE, List.nil()),
                        names.fromString("{newField}"), // This field name contains '{' and '}' so that no other user fields can be same with it.
                        factory.TypeIdent(TypeTag.BOOLEAN),
                        factory.Literal(TypeTag.BOOLEAN, 0)
                );
                /*
                 * My question:
                 * When AFTER_IMPORT_RESOLVED is true, the javac compiler will be terminated with error later,
                 * It looks like newFieldDecl.type and newFieldDecl.sym are required but I don't know how to specify them
                 */
                classDecl.defs = classDecl.defs.prepend(newFieldDecl);
            }
            return super.visitClass(classTree, v);
        }
    }
}

2.2 Resource code
Create resource file: src/main/resources/META-INF/com.sun.source.util.Plugin, its content has only one line:

testpkg.compiler.MyPlugin

3.3 Test code

3.1 Java code

package testpkg.example;

import testpkg.api.MyAnnotation;

@MyAnnotation
public class MyClass {}

3.2 Maven configuration

<plugin>
    <artifactId>maven-compiler-plugin</artifactId>
    <configuration>
        <source>1.8</source>
        <target>1.8</target>
        <forceJavacCompilerUse>true</forceJavacCompilerUse>
        <compilerArgument>-Xplugin:MyPlugin</compilerArgument><!--Add java compiler plugin in javac command-->
    </configuration>
    <dependencies>
        <dependency>
            <groupId>${project.groupId}</groupId>
            <artifactId>testprj-compiler</artifactId><!--Add java compiler plugin jar into javac class path-->
            <version>${project.version}</version>
        </dependency>
    </dependencies>
</plugin>

When MyPlugin.AFTER_IMPORT_RESOLVED is false, these third project works, the new field “{newField}” is added into MyClass.class and it can be seen by de-compiler tools, but the plugin has to check the annotation by both qualified name and simple name(or handle the importing statements manually), it’s too ugly

I want to change MyPlugin.AFTER_IMPORT_RESOLVED to be true, after the importing statements have been handled by javac, the plugin can check the annotation by its qualified name quaintly. Unfortunately, this way does not work, the new AST tree node requires some more information but I don’t know how to specify that information.

Any idea? Thanks

问题评论:

原文地址:

https://stackoverflow.com/questions/47756575/how-to-add-new-ast-tree-node-for-javactask-when-task-event-kind-is-enter

Tags:,

添加评论