How to process annotations with @Target(ElementType.TYPE_USE)?
Asked Answered
A

2

9

I'm implementing an annotation processor to make sure that the elements marked with an annotation are instances of a class that implements a certain interface, or are uses of types that implement a certain interface:

@Documented
@Target(value = { ElementType.PARAMETER, ElementType.TYPE_USE })
@Retention(value = RetentionPolicy.RUNTIME)
public @interface AuditSubject {

}

public interface Auditable {
    // methods that provide data for writing a log entry...
}

public class Report implements Auditable {

}

For the annotated elements, a log entry must be created after method execution (using AOP). Examples:

@CreateLogEntry
public Result persist(@AuditSubject Report newReport) {
    // A log entry must be created based on the incoming 'newReport' instance.    
}

@CreateLogEntry
public UpdateResult<@AuditSubject Report> update(Report update) {
    // A log entry must be created based on the updated report, which is not the same instance as 'update' but an equivalent one.
} 

@CreateLogEntry
public Result persistBatch(List<@AuditSubject Report> batch) {
    // A log entry must be created for each element in 'batch' after this method's execution.
}

The log entries must be created provided that Report implements Auditable; if it does not, a runtime exception is thrown (Yikes, I forgot to implement the interface!). Thus the annotation processor helps to catch programmer mistakes at compile time. So far I've been successful in checking all uses in parameters, but not in type uses. The relevant code from the annotation processor is as follows:

@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
    for (Element annotated : roundEnv.getElementsAnnotatedWith(AuditSubject.class)) {
        // Only prints elements with ElementKind.PARAMETER)!
        this.messager.printMessage(Kind.NOTE, TextUtils.replaceParams("annotated: {} ; Kind : {} ; enclosing : {}", annotated,  annotated.getKind(), annotated.getEnclosingElement()));

        if (annotated.getKind() == ElementKind.PARAMETER) {
            // Code here works as expected, raises errors for annotated parameters of classes that don't implement Auditable.
        } else if (annotated.getKind() == ElementKind.WHAT_TO_USE) {
            // What ElementKind do I need to use here?
        }
    }

    return false;
}

Only annotated elements with kind ElementKind.PARAMETER are recognized (the first line in the loop of process() only prints a single line for 'newReport') How can I check that the annotated types implement Auditable? There's no "ElementKind.TYPE_USE" constant to use. I haven't been able to find any relevant information on this matter. Thanks for your attention.

Awlwort answered 10/2, 2017 at 16:43 Comment(0)
C
4

The Java annotation processing API was designed when Java only supported annotations on declarations. The API only supports visiting declarations, such as fields, methods, and method parameters. It does not visit local variable declarations, nor other annotations within a method body, nor type annotations.

If you wish to process type annotations or annotations within method bodies, you will need to write your own code to recurse on types or to recurse examining the lines of code within a method.

An alternative to this is to use a tool like the Checker Framework. It implements its own visitors, and therefore an annotation processor built on it is invoked for every occurrence of a type annotation.

Commendam answered 10/2, 2017 at 17:9 Comment(0)
S
0

Why not use TYPE_PARAMETER? javax.annotation.processing.Processor API document there is:

An annotation type is considered present if there is at least one annotation of that type present on an element enclosed within the root elements of a round. For this purpose, a type parameter is considered to be enclosed by its generic element. Annotations on type uses, as opposed to annotations on elements, are ignored when computing whether or not an annotation type is present.

javax.lang.model.element.TypeParameterElement
Element getGenericElement()
Returns the generic class, interface, method, or constructor that is parameterized by this type parameter.
Spook answered 1/11, 2019 at 9:50 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.