Skip to main content

Java annotation processing tool – tips to writing and testing

Writing an annotation processor that’s easy maintainable is hard if you don’t put some effort into readability and testing. This post gives a few tips on readability in addition to recommending a tool for testing annotation processors using the java compiler.

“”

Java annotation processing tool is a tool you can use to process annotations in the source code. All you need is to implement an annotation processor. However, these processors tend to be quite difficult both to understand and to test.

At first I tried to use Mockito to mock the parts of the API needed for my code to work. This ended up in a lot of setting up in the tests and it didn’t strike me as a lean solution. A change of requirements would lead to a lot of change in the test setup. After browsing the Internet I found an interesting post at stackoverflow that had several solutions to write better tests. One solution is to implement the parts of JSR 269 needed for your processors to execute. Since I’m quite lazy I kept searching for a simpler solution. In the end I found an informal post telling me about the tool Compile Testing. This tool utilizes the java compiler with the option to specify the annotation processors to be used when compiling. This is all done using a fluent API that is simple to use and easy to understand. I tried it out on a processor that prevents the possibility to put @Ignore on a unit test without stating the cause for ignoring it. I then wrote five simple examples showing what’s allowed, and what’s not allowed. These examples got one testcase each to verify that the processor behaves as expected.

When I had finished my tests I figured that they where quite easy to read while the annotation processor itself was quite difficult to understand. So I spent som time trying to express my code a little clearer, extracting the ugly bits into a supporting class and rephrasing some of the methods. The api itself can be quite difficult to understand, but I think most of it is now encapsulated in more expressive methods so that it’s quite clear what’s going on.

The examples below are excerptions from the original source code. The full source can be found in GitHub


In these test driven times I figure I should show you the tests first. This testcase is just a chained statement telling the assertion framework that when a given java source file is processed with the IgnoreAnnotationProcessor it should fail to compile. It also asserts that the compiler tells us which method or class that caused the processor to fail.

[java]package com.visma.test.apt;

public class IgnoreAnnotationProcessorTest {

@Test
public void shouldNotCompileIgnoreAnnotatedMethodWithoutValue() {
ASSERT.about(javaSource())
.that(JavaFileObjects.forResource(“com/visma/test/apt/unittest/IgnoreOnMethodWithoutReason.java”))
.processedWith(new IgnoreAnnotationProcessor())
.failsToCompile()
.withErrorContaining(“Cause missing for @Ignore [com.visma.test.apt.unittest.IgnoreOnMethodWithoutReason.someTestMethod()]”);
}
}[/java]


The source the above testcase compiles is just a simple empty test that has an ignored method.

[java]
package com.visma.test.apt.unittest;

import org.junit.Test;
import org.junit.Ignore;

public class IgnoreOnMethodWithoutReason {
@Test
@Ignore
public void someTestMethod() {}
}[/java]


The processor itself is quite small. The hard bits of the code has been put into its own class. What I’m doing here is to loop all the classes and methods that is annotated with @Ignore. If the method or class doesn’t have a cause for being ignored it will print an error message to the provided processing environment.

[java]
package com.visma.test.apt;

@SupportedAnnotationTypes(“org.junit.Ignore”)
public class IgnoreAnnotationProcessor extends AbstractProcessor {

private static final boolean WILL_NOT_CLAIM_ANNOTATIONS = false;

@Override
public boolean process(Set<? extends TypeElement> annotationElements, RoundEnvironment roundEnv) {
for(IgnoreAnnotatedMethodOrClass ignoredMethodOrClass : fromRoundEnvironment(roundEnv)) {
if(!ignoredMethodOrClass.hasCauseDescription()) {
ignoredMethodOrClass.printErrorMessageTo(processingEnv);
}
}
return WILL_NOT_CLAIM_ANNOTATIONS;
}
}[/java]


Then it is the final piece of the puzzle. The IgnoreAnnotatedMethodOrClass has a static method to extract all the ignored elements from the round environment and it encapsulates the functionality to verify that the ignored element describes the cause for being ignored. It also has a method to print an error message to the given processing environment.

[java]package com.visma.test.apt;

class IgnoreAnnotatedMethodOrClass {

private static final String errorMessage = “Cause missing for @Ignore [%1$s]”;
private final Element methodOrClass;

static Set fromRoundEnvironment(RoundEnvironment roundEnv) {
Set ignoredElements = new HashSet();
for (Element methodOrClass : roundEnv.getElementsAnnotatedWith(Ignore.class)) {
ignoredElements.add(new IgnoreAnnotatedMethodOrClass(methodOrClass));
}
return ignoredElements;
}

boolean hasCauseDescription() {
return getCauseForIgnore().isPresent();
}

void printErrorMessageTo(ProcessingEnvironment processingEnv) {
String elementName = toString();
processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, format(errorMessage, elementName));
}
}[/java]

Most popular

  • ""

    What is an IT Security Policy?

    Every organisation—from startups to large, global corporations and nonprofits—must make sure that they have procedures to keep up with an ever-changing landscape of threats and vulnerabilities to keep its assets secure. But what is an IT Security Policy, and how do you enforce them?

  • ""

    Turning the UEFA Euro into math

    The Finnish company Weoptit, a company in Visma, has turned the UEFA Euro tournament into math and simulations. Based on a model originally built by their analysts prior to the World Cup 2006, they have played out the tournament 1,000 000 times to find out what results each team can expect from this summer’s football festival.