Search code examples
javaannotationsannotation-processing

Custom Annotation Processor - Detect Method with Annotations


I am trying to write an annotation Procssor to detect the methods that are annotated with the @PrintMethod annotation. For example in the test Class below, i want to print the codes within the test Method. Is there a way to do it?

From the AnnotationProcessor class stated below, i am only able get the method name but not the details of the method.

Test Class

public class test {

    public static void main(String[] args) {
        System.out.println("Args");
    }

    @PrintMethod
    private boolean testMethod(String input) {
        if(input!=null) {  
            return true;
        }
        return false; 
    }
}

Annotation Processor Class

public class AnnotationProcessor extends AbstractProcessor {
//......
    @Override
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
        //retrieve test Anntoation
        Set<? extends Element> ann =roundEnv.getElementsAnnotatedWith(PrintMethod.class);

        //Print the Method Name
        for(Element e: ann) {
            String msg="Element ee :"+ee.getSimpleName().toString();
            processingEnv.getMessager().printMessage( javax.tools.Diagnostic.Kind.ERROR, msg, e);
        }
    }
}

Solution

  • I was curious about this too so I decided to try and figure it out. Turns out to be easier than I expected. All you need to do is leverage the Trees api out of the proprietary tools.jar library. I've made a quick annotation processor along these lines here: https://github.com/johncarl81/printMethod

    Here's the meat of it:

    @SupportedSourceVersion(SourceVersion.RELEASE_6)
    @SupportedAnnotationTypes("org.printMethod.PrintMethod")
    public class PrintMethodAnnotationProcessor extends AbstractProcessor {
    
        private Trees trees;
    
        @Override
        public synchronized void init(ProcessingEnvironment processingEnv) {
            super.init(processingEnv);
            trees = Trees.instance(processingEnv); //initialize the Trees api.
        }
    
        @Override
        public boolean process(Set<? extends TypeElement> typeElements, RoundEnvironment roundEnvironment) {
    
            MethodPrintScanner visitor = new MethodPrintScanner();
    
            for (Element e : roundEnvironment.getElementsAnnotatedWith(PrintMethod.class)) {
                TreePath tp = trees.getPath(e);
                // visit the annotated methods
                visitor.scan(tp, trees);
            }
            return true;
        }
    
        @Override
        public SourceVersion getSupportedSourceVersion() {
            return SourceVersion.latestSupported();
        }
    }
    

    And the MethodPrintScanner:

    public class MethodPrintScanner extends TreePathScanner {
    
        @Override
        public Object visitMethod(MethodTree methodTree, Object o) {
            System.out.println(methodTree);
            return null;
        }
    }
    

    You can see that we are able to visit the TreePath associated with the given annotated Element. For each method, we simply println() the methodTree which gives us the contents of the method.

    Using your example, here's the output of the program during compilation:

    @PrintMethod()
    private boolean testMethod(String input) {
        if (input != null) {
            return true;
        }
        return false;
    }