Friday, December 25, 2015

Annotation processing during compilation time: Getting Class annotation value

An annotation type could have the following annotation value type:
  • Primitive type
  • String
  • Class
  • Enum
  • Another Annotation
  • An array of any of the above
While this post shows the different ways of getting annotation value during annotation processing at compile time, however, the spotlight is on getting the Class value. In all the following annotation processing code example, they are doing the same thing which to getting all the Tag annotation values specified in Application class.

Annotation type:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.SOURCE)
public @interface Tag {
    
    enum Kind {
        PRICE, STANDARD
    }
    
    String stringValue() default "";
    int intValue() default 0;
    Kind enumValue() default Kind.PRICE;
    Priority annotationTypeValue() default @Priority(0);
    Class classValue() default Void.class;
    Class[] classesValue() default {};
}

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.SOURCE)
public @interface Priority {
    int value();
}

Application class:

@Tag(
    stringValue = "A String Value",
    intValue = 100, 
    enumValue = Kind.STANDARD, 
    annotationTypeValue = @Priority(3), 
    classValue = Application.class, 
    classesValue = {Application.class}
)
public class Application {
    
    private int id;
    
    public void setId(int id) {
        this.id = id;
    }
}

From the Annotation object

This is the simplest and direct way of getting annotation values. The following code example is getting annotation values other than Class.

@AutoService(Processor.class)
@SupportedSourceVersion(SourceVersion.RELEASE_7)
@SupportedAnnotationTypes("com.hauchee.annotation.Tag")
public class TagProcessor extends AbstractProcessor {

    @Override
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
        
        for (Element typeElement : roundEnv.getElementsAnnotatedWith(Tag.class)) {
            System.out.println("From the Annotation object:");
            
            // Get the annotation object from the type element
            Tag tagAnnotation = typeElement.getAnnotation(Tag.class);
            System.out.printf(">> stringValue: %s\n",
                    tagAnnotation.stringValue());
            System.out.printf(">> intValue: %s\n",
                    tagAnnotation.intValue());
            System.out.printf(">> enumValue: %s\n",
                    tagAnnotation.enumValue().toString());
            System.out.printf(">> annotationTypeValue: %s\n",
                    tagAnnotation.annotationTypeValue().toString());
        }
        return true;
    }
}

When we compile Application project where the TagProcessor above is executed, the compilation is successful and we get the following result.

From the Annotation object:
>> stringValue: A String Value
>> intValue: 100
>> enumValue: STANDARD
>> annotationTypeValue: @com.hauchee.annotation.Priority(value=3)
The result is within expectation without suprise. Now, lets try getting Class value.

1
2
System.out.printf(">> classValue: %s\n", tagAnnotation.classValue().getName());
System.out.printf(">> classesValue: %s\n", tagAnnotation.classesValue()[0].getName());

Well, surprisingly the Application project compilation failed with the following exception.

Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:3.3:compile (default-compile) on project Application: Fatal error compiling: javax.lang.model.type.MirroredTypeException: Attempt to access Class object for TypeMirror com.hauchee.application.Application

The MirroredTypeException is thrown at line 1. If line 1 is removed, trying the getting Class array value at line 2 will also hit exception but the different one which is MirroredTypesException. What is wrong with them? We can get the answer from Element.getAnnotation JavaDoc.

The annotation returned by this method could contain an element whose value is of type Class. This value cannot be returned directly: information necessary to locate and load a class (such as the class loader to use) is not available, and the class might not be loadable at all. Attempting to read a Class object by invoking the relevant method on the returned annotation will result in a MirroredTypeException, from which the corresponding TypeMirror may be extracted. Similarly, attempting to read a Class[]-valued element will result in a MirroredTypesException.

Note: This method is unlike others in this and related interfaces. It operates on runtime reflective information — representations of annotation types currently loaded into the VM — rather than on the representations defined by and used throughout these interfaces. Consequently, calling methods on the returned annotation object can throw many of the exceptions that can be thrown when calling methods on an annotation object returned by core reflection. This method is intended for callers that are written to operate on a known, fixed set of annotation types.

Getting a Class value from annotation object directly is not a good move based on the explanation above. In fact, we should get DeclaredType (a subclass of TypeMirror) which represents the Class type or Interface type in annotation processing. The good news is we can get the type mirror corresponding to the type being accessed from MirroredTypesException directly.

