Java Annotation Processor: how to extract class values from an annotation

674 views Asked by At

How to extract the value Class[] value() in the annotation

package com.example;

public @interface ExampleAnnotation {
    Class[] value();
}

Without the annotation begin in the runtime of the Annotation Processor.

3

There are 3 answers

1
Ahmad Bawaneh On

I use the following utility i built for my own annotation processors:

public List<TypeMirror> getClassArrayValueFromAnnotation(Element element, Class<? extends Annotation> annotation, String paramName) {
    Elements elements = this.processingEnv.getElementUtils();
    Types types = this.processingEnv.getTypeUtils();

    List<TypeMirror> values = new ArrayList<>();

    for (AnnotationMirror am : element.getAnnotationMirrors()) {
        if (types.isSameType(am.getAnnotationType(), elements.getTypeElement(annotation.getCanonicalName()).asType())) {
            for (Map.Entry<? extends ExecutableElement, ? extends AnnotationValue> entry : am.getElementValues().entrySet()) {
                if (paramName.equals(entry.getKey().getSimpleName().toString())) {
                    List<AnnotationValue> classesTypes = (List<AnnotationValue>) entry.getValue().getValue();
                    Iterator<? extends AnnotationValue> iterator = classesTypes.iterator();

                    while (iterator.hasNext()) {
                        AnnotationValue next = iterator.next();
                        values.add((TypeMirror) next.getValue());
                    }
                }
            }
        }
    }
    return values;
}
0
LSafer On

This is my best way. I use the java stream API for more simplicity.

Write this in your processor class:

    public static final String TARGET = "com.example.ExampleAnnotation";

    @Override
    public boolean process(Set<? extends TypeElement> types, RoundEnvironment environment) {
        //process once!
        if (!environment.processingOver())
            //For us to not depend on a class in this runtime
            //try to find the TypeElement of it
            types.stream()
                    .filter(type -> type.getQualifiedName().contentEquals(TARGET))
                    .findAny()
                    .ifPresent(type ->
                            //it exists!
                            //let us see elements annotated with it!
                            environment.getElementsAnnotatedWith(type)
                                    .forEach(element ->
                                            //got an element!
                                            //let us iterate over the annotation mirrors it has got
                                            //then search for the annotation with the targeted type
                                            element.getAnnotationMirrors()
                                                    .stream()
                                                    .filter(annotation -> this.processingEnv.getTypeUtils().isSameType(type.asType(), annotation.getAnnotationType()))
                                                    .findAny()
                                                    .ifPresent(annotation -> {
                                                        //bingo!
                                                        //lets get its values
                                                        Map<? extends ExecutableElement, ? extends AnnotationValue> values = annotation.getElementValues();

                                                        //lets find the annotation value `Class[] value()`
                                                        //any inappropriate value will be simply IGNORED (do not die strategy)
                                                        //this block trusts the javax documentation only!
                                                        //see javax.lang.model.element.AnnotationValue
                                                        List<TypeMirror> value = values.entrySet()
                                                                .stream()
                                                                .filter(entry -> entry.getKey().getSimpleName().contentEquals("value"))
                                                                .findAny()
                                                                .map(entry -> entry.getValue().getValue())
                                                                .filter(List.class::isInstance)
                                                                .<List<AnnotationValue>>map(List.class::cast)
                                                                .map(list ->
                                                                        list.stream()
                                                                                .map(AnnotationValue::getValue)
                                                                                .filter(TypeMirror.class::isInstance)
                                                                                .map(TypeMirror.class::cast)
                                                                                .collect(Collectors.toList())
                                                                )
                                                                .orElseGet(Collections::emptyList);

                                                        //Operate below ---------------------------------
                                                        //Operate above --------------------------------
                                                    })
                                    )
                    );

        return false;
    }
1
ktul On

In many cases, you can get the TypeMirror from the exception that is thrown when you try to access a Class or Class[] parameter (see this answer).

When you access a class parameter, a MirroredTypeException is thrown, while when you access a class array parameter, a MirroredTypesException is thrown. Both provide the TypeMirrors.

In the following example, the methods mirror and mirrorAll wrap the verbose try-catch pattern and provide the respective TypeMirrors. They accept the respective getter via a method reference.

public @interface ExampleAnnotation {
    Class<?> type();
    Class<?>[] types();
}

private void process(TypeElement typeElement){
    ExampleAnnotation annotation = typeElement.getAnnotation(ExampleAnnotation.class);
    TypeMirror type = mirror(annotation::type);
    List<? extends TypeMirror> types = mirrorAll(annotation::types);
}

public static TypeMirror mirror(Supplier<Class<?>> classValue) {
    try {
        var ignored = classValue.get();
        throw new IllegalStateException("Expected a MirroredTypeException to be thrown but got " + ignored);
    } catch (MirroredTypeException e) {
        return e.getTypeMirror();
    }
}

public static List<? extends TypeMirror> mirrorAll(Supplier<Class<?>[]> classValues) {
    try {
        var ignored = classValues.get();
        throw new IllegalStateException("Expected a MirroredTypesException to be thrown but got " + ignored);
    } catch (MirroredTypesException e) {
        return e.getTypeMirrors();
    }
}