Showing posts with label Annotation. Show all posts
Showing posts with label Annotation. Show all posts

Monday, March 21, 2016

Repeating Annotation Effect - How to get the correct annotation at runtime

Since Annotation was introduced in Java 5, Java had also provided the Reflection interface, java.lang.reflect.AnnotatedElement which represents an annotated element of the program at runtime. It allows us to get the program element's annotations reflectively. An annotation might not be directly present on a program element. Hence, getter methods in AnnotatedElement help to detect and return the annotation(s) with certain kind of presence.

Moreover, the kind of annotation presence is further expanded when Java 8 introduced Repeating Annotation. And of course, new methods were added onto AnnotatedElement to make sure the caller is able to retrieve the annotation with new kind of presence.

This post describes:
  • Different kind of annotation presence, prior to and since Java 8.
  • Which AnnotatedElement method deals with which kind of annotation presence.
The following annotation types and classes are used for explanation in this post.

Given annotation types below.
@Retention(RetentionPolicy.RUNTIME) // Annotation available at runtime
@Target(ElementType.TYPE) // Target to Class/Interface element only
@Inherited // Inheritable. In this case, annotation is inherited to Subclass
public @interface Alert {
    String name() default "";
}

@Retention(RetentionPolicy.RUNTIME) // Annotation available at runtime
@Target(ElementType.TYPE) // Target to Class/Interface element only
@Inherited // Inheritable. In this case, annotation is inherited to Subclass
public @interface Role {
    String name() default "";
}

Apply annotation onto the Access classes below.
@Role(name = "superadmin")
@Alert(name = "superadmin") 
abstract class Access { // Superclass
}

@Role(name = "staff") // Overrided superclass @Role annotation
class ReadOnlyAccess extends Access { // Subclass
}

Now we will use the methods in AnnotatedElement to retrieve the @Role and @Alert annotation at runtime.

Prior to Java 8

Prior to Java 8, there are no precise terms and descriptions about the annotation presences. However, they are obvious and  not too complicated to be understood. Basically, an annotation could have 2 kinds of presences on a program element.
  • Directly present
  • Present

Directly Present

An annotation is explicitly specified on an element and this annotation is visible or available at runtime. To get the annotation that direct present on an element, we use getDeclaredAnnotation(Class) method.

Class c = ReadOnlyAccess.class;
Role roleDirectPresent = (Role) c.getDeclaredAnnotation(Role.class); // roleDirectPresent = @Role(staff)
Alert alertDirectPresent = (Alert) c.getDeclaredAnnotation(Alert.class); // alertDirectPresent = null
Annotation[] allDirectlyPresent = c.getDeclaredAnnotations(); // allDirectPresent = {@Role(staff)}

We are able to get the @Role(staff) because it is directly specified on the ReadOnlyAccess class element. On the hand, although @Alert is specified on Access class element, and it is inherited to ReadOnlyAccess, but we never able to get it using getDeclaredAnnotation(Class) because it is not direct present on ReadOnlyAccess class element.

Present

An annotation is either directly present on an element or inherited from the element's parent and this annotation is visible or available at runtime. To get the annotation that present on an element, we use getAnnotation(Class) method.

Class c = ReadOnlyAccess.class;
Role rolePresent = (Role) c.getAnnotation(Role.class); // rolePresent = @Role(staff)
Alert alertPresent = (Alert) c.getAnnotation(Alert.class); // aleartPresent = @Alert(superadmin) - inherited
Annotation[] allPresent = c.getAnnotations(); // allPresent = {@Role(staff), @Alert(superadmin)}

By using getAnnotation(Class) method, we not only able to get the annotation @Role(staff) that is directly present on ReadOnlyAccess, we also able to get the annotation @Alert(superadmin) that is inherited from Access class element.

Note: Getting an inherited annotation does not work for all annotated element. For example, it does not works for METHOD element because the getAnnotation(Class) method in java.lang.reflect.Method still getting the annotation that directly present on the method element.

Since Java 8

Started from Java 8, an annotation type supports repeatable. We are now able to specify zero to many annotations with the same annotation type. Now, let enhance our annotation type to be repeatable.

Repeating Annotation

We first create the containing annotation types.
@Inherited
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Alerts {
    Alert[] value(); // to contain an array of Alert annotations
}