try {
    System.out.printf(">> classValue: %s\n", // never print
            tagAnnotation.classValue().getName());
} catch (MirroredTypeException e) {
    TypeMirror typeMirror = e.getTypeMirror();
    System.out.printf(">> classValue(MirroredTypeException): %s\n",
            typeMirror.toString());
}
try {
    System.out.printf(">> classesValue: %s\n", // never print
            tagAnnotation.classesValue()[0].getName());
} catch (MirroredTypesException e) {
    List<? extends TypeMirror> typeMirrors = e.getTypeMirrors();
    System.out.printf(">> classValue(MirroredTypesException): %s\n",
            typeMirrors.get(0).toString());
}

And the result:
>> classValue(MirroredTypeException): com.hauchee.application.Application
>> classValue(MirroredTypesException): com.hauchee.application.Application
Although it works but this is taking the short-cut and not a good coding practise. Moreover, we need to take the risk if the reflection in annotation object is not working due to any reason that we don't know.

From the Annotation element

What we actually process in annotation processor is the Java source. The Java source is modelled into elements. Take the Application class as an example, the corresponding elements are as below:

package com.hauchee.application; // PackageElement

import com.hauchee.annotation.Priority;
import com.hauchee.annotation.Tag;
import com.hauchee.annotation.Tag.Kind;

@Tag( // AnnotationMirror
    // ExecutableElement:AnnotationValue pairs
    stringValue = "A String Value",
    intValue = 100, 
    enumValue = Kind.STANDARD, 
    annotationTypeValue = @Priority(3), 
    classValue = Application.class, 
    classesValue = {Application.class}
)
public class Application { // TypeElement
    
    private int id; // VariableElement
    
    public void setId(int id) { // ExecutableElement
        this.id = id;
    }
}

The element that we are interested in is the AnnotationMirror. Its name easily lead us to think that it is a subclass of TypeMirror. Apparently it is not. While the TypeMirror is about the Type family, the AnnotationMirror is from the Element family.

The annotation element key such as stringValue is a ExecutableElement. This is true indeed. It is a method in annotation type and executable from an annotation object you have seen in the previous approach.

@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {

    for (Element typeElement : roundEnv.getElementsAnnotatedWith(Tag.class)) {
        System.out.println("From the Annotation element:");

        // Get the annotation element from the type element
        List<? extends AnnotationMirror> annotationMirrors = typeElement.getAnnotationMirrors();
        for (AnnotationMirror annotationMirror : annotationMirrors) {
            
            // Get the ExecutableElement:AnnotationValue pairs from the annotation element
            Map<? extends ExecutableElement, ? extends AnnotationValue> elementValues
                    = annotationMirror.getElementValues();
            for (Map.Entry<? extends ExecutableElement, ? extends AnnotationValue> entry
                    : elementValues.entrySet()) {
                String key = entry.getKey().getSimpleName().toString();
                Object value = entry.getValue().getValue();
                switch (key) {
                    case "intValue":
                        int intVal = (Integer) value;
                        System.out.printf(">> intValue: %d\n", intVal);
                        break;
                    case "stringValue":
                        String strVal = (String) value;
                        System.out.printf(">> stringValue: %s\n", strVal);
                        break;
                    case "enumValue":
                        VariableElement enumVal = ((VariableElement) value);
                        System.out.printf(">> enumValue: %s\n", enumVal.getSimpleName());
                        break;
                    case "annotationTypeValue":
                        AnnotationMirror anoTypeVal = (AnnotationMirror) value;
                        System.out.printf(">> annotationTypeValue: %s\n", anoTypeVal.toString());
                        break;
                    case "classValue":
                        TypeMirror typeMirror = (TypeMirror) value;
                        System.out.printf(">> classValue: %s\n", typeMirror.toString());
                        break;
                    case "classesValue":
                        List<? extends AnnotationValue> typeMirrors
                            = (List<? extends AnnotationValue>) value;
                        System.out.printf(">> classesValue: %s\n",
                            ((TypeMirror) typeMirrors.get(0).getValue()).toString());
                        break;
                }
            }
        }
    }
    return true;
}

Result:
From the Annotation element:
>> stringValue: A String Value
>> intValue: 100
>> enumValue: STANDARD
>> annotationTypeValue: @com.hauchee.annotation.Priority(3)
>> classValue: com.hauchee.application.Application
>> classValue: com.hauchee.application.Application
This approach is more reliable compared to the previous one. However, the returned object of the AnnotationValue is an Object. Therefore, as you can see, there are a lot of class castings before we can get the real value we want. Wrong casting will be troublesome.

