7

This is the first time I am writing a Annotation Processor and I want to invoke it programmatically. Is it possible?

I have written small code for processor:

@SupportedAnnotationTypes({"app.dev.ems.support.annotation.HBMModel"})
public class HBMModelProcessor extends AbstractProcessor {

    @Override
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
        Set<? extends Element> elements = roundEnv.getElementsAnnotatedWith(HBMModel.class);
        System.out.println(elements);
        return true;
    }

}

Now If I want to invoke the process method, then how can I do this? Can I do it in following way:

HBMModelProcessor modelProcessor = new HBMModelProcessor();
modelProcessor.process(annotations, roundEnv)

Any information will be very helpful to me.

Thanks.

Tapas Bose
  • 28,796
  • 74
  • 215
  • 331

3 Answers3

4

You can call the Java compiler with annotation processors programmatically, inside the same process, like this:

import com.sun.tools.javac.processing.PrintingProcessor;
import fi.jumi.actors.generator.JavaSourceFromString;
import org.junit.*;
import org.junit.rules.TemporaryFolder;

import javax.annotation.processing.Processor;
import javax.tools.*;
import javax.tools.JavaCompiler.CompilationTask;
import java.io.IOException;
import java.util.Arrays;

import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.is;

public class ExampleTest {

    @Rule
    public final TemporaryFolder tempDir = new TemporaryFolder();

    @Test
    public void example() throws IOException {
        JavaFileObject src = new JavaSourceFromString(
                "com.example.GuineaPig",
                "package com.example;\n" +
                "public interface GuineaPig {\n" +
                "    void foo();\n" +
                "}"
        );
        compile(new PrintingProcessor(), src);
    }

    private void compile(Processor processor, JavaFileObject... compilationUnits) throws IOException {
        JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
        DiagnosticCollector<JavaFileObject> diagnostics = new DiagnosticCollector<JavaFileObject>();
        StandardJavaFileManager fileManager = compiler.getStandardFileManager(diagnostics, null, null);
        fileManager.setLocation(StandardLocation.CLASS_OUTPUT, Arrays.asList(tempDir.getRoot()));

        CompilationTask task = compiler.getTask(null, fileManager, diagnostics, null, null, Arrays.asList(compilationUnits));
        task.setProcessors(Arrays.asList(
                processor
        ));
        boolean success = task.call();
        for (Diagnostic<? extends JavaFileObject> diagnostic : diagnostics.getDiagnostics()) {
            System.err.println(diagnostic);
        }
        assertThat("compile succeeded", success, is(true));
    }
}

If you remove the call to setProcessors then it will detect annotation processors automatically based on the META-INF/services/javax.annotation.processing.Processor files on classpath.

Esko Luontola
  • 73,184
  • 17
  • 117
  • 128
4

jOOR has an API to simplify access to javax.tools.JavaCompiler as shown in this answer. You can trigger it easily as follows:

Reflect.compile(
    "com.example.MyClass",
    "package com.example; "
  + "@app.dev.ems.support.annotation.HBMModel "
  + "class MyClass {}",
    new CompileOptions().processors(new HBMModelProcessor())
);

This is specifically useful for unit testing annotation processors. See also this blog post here: https://blog.jooq.org/2018/12/07/how-to-unit-test-your-annotation-processor-using-joor

Disclaimer, I work for the company behind jOOR.

Lukas Eder
  • 211,314
  • 129
  • 689
  • 1,509
3

This is a link to my answer to a similar question.

You could do annotation processing the way you suggest in your question, but you would somehow have to produce the annotations and roundEnv.

The intended use of annotation processing is during compilation. I recommend a two step compilation process.

  1. Compile your annotation processor and related files in the usual way.
  2. compile the other files (using a compiler that supports annotation processing). You may have to some arguments to the compiler: the processor path, the class name of the processor, etc.

The compiler will produce the annotations and roundEnv variables and an instance of your processor. (Most compilers require that your processor be public and have a public constructor.) The compiler will then invoke the process method.

Community
  • 1
  • 1
emory
  • 10,725
  • 2
  • 30
  • 58
  • Thanks for reply. So there is no way to get these two variable `annotations` and `roundEnv`? – Tapas Bose May 12 '12 at 18:07
  • Of course there is a way to get `annotations` and `roundEnv`. `annotations = new HashSet(); annotations . add ( /* your annotations */) ;` and `roundEnv = new RoundEnvironment ( ) { /* implementation */ }` Are you trying to unit-test your processor? You could probably use a mocking framework. – emory May 12 '12 at 19:31
  • @emory, I wanna unit test an annotation processor. And a sample would be welcome as this is the first google entry. – Snicolas Sep 07 '13 at 08:23
  • @Snicolas I created an ideone that should help get you started - http://ideone.com/7HhgzI – emory Sep 07 '13 at 13:45
  • 1
    I managed it finally. I used this as Inspiration source : https://today.java.net/pub/a/today/2008/04/10/source-code-analysis-using-java-6-compiler-apis.html#invoking-the-compiler-from-code-the-java-compiler-api. I got a sample to unit test a processor [here](https://github.com/stephanenicolas/boundbox). The library is tested using this techique. – Snicolas Sep 08 '13 at 08:20