@Inherited
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Roles {
    Role[] value(); // to contain an array of Role annotations
}

Then specify the @Repeatable annotation onto the annotation types respectively.
@Inherited
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Repeatable(Alerts.class) // Use @Alerts as container annotation
public @interface Alert {
    String name() default "";
}

@Inherited
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Repeatable(Roles.class) // Use @Roles as container annotation
public @interface Role {
    String name() default "";
}

A few important notes to take:
  • The containing annotation type must have a method named as "value" and return an array of specific annotation. If not, it will be treated as normal annotation type.
  • The characteristic (such as inheritable, runtime visible, target) of the containing annotation type must consistent with the annotation type that it would like to contain. Failing to do so will cause compilation error at annotation type that takes it as annotation container.

Backward Compatibility

Making an annotation type to support repeatable will not break backward compatibility. In the case where the Access and ReadOnlyAccess class remain the same,
@Role(name = "superadmin")
@Alert(name = "superadmin") 
abstract class Access { // Superclass
}

@Role(name = "staff") 
class ReadOnlyAccess extends Access { // Subclass
}

Despite the @Alert and @Role is now repeatable, but we are still able to get the single annotation using the methods below..
Role roleDirectPresent = (Role) c.getDeclaredAnnotation(Role.class); // roleDirectPresent = @Role(staff)       
Role rolePresent = (Role) c.getAnnotation(Role.class); // rolePresent = @Role(staff)

Note: The @Role(staff) annotation that is specified on the ReadOnlyAccess class element will still override the @Role(superadmin) annotation of it superclass Access. They are never accumulated to become repeating annotation

New Kind of Annotation Presence

Due to the existence of the container annotation, new kinds of annotation presence are introduced. What does the methods in AnnotatedElement can do also become more complicated.
  • Directly Present
  • Indirectly Present
  • Present
  • Associated

Let's specify the repeating annotation in our classes. I will then use the AnnotationElement methods to get the annotation and explain the kind of presence accordingly. You may need to come back to here again when you go through the next sections because the example codes in next section are all talking about getting annotation we have specified here.
@Role(name = "superadmin")
@Alert(name = "superadmin") 
@Alert(name = "superuser") // Repeated @Alert
abstract class Access {
}

@Role(name = "staff")
@Role(name = "guest") // Repeated @Role
class ReadOnlyAccess extends Access {
}

Directly Present

An annotation A is directly present on an element E if E has a RuntimeVisibleAnnotations or RuntimeVisibleParameterAnnotations or RuntimeVisibleTypeAnnotations attribute, and the attribute contains A. - AnnotatedElement Oracle Doc

Well, it basically maintains the contract prior to Java 8. However, things work differently due to the existence of repeating annotation. Look at the ReadOnlyAccess class, now we have the repeating annotation, in this case, two @Role annotations are specified on it. What we see is two individual @Role annotations but in fact, they are contained in a container annotation @Roles. Decompile the ReadOnlyAccess class and you will find that, Java Compiler had turned them into a container annotation as below.

@Roles(
    value = {
        @Role(name = "staff"),
        @Role(name = "guest")
    }
)
class ReadOnlyAccess extends Access {
}

That's mean, what directly present on the ReadOnlyAccess class element is container annotation @Roles.

Note: We can specify the repeating annotations by using the containing annotation type. After all, it is also a valid annotation type.

Let's try to get the annotation that directly present on ReadOnlyAccess class element. Again, we use the getDeclaredAnnotation(Class) to do that. Have a look on the variable(s) returned from each method call below, they should help you to understand the idea behind.

Class c = ReadOnlyAccess.class;

// roleDirectPresent = null 
// @Role annotation no longer direct present. It is an attribute in container annotation @Roles.
Role roleDirectPresent = (Role) c.getDeclaredAnnotation(Role.class);

// rolesDirectPresent = @Roles({@Role(staff), @Role(guest)})
// @Roles is the container annotation that direct present on the class element.
Roles rolesDirectPresent = (Roles) c.getDeclaredAnnotation(Roles.class);

// alertDirectPresent = null
// @Alert not explicitly present at all.
Alert alertDirectPresent = (Alert) c.getDeclaredAnnotation(Alert.class);