From the Annotation Value Visitor

The AnnotationValue can accept visitor to process its value object. This means that the annotation value processing can be offloaded from annotation processor. This design makes the codes in a neat way.

@AutoService(Processor.class)
@SupportedSourceVersion(SourceVersion.RELEASE_7)
@SupportedAnnotationTypes("com.hauchee.annotation.Tag")
public class TagProcessor extends AbstractProcessor {

    @Override
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {

        // The annotation value visitor that is able to assess all annotation value
        MyValueVisitor valueVisitor = new MyValueVisitor();
        
        for (Element typeElement : roundEnv.getElementsAnnotatedWith(Tag.class)) {
            System.out.println("From the Annotation Value Visitor:");
    
            // Get the annotation element from the type element
            List<? extends AnnotationMirror> annotationMirrors = typeElement.getAnnotationMirrors();
            for (AnnotationMirror annotationMirror : annotationMirrors) {
                Map<? extends ExecutableElement, ? extends AnnotationValue> elementValues
                        = annotationMirror.getElementValues();
                for (Map.Entry<? extends ExecutableElement, ? extends AnnotationValue> entry
                        : elementValues.entrySet()) {
                    entry.getValue().accept(valueVisitor, null);
                }
            }
        }
        return true;
    }
}

Result:
From the Annotation Value Visitor:
>> stringValue: A String Value
>> intValue: 100
>> enumValue: STANDARD
>> annotationTypeValue: @com.hauchee.annotation.Priority(3)
>> classValue: com.hauchee.application.Application
>> classValue: com.hauchee.application.Application

Below is the annotation value visitor to print all the Tag annotation values. As you can see, no casting is required which eliminate the risk of wrong casting during processing time.

public class MyValueVisitor extends SimpleAnnotationValueVisitor7<Void, Void> {

    @Override
    public Void visitInt(int i, Void p) {
        System.out.printf(">> intValue: %d\n", i);
        return p;
    }

    @Override
    public Void visitString(String s, Void p) {
        System.out.printf(">> stringValue: %s\n", s);
        return p;
    }

    @Override
    public Void visitEnumConstant(VariableElement c, Void p) {
        System.out.printf(">> enumValue: %s\n", c.getSimpleName());
        return p;
    }

    @Override
    public Void visitAnnotation(AnnotationMirror a, Void p) {
        System.out.printf(">> annotationTypeValue: %s\n", a.toString());
        return p;
    }

    @Override
    public Void visitType(TypeMirror t, Void p) {
        System.out.printf(">> classValue: %s\n", t.toString());
        return p;
    }

    @Override
    public Void visitArray(List<? extends AnnotationValue> vals, Void p) {
        for (AnnotationValue val : vals) {
            val.accept(this, p);
        }
        return p;
    }
}

Another important point is that do not implement the AnnotationValueVisitor interface directly. This is because new methods could be added to accommodate new, currently unknown, language structures added to future versions of the Java™ programming language. In order to avoid of source incompatible in the future version, visitor class should extends the appropriate abstract visitor class that implements this interface. The MyValueVisitor class above extends SimpleAnnotationValueVisitor7. The number "7" at the back indicating it complies with RELEASE_7 source version which is same as the SupportedSourceVersion defined in the TagProcessor.


References:
http://hannesdorfmann.com/annotation-processing/annotationprocessing101/
http://blog.retep.org/2009/02/13/getting-class-values-from-annotations-in-an-annotationprocessor/
http://stackoverflow.com/questions/17660469/get-field-class-in-annotations-processor
http://stackoverflow.com/questions/1458535/which-types-can-be-used-for-java-annotation-members
https://docs.oracle.com/javase/7/docs/api/javax/lang/model/type/MirroredTypeException.html
https://docs.oracle.com/javase/7/docs/api/javax/lang/model/element/Element.html
https://docs.oracle.com/javase/7/docs/api/javax/lang/model/element/AnnotationValueVisitor.html
https://docs.oracle.com/javase/7/docs/api/javax/lang/model/util/SimpleAnnotationValueVisitor7.html

Sunday, November 29, 2015

Annotation processing during compilation time: Error Handling

