fsteeg.com | notes | tags

∞ /notes/java-annotation-processing-in-eclipse | 2017-12-30 | eclipse programming

Java annotation processing in Eclipse

Java annotations provide metadata for Java code. Many developers use annotations provided by standard Java APIs (like @Override) or by frameworks (like @Test). Developers can define their own annotations and process them at runtime using reflection. Additionally, Java provides APIs for writing custom annotation processors that can process these annotations at compile time.

Eclipse provides support for hooking these processors into the compilation process. So when you edit code with these annotations, the processor can analyse the source files, do stuff, and report information back to you.

To give you an idea about how that works, I’ll use Contracts for Java as an example, a tool that implements a contract model similar to that of Eiffel in Java, based on annotations. For our setup, we’ll need Eclipse 4.7 (Oxygen, the 2017 release) or later.

To use Contracts for Java, create a Java project, and add the latest release to the build path:

Java project

Then add some code that uses contract annotations (copy and paste the code below into the src folder of your project):

import com.google.java.contract.Ensures;
import com.google.java.contract.Requires;
 
public class Contracts {
    public static void main(String[] args) {
        System.out.println(new Numbers().add(-10, 5));
    }
}
 
class Numbers {
    @Requires({ "c > 0", "b > 0" })
    @Ensures({ "result > a", "result > b" })
    int add(int a, int b) {
        return a - b;
    }
}

At this point, the project should not report any issues. Normal compilation of the Java source file works just fine. Our processor is not enabled for our annotations yet. To configure the annotation processing we go to Project > Properties > Java Compiler > Annotation Processing.

Select Enable project specific settings and add the following options that we want to pass to the annotation processor: set com.google.java.contract.classpath to %classpath% and com.google.java.contract.classoutput to %PROJECT.DIR%/.apt_generated (the location of your project’s generated source directory):

Project > Properties > Java Compiler > Annotation Processing

These properties are used by the processor to compile the annotated files, and to create output files. The %classpath% placeholder is replaced with your Java project’s build path by Eclipse, so that the annotation processor can access all the libraries used in your project when compiling your code. The %PROJECT.DIR% placeholder is replaced with the path to your Java project.

Finally we add the processor Jar to Project > Properties > Java Compiler > Annotation Processing > Factory Path:

Project > Properties > Java Compiler > Annotation Processing > Factory Path

After confirming these changes, and running a full build, compilation issues in the annotations are now reported in the editor. Our code contains a precondition mentioning a variable called c. But the annotated method has no c parameter. This is reported as an error in the editor:

Java editor

After we fix the first precondition to a > 0, the code compiles, and we can run it (Run > Run as > Java application).

To see the files generated by the processor in the .apt_generated directory in Eclipse, you should disable filtering of .*resources in the Package Explorer’s view menu (the little triangle > Filters…):

View menu

Also make sure you have set up your workspace to refresh automatically in Preferences > General > Workspace > Refresh using native hooks or polling:

Preferences > General > Workspace

Besides the annotation processing at compile time, Contracts for Java also uses bytecode instrumentation for its runtime checks. To have the contracts checked at runtime, add -javaagent:cofoja.contracts.asm-1.3-20160207.jar to the VM arguments of your run configuration (go to Run > Run Configurations, activate the Arguments tab):

Run configuration

Now, when running, we are made aware of the violated precondition in our code, since we are passing -10 as a, which is not larger than 0:

Precondition error

After we fix the call violating the precondition to new Numbers().add(10, 5); we now see that our implementation of add does not fulfill the postcondition, since the result is not larger than a:

Postcondition error

After fixing our implementation to return a + b, not a - b, all contracts are fulfilled and the code now runs without errors.

Contracts for Java uses annotations in an interesting way and shows what can be achieved with annotation processing as a tool, and how Java annotation processors can integrate with Eclipse.

If you’re interested in creating your own processors, check out my test projects for bug 3412981 to get a basic setup where an annotation processor compiles the annotated Java source file.

For more information on these tools check out the Contracts for Java and JDT-APT project sites.


1 That bug came up in the comments of a previous post about using Contracts for Java in Eclipse.