// allDirectPresent = {@Roles({@Role(staff), @Role(guest)})}
Annotation[] allDirectPresent = c.getDeclaredAnnotations();

// allRolesDirectPresent = @Roles({@Role(staff), @Role(guest)})
// New method in AnnotatedElement since Java 8. Get container annotation that is directly present on the class element
Roles[] allRolesDirectPresent = (Roles[]) c.getDeclaredAnnotationsByType(Roles.class);

The getDeclaredAnnotationsByType(Class) is a new method in AnnotatedElement which was introduce since Java 8. It can be used to get annotation that is directly present on an element. It seems working equivalent to getDeclaredAnnotation(Roles.class), but it actually can do more. See the next Indirectly Present section.

Indirectly Present

An annotation A is indirectly present on an element E if E has a RuntimeVisibleAnnotations or RuntimeVisibleParameterAnnotations or RuntimeVisibleTypeAnnotations attribute, and A 's type is repeatable, and the attribute contains exactly one annotation whose value element contains A and whose type is the containing annotation type of A 's type. - AnnotatedElement Oracle Doc

This is one of the new kinds of annotation presence introduced since Java 8. By using our example code for the explanation, the annotation @Role is indirectly present on a ReadOnlyAccess class element because it is runtime visible, and it resides in the @Roles container annotation which is specified on ReadOnlyAccess.

At first glance on the ReadAccessOnly, it may hard to understand why @Role annotations are not directly present on ReadAccessOnly. They did directly specified on the ReadAccessOnly class element. The reason is what I have described in previous Directly Present section. Whenever more than one repeating annotation specified on an element, Java will use container annotation to host the repeating annotations. Therefore, @Role annotations are indirectly present on ReadAccessOnly class element via container annotation @Roles

The getDeclaredAnnotationByType(Class) method can be used to get the annotations from the container annotation.

// allRoleDirectPresent = {@Role(staff), @Role(guest)}
Role[] allRoleDirectPresent = (Role[]) c.getDeclaredAnnotationsByType(Role.class);

This saves our effort from getting the container annotation first, then look through its value attribute to get the annotations.

Present

An annotation A is present on an element E if either:
  • A is directly present on E; or
  • No annotation of A 's type is directly present on E, and E is a class, and A 's type is inheritable, and A is present on the superclass of E.
AnnotatedElement Oracle Doc

Again, it basically maintains the contract prior to Java 8. The AnnotatedElement methods which able to get annotations that present on an element now also able to get the container annotation which directly present on the element or inherited from the element's parent.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
// rolePresent = @Role(superadmin)
// An annotation inherited from parent class element.
Role rolePresent = (Role) c.getAnnotation(Role.class);

// rolesPresent = @Roles({@Role(staff), @Role(guest)})
// Container annotation is directly present on class element
Roles rolesPresent = (Roles) c.getAnnotation(Roles.class);

// alertPresent = null
// Not specify in either superclass or subclass.
Alert alertPresent = (Alert) c.getAnnotation(Alert.class);

// alertsPresent = @Alerts({@Alert(superadmin), @Alert(superuser)})
// Container annotation is inherited from parent class element.
Alerts alertsPresent = (Alerts) c.getAnnotation(Alerts.class);

// allPresent = {@Role(staff), @Alerts({Alert(superadmin), @Alert(superuser)})}
Annotation[] allPresent = c.getAnnotations();

The interesting part is at line #3 and #7. The @Role annotation specified on superclass Access is not overridden by the @Roles container annotation specified on subclass ReadOnlyAccess. Well, they are two different annotations in fact. And of course, they will not be accumulated into a container annotation.

Associated

An annotation A is associated with an element E if either:
  • A is directly or indirectly present on E; or
  • No annotation of A 's type is directly or indirectly present on E, and E is a class, and A's type is inheritable, and A is associated with the superclass of E.

Another new kind of annotation presence introduced since Java 8. It covers the broadest annotation presence where an annotation is directly or indirectly present on an element or inherited from the element's parent.

The only method in AnnotatedElement that able to return annotation that associated to an element is getAnnotationsByType(Class).

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
// roleAssociated = {@Role(staff), @Role(guest)}
// Because @Role is repeatable, hence look through the container annotation and return it value attributes.
Role[] roleAssociated = (Role[]) c.getAnnotationsByType(Role.class);

