Java8, how discover the class and method name in visitMethodInvocation?

后端 未结 2 1562
予麋鹿
予麋鹿 2020-12-15 14:30

With Java7 and Java8, I would like to generate a warning if some methods was called. The warning will be print if a specific jar is present when then user compile.

I

相关标签:
2条回答
  • 2020-12-15 14:56

    As an alternative to the great answer from @emory, you can consider using the pluggable type-checking annotation processing provided by the Checker Framework. The advantage is it can help you to easily determinate the type of the method invoker. Here is an example processor based on the checker framework (add checker.jar to the classpath when compile).

    @SupportedAnnotationTypes("*")
    @SupportedSourceVersion(SourceVersion.RELEASE_8)
    public class MyTypeProcessor extends AbstractTypeProcessor {
        class MyTreePathScanner extends TreePathScanner<Void, Void> {
            private final Trees trees;
            private final TreePath root;
    
            public MyTreePathScanner(TreePath root) {
                this.trees = Trees.instance(processingEnv);
                this.root = root;
            }
    
            @Override
            public Void visitMemberSelect(MemberSelectTree node, Void aVoid) {
                ExpressionTree expression = node.getExpression();
                TreePath expr = TreePath.getPath(root, expression);
                TypeMirror type = trees.getTypeMirror(expr);
                Element typeElement = processingEnv.getTypeUtils().asElement(type);
                Optional<? extends Element> invoker = typeElement.getEnclosedElements().stream().filter(
                        e -> e.getSimpleName().equals(node.getIdentifier())).findFirst();
                if (invoker.isPresent() && invoker.get().getKind() == ElementKind.METHOD) {
                    System.out.println("Type: " + typeElement + ", method: " + invoker.get());
                }
                return super.visitMemberSelect(node, aVoid);
            }
    
        }
    
        @Override
        public void typeProcess(TypeElement typeElement, TreePath root) {
            new MyTreePathScanner(root).scan(root, null);
        }
    }
    

    Which is processing the following input source.

    public class Test {
    
        public void foo() {
        }
    
        public static void main(String[] args) {
            System.out.println("Hello world!");
            Test t = new Test();
            t.foo();
        }
    }
    

    Here is the output:

    Type: java.io.PrintStream, method: println()
    Type: Test, method: foo()
    
    0 讨论(0)
  • 2020-12-15 15:17

    You can do something like:

    package mystuff;
    
    import com.sun.source.tree.*;
    import com.sun.source.util.*;
    import java.util.*;
    import javax.annotation.processing.*;
    import javax.lang.model.element.*;
    import javax.tools.*;
    
    @SupportedAnnotationTypes("*")
        public class Proc extends AbstractProcessor{
        @Override
            public boolean process(Set<?extends TypeElement>annotations,RoundEnvironment roundEnvironment){
            final Trees trees=Trees.instance(processingEnv);
            for(Element element:roundEnvironment.getRootElements()){
            TreePath path=trees.getPath(element);
            final CompilationUnitTree compilationUnit=path.getCompilationUnit();
            compilationUnit.accept(new TreeScanner<Object,Object>(){
                @Override
                    public Object visitMethodInvocation(MethodInvocationTree tree,Object data){
                    tree.getMethodSelect().accept(new SimpleTreeVisitor<Object,Object>(){
                        @Override
                        public Object visitMemberSelect(MemberSelectTree tree,Object data){
                        processingEnv.getMessager().printMessage(Diagnostic.Kind.NOTE,String.format("class:  %1$s\nmethod: %2$s",tree.getExpression(),tree.getIdentifier()));
                        return null;
                        }
                    },null);
                    return null;
                }
                },null);
            }
            return true;
        }
        }
    

    I used that processor to process the below class

    package stuff;
    
    import java.util.*;
    
    @MyAnnotation
    class MyProgram{
        public void run(){
        System.out.println("Hello World!");
        }
    }
    

    and achieved this result:

    class:  System.out
      method: println
    

    I am pretty sure that the method name generated is what you are looking for. I am pretty sure that the "class" is not exactly what you are looking for, but is a pretty good start.

    In my example you probably wanted it to print "java.io.PrintStream" for the class. To get that you could use processingEnv.getElementUtils().getTypeElement("java.lang.System") to get a TypeElement representing the system class. Then you can use processingEnv.getElementUtils().getAllMembers() to get every single member of the system class. Iterate through that to find out. Use the asType method to get its type.

    The preceding paragraph was a gross simplification. The processor did not know a priori that out is a static member of a class that is part of the implicitly imported java.lang package. So your code will have to try and fail to find the following classes System and java.util.System (because it is in the imports), System.out, java.util.System.out, and java.lang.System.out.

    I only dealt with MemberSelect. You will have to deal with other possibilities including MethodInvocation. For example new Object().toString().hashCode() should be class=Object, method=hashCode.

    0 讨论(0)
提交回复
热议问题