Showing posts with label Reflection. Show all posts
Showing posts with label Reflection. 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

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