// rolesAssociated = {@Roles({@Role(staff), @Role(guest)})}
// @Roles container annotation is directly present on class element.
Roles[] rolesAssociated = (Roles[]) c.getAnnotationsByType(Roles.class);

// alertAssociated = {@Alert(superadmin), @Alert(superuser)}
// Because @Alert is repeatable, hence look through the inherited container annotation and return it value attributes.
Alert[] alertAssociated = (Alert[]) c.getAnnotationsByType(Alert.class);

// alertsAssociated = {@Alerts({@Alert(superadmin), @Alert(superuser)})}
// @Alerts container annotation is inhertied from parent class element.
Alerts[] alertsAssociated = (Alerts[]) c.getAnnotationsByType(Alerts.class);

The highlights are at line #3 and #7.

#3 - Because @Role is repeatable, so getAnnotationByType(Role.class) looks through the container annotation @Roles and return all @Role annotations that the container contains. If the container is empty, then it will return the inherited @Role annotation (if any).

#7 - getAnnotationByType(Alert.class) is able to return all @Alert annotation inhertied by the ReadOnlyAccess.

Summary

Creating a repeating annotation type is straightforward, but getting the correct annotation could be a bit complicated. By knowing different kinds of annotation presence, it will help us to get the annotation that we want.

Overview of kind of presence detected by different AnnotatedElement methods - AnnotatedElement Oracle Doc
Kind of Presence
MethodDirectly PresentIndirectly PresentPresentAssociated
TgetAnnotation(Class<T>) X
Annotation[]getAnnotations() X
T[]getAnnotationsByType(Class<T>) X
TgetDeclaredAnnotation(Class<T>) X
Annotation[]getDeclaredAnnotations() X
T[]getDeclaredAnnotationsByType(Class<T>) XX

Besides runtime processing, Java also introduced the javax.lang.model.AnnotatedConstruct interface which represents the annotated source code element and allows us to get the annotation during annotation processing at compile time. The annotation presence terms and concepts are same as the AnnotatedElement. If you are interested in annotation processing, you can learn more from Annotation Processing during Compile Time.

References:
https://docs.oracle.com/javase/7/docs/api/java/lang/reflect/AnnotatedElement.html
https://docs.oracle.com/javase/8/docs/api/java/lang/reflect/AnnotatedElement.html
https://docs.oracle.com/javase/tutorial/java/annotations/repeating.html
https://docs.oracle.com/javase/8/docs/api/javax/lang/model/AnnotatedConstruct.html

Sunday, January 3, 2016

Annotation processing during compilation time: Generating source code

Annotation processor is able to generate additional files including new Java source file during annotation processing at compile time. Moreover, this newly generated Java source file will also get compiled in the same compilation unit.

This post shows you a working example of annotation processor which able to create a new POJO class for the class field that annotated with @Pojo annotation without a programmer has to write this class explicitly. This post focuses on source code implementation. For projects structure and how to register an annotation processor, please refer to my other post, Annotation Processing: Register Annotation Processor.

Annotation Type:

Firstly, we need a project that contains the custom annotation type as below:

@Target(ElementType.FIELD) // Target to field element only
@Retention(RetentionPolicy.SOURCE) // Source level retention policy
public @interface Pojo {
    
    /**
     * The types of the fields in this POJO class. 
     * Each type has a corresponding name specified in 'fieldNames' attribute.
     */
    Class[] fieldTypes() default {};
    
    /**
     * The names of the fields in this POJO class. 
     * Each name has a corresponding type specified in 'fieldTypes' attribute.
     */
    String[] fieldNames() default {};
}

The @Pojo annotation with the Pojo annotation type above has the following characteristics:
  • It can only be applied to the field declaration in a class. Applying it on to other declaration will get a compilation error. "annotation type not applicable to this kind of declaration"
  • It compiles the source code level retention policy. This means that the @Pojo annotation (which applied in the application source code) information will be discarded after the source code compilation. Well, this retention policy is all we need for the source code generation example in this post.
  • It has 2 annotation attributes. The fieldTypes is expecting an array of Class elements; the fieldNames is expecting an array of String elements. Both elements at the same element index are information mandatory for constructing a field in the to be created POJO class. Therefore, both array size must be the same and String element value must not be an empty string.

