Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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.

like image 498
jpangamarca Avatar asked Feb 10 '17 16:02

jpangamarca


1 Answers

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.

like image 150
mernst Avatar answered Oct 04 '22 04:10

mernst