Even though the annotation processing is part of the source code compilation process, the annotation processor just likes any other normal Java program where we could implement the validation logic in order to make sure it processes the annotation properly at compile time. While the annotation processor could throw any runtime exception like what other normal Java programs do, there are other better ways it could handle and deliver the validation error. In this post, I will go through and evaluate different ways of reporting a validation error from an annotation processor.

Given the Java classes with different role below:

Custom annotation type: PrivateField
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.SOURCE)
public @interface PrivateField {
}

Application: Application
public class Application {
    
    @PrivateField
    private String id;
    
    @PrivateField
    String name;
}

The PrivateField annotation type above is used to ensure the annotated field is declared as private. This restriction cannot be enforced by the PrivateField annotation type itself and hence annotation processor come in place to enforce the restriction.

Note: Please refer to Annotation Processing: Annotation Processor Service Loader to learn how the classes are structured in Maven projects and how to register the annotation processor.

By the way, this post is not about designing and implementing a useful annotation type. The code example given here are mainly used to demonstrate how the annotation processor dealing with the error at compile time.

Throwing runtime exception directly

@AutoService(Processor.class)
@SupportedAnnotationTypes("com.hauchee.annotation.PrivateField")
public class PrivateFieldProcessor extends AbstractProcessor {
    
    @Override
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
        for (Element element : roundEnv.getElementsAnnotatedWith(PrivateField.class)) {
            if (!element.getModifiers().contains(Modifier.PRIVATE)) {
                throw new IllegalArgumentException(
                        String.format("The field \"%s\" is not private.",
                                element.getSimpleName()));
            }
        }
        return true;
    }
}

Now, when we compile the Application source code with the annotation processor above, we get the following error at compile time.

Fatal error compiling: java.lang.IllegalArgumentException: The field "name" is not private.

We are able to see the validation message from the error above. However, throwing a runtime exception directly from the annotation processor is deficient because,
  • The error does not tell specific detail such as which Java class introduces the problem. It may too obvious in this example because there is only Application.java in the Application project. When there are many other classes also make use of the same annotation, then it is hard to find out which one is the root cause.
  • The error is giving a misleading impression that the compiler is not functioning properly. Fatal error compiling means the compiler having an issue to perform its job. Compilation error means the compiler done its job and found the Java source code compilation error.
Anyway, this does not mean that we cannot use Java Exception at all in annotation processor. After all it still is a Java application which can be designed and implemented properly.

Return false from process() method

@AutoService(Processor.class)
@SupportedAnnotationTypes("com.hauchee.annotation.PrivateField")
public class PrivateFieldProcessor extends AbstractProcessor {
    
    @Override
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
        for (Element element : roundEnv.getElementsAnnotatedWith(PrivateField.class)) {
            if (!element.getModifiers().contains(Modifier.PRIVATE)) {
                return false;
            }
        }
        return true;
    }
}

The annotation processor above would not cause any error at compile time. However, it had misused of the return parameter of process() method. By returning false, the annotation processor will excuse itself from claiming this annotation type. If there is a good reason to do that, then it is fine. For our case, the validation error requires programmer attention and action. By returning false, this does not resolve the problem but even worst it hide the problem without any notice.

Make use of Annotation Messager

@AutoService(Processor.class)
@SupportedAnnotationTypes("com.hauchee.annotation.PrivateField")
public class PrivateFieldProcessor extends AbstractProcessor {
    
    @Override
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
        for (Element element : roundEnv.getElementsAnnotatedWith(PrivateField.class)) {
            if (!element.getModifiers().contains(Modifier.PRIVATE)) {
                processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR,
                    String.format("The field \"%s\" is not private.",
                            element.getSimpleName()));
            }
        }
        return true;
    }
}

Any Java class that extends javax.annotation.processing.AbstractProcessor will inherit its javax.annotation.processing.ProcessingEnvironment. We can get a set of useful tools from this processingEnv. One of them is javax.annotation.processing.Messager which can be used to reporting annotation processing error. Compile the Application source code with the annotation processor above, we get the following compilation error.

Compilation failure
The field "name" is not private.

Compare to the first approach, the error message above is indeed a compilation error in Application.java. However, the error message above also does not tell any specific detail which could help in fixing the invalid annotation. This could be resolved by passing the affected source element to the Messager as below.

processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR,
    String.format("The field \"%s\" is not private.",
            element.getSimpleName()), element);

Compile the Application source code with the new changes made in annotation processor above, we get the following compilation error.

Compilation failure
com/hauchee/application/Application.java:[11,12] The field "name" is not private.

It specifically tells us that which Java class, at which line causing the compilation error. This information is very useful in identifying the invalid field. In fact, the IDE could digest the error and affected source element reported via Messager and highlights the invalid annotated source element in the script editor. The Netbeans IDE screenshot below proves that the handling of annotation processing error is consistent with other compilation error.


Summary:

Prevent from throwing any runtime exception from annotation processor. We still can make use of Java Exception but catch them in the process() method and report the error via Messager. Whenever possible pass the affected source code element together with the error message as it would help in identifying the invalid source element. Only return false from process() method if you have a good reason to do so.


References:
http://hannesdorfmann.com/annotation-processing/annotationprocessing101/
https://docs.oracle.com/javase/7/docs/api/javax/annotation/processing/Messager.html



Wednesday, November 18, 2015

Annotation processing during compilation time: Annotation Processor Service Loader

How to register an Annotation Processor

Annotation Processor appears in the form of Service Provider. However, instead of being accessed during application runtime, the annotation processor is being scanned and processed by the Annotation Processing Tool which built in the javac at application compile time

This post mainly demonstrates how to register a working annotation processor to the annotation processing tool in order to process certain annotation that used in an application source code. There are 3 roles here and are built in 3 different Maven projects.
  • Custom annotation type
  • Application
  • Annotation Processor


Annotation Maven Project: 

Custom Annotation type

A custom annotation type named Count is created. It targets to instance field only and having the source code level retention policy. This also means that the Count annotation (which applied in the application source code) information will be discarded after source code compilation.

Note: The source code level retention policy is used in this example but the annotation processor could also process annotation type with other retention policy.
package com.hauchee.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.SOURCE)
public @interface Count {
}

pom.xml

Nothing special. It is just a simple JAR after built.
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
                            http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.hauchee</groupId>
    <artifactId>Annotation</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>jar</packaging>
</project>


Application Maven Project:

Application Class

A simple class which its fields are annotated by Count annotations.
package com.hauchee.application;

import com.hauchee.annotation.Count;

public class Application {
    @Count
    String name;
    
    @Count
    int age;
}

pom.xml

This project has the dependency on the Annotation artifact (the Maven project you have seen above) and the AnnotationProcessor artifact which you will see it in the following sections.
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 
                            http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.hauchee</groupId>
    <artifactId>Application</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>jar</packaging>
    <dependencies>

        <!-- Dependency to Annotation -->
        <dependency>
            <groupId>com.hauchee</groupId>
            <artifactId>Annotation</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>

        <!-- Dependency to Annotation Processor -->
        <dependency>
            <groupId>com.hauchee</groupId>
            <artifactId>AnnotationProcessor</artifactId>
            <version>1.0-SNAPSHOT</version>
            <optional>true</optional>
        </dependency>

    </dependencies>
</project>


Conventional approach

Annotation Processor Project

Annotation Processor Implementation 

We write an annotation processor by doing the following:
  • Extends javax.annotation.processing.AbstractProcessor which make the class an annotation processor recognizable by the annotation processing tool. 
  • Override getSupportedAnnotationTypes() to tells the tool about the annotation types which are supported by this processor.
  • Provide implementation in process() method to process the annotated elements. The implementation below captures and counts those elements that annotated by Count annotation type.
package com.hauchee.annotationprocessor;

import com.hauchee.annotation.Count;
import java.util.HashSet;
import java.util.Set;
import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.RoundEnvironment;
import javax.lang.model.element.Element;
import javax.lang.model.element.TypeElement;

public class AnnotationProcessor extends AbstractProcessor {
    
    private int count;

    @Override
    public Set<String> getSupportedAnnotationTypes() {
        Set<String> annotationTypes = new HashSet<>();
        annotationTypes.add("com.hauchee.annotation.Count");
        return annotationTypes;
    }

    @Override
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
        for (Element element : roundEnv.getElementsAnnotatedWith(Count.class)) {
            System.out.format("\"%s\" is annotated with Count annotation.\n",
                    element.getSimpleName());
            count++;
        }
        if (count > 0) {
            System.out.format("Total of variable element which annotated with "
                    + "Count annotation: %d\n", count);
        }
        count = 0;
        return true;
    }
}

Service Configuration file

As mentioned at the beginning, annotation processor is a service provider. In order to register the processor, we create a file named javax.annotation.processing.Processor in /resources/META-INF/services directory. Then write down the fully qualified class name of the processor in the newly created file. The project structure looks like below:

pom.xml

This project has the dependency on the Annotation artifact. More importantly, we need to disable the annotation processing for this project by specifying -proc:none in maven compiler plugin. By default, the compiler compiles all classes and carries out the annotation processing. Without turning off the annotation processing, we will get the following compilation error:

error: Bad service configuration file, or exception thrown while constructing Processor object: javax.annotation.processing.Processor: Provider com.hauchee.annotationprocessor.AnnotationProcessor not found

Again, as mentioned at the beginning, the annotation processor is scanned and processed by the annotation processing tool at the compile time. That is the time our processor is not compiled yet and therefore, the tool never find it. By turning off the annotation processing, this will allow our processor get compiled.

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 
                            http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.hauchee</groupId>
    <artifactId>AnnotationProcessor</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>jar</packaging>
    
    <dependencies>
        <!-- Dependency to Annotation -->
        <dependency>
            <groupId>com.hauchee</groupId>
            <artifactId>Annotation</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
    </dependencies>
    
    <build>
        <pluginManagement>
            <plugins>
                <plugin>
                    <artifactId>maven-compiler-plugin</artifactId>
                    <version>2.3.2</version>
                    <configuration>
                        <source>1.8</source>
                        <target>1.8</target>
                        <!-- Disable annotation processing for this project. -->
                        <compilerArgument>-proc:none</compilerArgument>
                    </configuration>
                </plugin>
            </plugins>
        </pluginManagement>
    </build>
</project>

Compilation Result:

Now, all projects are set. Build the Application project, the annotation processing kicks in and we get the result below from the compilation output.
"name" is annotated with Count annotation.
"age" is annotated with Count annotation.
Total of variable element which annotated with Count annotation: 2


Handy approach

There is a handy approach which could save us from doing all the manual works and avoid human errors by using AutoSerivice annotation processor developed by Google. Below is the enhanced version of Annotation Processor project.

Annotation Processor Implementation 

The implementation is improved by
  • Using AutoService annotation which help to auto generate service configuration file at compile time.
  • Using SupportedAnnotationTypes annotation which save us from overriding the getSupportedAnnotationTypes() method.
package com.hauchee.annotationprocessor;

import com.google.auto.service.AutoService;
import com.hauchee.annotation.Count;
import java.util.Set;
import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.Processor;
import javax.annotation.processing.RoundEnvironment;
import javax.annotation.processing.SupportedAnnotationTypes;
import javax.lang.model.element.Element;
import javax.lang.model.element.TypeElement;

@AutoService(Processor.class)
@SupportedAnnotationTypes("com.hauchee.annotation.Count")
public class AnnotationProcessor extends AbstractProcessor {
    
    private int count;

    @Override
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
        for (Element element : roundEnv.getElementsAnnotatedWith(Count.class)) {
            System.out.format("\"%s\" is annotated with Count annotation.\n",
                    element.getSimpleName());
            count++;
        }
        if (count > 0) {
            System.out.format("Total of variable element which annotated with "
                    + "Count annotation: %d\n", count);
        }
        count = 0;
        return true;
    }
}

pom.xml

Added auto-service dependency so that we could make sure of AutoService annotation. The -proc:none compiler configuration option is no longer required because the service file not explicitly exists hence never scanned by the compiler. The service file is then auto-generated by com.google.auto.service.processor.AutoServiceProcessor when this processor is scanned and processed by annotation processing tool.

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" 
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
                            http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.hauchee</groupId>
    <artifactId>AnnotationProcessor</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>jar</packaging>
    
    <dependencies>
        <!-- Dependency to Annotation -->
        <dependency>
            <groupId>com.hauchee</groupId>
            <artifactId>Annotation</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
        
        <!-- Dependency to AutoService annotation processor -->
        <dependency>
            <groupId>com.google.auto.service</groupId>
            <artifactId>auto-service</artifactId>
            <version>1.0-rc2</version>
            <optional>true</optional>
        </dependency>
    </dependencies>
</project>

Compilation Result:

Compile Application project and viola, we get the same result from compilation output.

Conclusion:

The annotation processor example given in this post is a dummy as this post mainly telling the ways to register a service. In fact, it could do a lot of useful stuff. AutoService processor is the best example. It changed and simplified the way we register a service and make the service registration piece of cake.