Application

This is the class in another project where we apply the @Pojo annotation.

package com.hauchee.application; // PackageElement

import com.hauchee.annotation.Pojo;

public class Application { // TypeElement
    
    private String id; // VariableElement
    
    @Pojo( // AnnotationMirror
        // ExecutableElement:AnnotationValue pairs    
        fieldNames = {"name", "price"}, 
        fieldTypes = {String.class, Double.class}
    )
    private Game game; // VariableElement

    public Application(String id, Game game) { // ExecutableElement
        this.id = id;
        this.game = game;
    }

    public String getId() { // ExecutableElement
        return id;
    }

    public Game getGame() { // ExecutableElement
        return game;
    }
}

When the annotation processing takes place, the annotation processor sees the Application source above as an element tree below:



By the way, the Game class of the game field does not exist. By applying the @Pojo annotation with the field information, this allows annotation processor to generate the POJO class for Game.

Annotation Processor

Now is the time to implement the annotation processor to process the field that annotated with @Pojo annotation. Let's start with the skeleton as below:

Annotation processor skeleton:

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

    private Elements elementsUtil;
    private Filer filer;
    private Messager messager;
    
    @Override
    public synchronized void init(ProcessingEnvironment processingEnv) {
        super.init(processingEnv);
        elementsUtil = processingEnv.getElementUtils();
        filer = processingEnv.getFiler();
        messager = processingEnv.getMessager();
    }

    @Override
    public boolean process(Set<? extends TypeElement> annotations,
            RoundEnvironment roundEnv) {
        
        // Continue to get the involved elements. Refer to next section.
        return true;
    }

    private void error(VariableElement varElement,AnnotationMirror annotationMirror,
            String message) {
        messager.printMessage(Diagnostic.Kind.ERROR, message, varElement, annotationMirror);
    }
}

We create a new annotation processor named PojoProcessor by extending the javax.annotation.processing.AbstractProcessor. This new annotation processor has to be registered as a service so that it is recognizable by the Annotation Processing Tool (APT). This can be done in a conventional way but in this example, I make use of Google auto-service @AutoService which make the registration much easier. For more detail, please read the Annotation Processing: Register Annotation Processor.

Once the annotation processor is registered successfully, it is initialized (where its init() method is called) by the APT and stay in standby mode during compilation time. In this example, the init() method is overridden in order to collect all necessary utility classes.
  • Elements: An utility class for operating on elements. In this example, we use this class to retrieve the element that we want.
  • Filer: The filer is used to create new source, class, or auxiliary files. In this example, we use filer to generate new Java source files.
  • Messager: The messager is used to report errors, warnings, and other notices. In this example, we use messager (in the error() method) to report validation errors. This will then provide hints to the IDE to react on those errors. For more detial, please read the Annotation Processing: Error Handling
  • Types: An utility class for operating on types. Not use in this example
When the PojoProcessor start processing? In another words, when its process() method is executed? At compile time, if the APT scanned and detected the @Pojo annotation used in a class (in this case, the Application field), it will invoke the process() method of the PojoProcessor which has declared itself to support the Pojo annotation type in @SupportedAnnotationType.

Getting the involved elements

Coming up the implementation in process() method shows how to retrieve the highlighted elements below. Those are elements required in this example for error reporting as well as source code generation.

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

    // The variable element which annotated with @Pojo annotation.
    VariableElement varElement = null;

    // The @Pojo annotation element.
    AnnotationMirror annotationMirror = null;

    try {
        // This returns the variable element that annotated with @Pojo annotation.
        for (Element annotatedElement : roundEnv.getElementsAnnotatedWith(Pojo.class)) {
            varElement = (VariableElement) annotatedElement;

            // Getting all the annotation elements from the variable element.
            List<? extends AnnotationMirror> allAnnotationMirrors
                    = varElement.getAnnotationMirrors();
            for (AnnotationMirror aAnnotationMirror : allAnnotationMirrors) {

                // Make sure the annotation element is belong to Pojo annotation type.
                if (aAnnotationMirror.getAnnotationType().toString()
                        .equals(Pojo.class.getName())) {

                    // Found the @Pojo annotation element.
                    annotationMirror = aAnnotationMirror;

                    // Getting the annotation element values from the @Pojo annotation element.
                    Map<? extends ExecutableElement, ? extends AnnotationValue> elementValues
                            = annotationMirror.getElementValues();
                    
                    // Continue to get the annotation values. Refer to next section.
                    break;

                }
            }

        }
    } catch (ProcessingException e) {
        error(varElement, annotationMirror, e.getMessage());
    } catch (IOException e) {
        error(null, null, e.getMessage());
    }

    return true;
}

Getting and validating the annotation values

After we have obtained the annotation element values, next is to extract the values from these elements. This could be fairly simple if the annotation element types are primitive types. But it become tricky when it involves Class type. For more detail, please read Annotation Processing: Getting Class annotation value.

In this example, I have implemented an annotation value visitor to help the PojoProcessor to extract the values.

class PojoAnnotationValueVisitor extends SimpleAnnotationValueVisitor7<Void, String> {

    private final List<TypeName> fieldTypes = new ArrayList<>();
    private final List<String> fieldNames = new ArrayList<>();

    // Only need to override the visitArray() method because both 'fieldTypes' and
    // 'fieldNames' are array.
    @Override
    public Void visitArray(List<? extends AnnotationValue> vals, String elementName) {
        for (AnnotationValue val : vals) {
            Object value = val.getValue();
            switch (elementName) {
                case "fieldTypes":
                    fieldTypes.add(TypeName.get((TypeMirror) value));
                    break;
                case "fieldNames":
                    fieldNames.add((String) value);
                    break;
            }
        }

        return null;
    }

    void validateValues() throws ProcessingException {
        if (fieldNames.size() != fieldTypes.size()) {
            throw new ProcessingException(
                    "The length of 'fieldNames' must be same as the length of 'fieldTypes'.");
        }
        validateEmptyElement(fieldNames, "fieldNames");
    }

    private void validateEmptyElement(List<String> elements, String name)
            throws ProcessingException {
        
        int i = 0;
        for (String fieldName : elements) {
            if (fieldName.trim().isEmpty()) {
                throw new ProcessingException(
                        "\"%s\" with element index \"%d\" must not be an empty String.", name, i);
            }
            i++;
        }
    }

    public List<TypeName> getFieldTypes() {
        return fieldTypes;
    }

    public List<String> getFieldNames() {
        return fieldNames;
    }
}

The PojoAnnotationValueVisitor 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 PojoProcessor.

This visitor also helps in validating the annotation values. It will throw checked ProcessingException (as below) if the annotation value is invalid. The validation shown in this visitor could be better but let's just live with the simple validation checking.

class ProcessingException extends Exception {

    public ProcessingException(String msg, Object... args) {
        super(String.format(msg, args));
    }
}

Now, let's make use of the visitor in PojoProcessor.

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

    ...

    // Create a visitor instance.
    PojoAnnotationValueVisitor visitor = new PojoAnnotationValueVisitor();

    // Getting annotation element values from the @Pojo element.
    Map<? extends ExecutableElement, ? extends AnnotationValue> elementValues
            = annotationMirror.getElementValues();
    for (Map.Entry<? extends ExecutableElement, ? extends AnnotationValue> entry
    	: elementValues.entrySet()) {

        // The 'entry.getKey()' here is the annotation attribute name.
        String key = entry.getKey().getSimpleName().toString(); 

        // The 'entry.getValue()' here is the annotation value which could accept a visitor.
        entry.getValue().accept(visitor, key);
    }

    visitor.validateValues(); // Throw ProcessingException if annotation value is invalid.

    ...
}

Generating new source code

Once we get the valid annotation values, next thing is to process those values and generate the new source file. Basically, Filer is able to open an OutputStream and we could just construct the new source code content in Strings and write them into this OutputStream. However, there is a more structural and organized approach to achieve the same thing by making use of third party JavaPoet. In this example, I have implemented a PojoSourceCodeBuilder to helps PojoProcessor to build the Java source content.

class PojoSourceCodeBuilder {
    
    private final Filer filer;
    private final String packageName;
    private final List<FieldSpec> fieldSpecs;
    private final List<MethodSpec> methodSpecs;
    
    public PojoSourceCodeBuilder(Filer filer, PackageElement pkgElement) {
        this.filer = filer;
        this.packageName = pkgElement.isUnnamed()
                ? null : pkgElement.getQualifiedName().toString();
        this.fieldSpecs = new ArrayList<>();
        this.methodSpecs = new ArrayList<>();
    }

    private void addGetterSetterMethods(String fieldName, TypeName fieldType) {
        String nameForGetterSetter
                = fieldName.substring(0, 1).toUpperCase() + fieldName.substring(1);         

        /** Getter method spec. **/
        methodSpecs.add(MethodSpec.methodBuilder("get" + nameForGetterSetter)
                .addModifiers(Modifier.PUBLIC)
                .addStatement("return " + fieldName)
                .returns(fieldType).build());

        /** Setter method spec. **/
        methodSpecs.add(MethodSpec.methodBuilder("set" + nameForGetterSetter)
                .addModifiers(Modifier.PUBLIC)
                .addParameter(fieldType, fieldName, Modifier.FINAL)
                .addStatement("this." + fieldName + " = " + fieldName)
                .returns(TypeName.VOID).build());
    }
    
    public PojoSourceCodeBuilder addFieldWithGetterSetter(String fieldName, TypeName fieldType) {
        fieldSpecs.add(FieldSpec.builder(fieldType, fieldName, Modifier.PRIVATE).build());
        addGetterSetterMethods(fieldName, fieldType);
        return this;
    }
    
    public void writeToJavaFile(String className) throws IOException {
        TypeSpec typeSpec = TypeSpec.classBuilder(className).addFields(fieldSpecs)
                .addModifiers(Modifier.PUBLIC)
                .addMethods(methodSpecs).build();
        JavaFile.builder(packageName, typeSpec).build().writeTo(filer);
    }
}

Now, let's make use of the source code builder in PojoProcessor.

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

    ...

    visitor.validateValues();
    buildPojoClass(varElement, visitor);
    break;

    ...
}

private void buildPojoClass(
        VariableElement varElement, PojoAnnotationValueVisitor visitor)
        throws IOException {

    List<String> fieldNames = visitor.getFieldNames();
    List<TypeName> fieldTypes = visitor.getFieldTypes();

    PackageElement pkgElement = elementsUtil.getPackageOf(varElement.getEnclosingElement());

    // Create a source code builder instance.
    PojoSourceCodeBuilder sourceCodeBuilder = new PojoSourceCodeBuilder(filer, pkgElement);

    // Process the field names and types.
    for (int i = 0; i < fieldTypes.size(); i++) {
        TypeName fieldType = fieldTypes.get(i);
        String fieldName = fieldNames.get(i);

        sourceCodeBuilder.addFieldWithGetterSetter(fieldName, fieldType);
    }

    // Write to the new Java source file.
    sourceCodeBuilder.writeToJavaFile(varElement.asType().toString());
}

Great! We are all set now. Build the projects in the following sequence.
  1. Annotation
  2. Annotation Processor
  3. Application
Previously, without the annotation processor, building the Application project will get "cannot find symbol" compile error at the line where Game type is used. Now, building the Application project with the annotation processor is succesful and the Game source file is automatically generated.

public class Game {

    private String name;

    private Double price;

    public String getName() {
        return name;
    }

    public void setName(final String name) {
        this.name = name;
    }

    public Double getPrice() {
        return price;
    }

    public void setPrice(final Double price) {
        this.price = price;
    }
}

Processing round

The same process() method could be called more than 1 time. The official javadoc define processing like as follows:

Annotation processing happens in a sequence of rounds. On each round, a processor may be asked to process a subset of the annotations found on the source and class files produced by a prior round. The inputs to the first round of processing are the initial inputs to a run of the tool; these initial inputs can be regarded as the output of a virtual zeroth round of processing.

To stick with our Pojo example, in the situation where the newly generated source code also has a field that annotated with @Pojo, the PojoProcessor will have to process another round for this newly added @Pojo annotated field (originating from the prior round of processing) in the new source file.

Round Input Output
1 Application Game
2 Game Category
3 Category N/A
4 N/A N/A

In order to demonstrate this, let's enhance the Pojo annotation type to accepts an array of String which representing the new field(s) to be annotated with @Pojo annotation.

Annotation type

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.SOURCE)
public @interface Pojo {
    
    ...
    
    /**
     * The string element in this array is representing a new field which its type
     * is same as its name and it is annotated with @Pojo annotation.
     */
    String[] fieldsAnnotatedWithPojo() default {};
}

Add the new annotation attribute in Application class.

Application

public class Application {
    
    ...
    
    @Pojo(  
        fieldNames = {"name", "price"}, 
        fieldTypes = {String.class, Double.class},
        fieldsAnnotatedWithPojo = {"Category"} // new annotation attribute
    )
    private Game game;

    ...
}

For the annotation processor, there are a few places need to be enhanced.

PojoAnnotationValueVisitor

class PojoAnnotationValueVisitor extends SimpleAnnotationValueVisitor7<Void, String> {

    ...
    
    private final List<String> fieldsAnnotatedWithPojo = new ArrayList<>();

    @Override
    public Void visitArray(List<? extends AnnotationValue> vals, String elementName) {
        for (AnnotationValue val : vals) {
            Object value = val.getValue();
            switch (elementName) {
                
                ...
                    
                case "fieldsAnnotatedWithPojo": // To extract 'fieldsAnnotatedWithPojo' value.
                    fieldsAnnotatedWithPojo.add((String) value);
                    break;
            }
        }
        return null;
    }

    void validateValues() throws ProcessingException {
        
        ...
        
        // Validate 'fieldsAnnotatedWithPojo' value.
        validateEmptyElement(fieldsAnnotatedWithPojo, "pojoAnnotatedFields");
    }

    ...

    // Return 'fieldsAnnotatedWithPojo' value.
    public List<String> getFieldsAnnotatedWithPojo() {
        return fieldsAnnotatedWithPojo;
    }
}

PojoSourceCodeBuilder

class PojoSourceCodeBuilder {
    
    ...
    
    // To add annotated field with getter and setter method.
    public PojoSourceCodeBuilder addAnnotatedFieldWithGetterSetter(
            String fieldName, TypeName fieldType, Class annotationClass) {
        
        fieldSpecs.add(FieldSpec.builder(fieldType, fieldName, Modifier.PRIVATE)
                .addAnnotation(annotationClass).build());
        addGetterSetterMethods(fieldName, fieldType);
        return this;
    }
    
    public String getPackageName() {
        return packageName;
    }
    
    ...
}

PojoProcessor

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

    ...

    private void buildPojoClass(
            VariableElement varElement, PojoAnnotationValueVisitor visitor)
            throws IOException {

        ...

        // Process fields that to be annotated with Pojo annotation.
        for (String aFieldAnnotatedWithPojo : fieldsAnnotatedWithPojo) {
            String fieldAnnotatedWithPojo = aFieldAnnotatedWithPojo;
            String pojoAnnotatedFieldName = fieldAnnotatedWithPojo.substring(0, 1).toUpperCase()
                    + fieldAnnotatedWithPojo.substring(1);
            ClassName fieldType = ClassName.get(sourceCodeBuilder.getPackageName(), pojoAnnotatedFieldName);

            sourceCodeBuilder
                    .addAnnotatedFieldWithGetterSetter(fieldAnnotatedWithPojo, fieldType, Pojo.class);
        }

        ...
    }
}

Now, build the projects in the same sequence and the following source files are generated automatically.

// Game.java
public class Game {

    ...

    @Pojo
    private Category Category;

    ...

    public Category getCategory() {
        return Category;
    }

    public void setCategory(final Category Category) {
        this.Category = Category;
    }
}

// Category.java
public class Category {
}

The key point about round processing is that the annotation processor must gracefully handle an empty set of annotations. For instance, if we have the PojoAnnotationValueVisitor as instance variable in PojoProcessor as instance variable in PojoProcessor, in the second round of processing, the visitor will return the values of prior processing which could mess up the whole processing. Therefore, make sure our implementation to provide a clean state for every new round of processing.

Conclusion

This is a long post mainly focus on code generation. However, I recommend you to read my other annotation processing posts to know more in detail in specific area. You can get the entire example projects in this post from GitHub.


References: