What does it mean for an annotation processor to "claim" the empty set of annotations?

6 days ago 3
ARTICLE AD BOX

You've misunderstood the specification.

"Annotation interface" refers to specific annotations, not types. In other words, given 2 source files:

@Foo public class Example1 {} @Foo public class Example2 {}

Those are 2 separate annotations that can be claimed. The term 'annotation interface' is a bit misleading, perhaps, but in context, clear.

The set simply means: You claim every annotation that was handed to you. So, in your implementation of:

boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv)

You need to read this as:

Here are a bunch of annotations for you to handle. They have been placed in a set. Please tell me whether you would like to indicate that you've "done" them. Whatever job the annotations require to be done, if you've done them, then return true, otherwise do not do that. You can only claim "yup, tick the box, those are done" for them all. You cannot mark only half of them as 'done'.

You seem to have read the docs as the mathematical set: That if, say, one process is invoked with {FOO, BAR} (whatever you think FOO and BAR are here), then no other processor will ever be invoked with {FOO, BAR}, but they may be invoked with {FOO, BAR, BAZ}. This interpretation is ludicrous (in that it is utterly non-sensical), and obviously incorrect.

You don't claim 'all of Foo'. You might think it boils down to the same thing, given that your AP will be given all the types with a @Foo annotation on them in the first round, therefore, if you return true, you claim them all, and we might as well state that you have claimed the entire concept of Foo, the type, itself (instead of having claimed each individual annotation handed to you).

But that is provably not how it works - annotations can produce new source files, and those can be annotated with @Foo. If in the first round you get handed a bunch of @Foo annotated source elements and you return true;, then in the second round you may still be invoked with more annotations passed to you via annotations parameter - you'd be invoked with any source types generated in round 1 that were generated with @Foo. If you had return false; instead, in round 2 you'd be handed the union of whatever you got in round 1 + all the types generated in round 1 that are annotated with @Foo.

Thus proving this claiming stuff is about individual annotations instead of the annotation type as a whole.

Should I file a bug with the spec?

No.

In the end, english is not a formal language. A modicum of understanding by the reader is inherently required for any spec to make sense. There is a tradeoff to be made: You can always write specs to be more clear, but the problem with that approach is that the spec then ends up being thousands of pages long. For example, java specs do not include english dictionaries. Nor do they link to a dictionary to be taken as canonical source. Nevertheless, the term "Processes" is used in the javadoc of the process method and never actually explained.

Hence, trivially: This spec is not that kind of 'formal'. You need somebody with some basic understanding to read it for the spec to make sense. If a spec is ambiguous even if read by someone with a reasonable level of familiarity, then you file a bug report. If a spec could be written more succintly and yet also more clearly (a win-win situation), you should file a PR 1.

Point is, the way you read it strikes me as not reasonable: Someone who has read the basics of what the AP system is about and knows basic java should be expected to know that interpretation of the spec is obviously not the intended meaning. Hence, this is not the fault of the spec document (but, given that english itself is subjective to an extent, such an opinion is also inherently subjective. That's part of what I meant by 'not formal': Purely objective, logical arguments can never on their own conclude whether a spec is 'good' or 'bad').

It is important to think about what APIs are trying to accomplish. To get a semantic sense: What does process mean? What does the term claim in the APIdoc mean? What is the point?

Clearly, the idea is twofold:

There's a round system. As long as a source file with some annotation in it that triggers processor X keeps being part of rounds, does that annotation processor that wants to deal with X continue to be included in these rounds? Sometimes, yes: There's a reason there are rounds. If your annotation processor looks at other source content to know what to do, well, some future round might be adding more source content, so, processor X cannot do the job yet. But, also sometimes: No. If X is not dependent on such things, it can just do the job in the first round (let's say: Generate a JSON file with all public static final constants in the class itself, disregarding any such content in supertypes - you do not need to wait for other APs: You know all you need in the very first round). In the latter case, being continually asked to do processing when you've already 'done the thing' is annoying, so you can just return true the moment you've 'done the thing' and you will no longer be called.

Annotation processing may not be cheap and annotation processing is namespaced: Conflicts should not happen. It cannot be that 2 conflicting APs are both trying to process annotation com.foo.whatever.Anno. This only happens if Foo Incorporated has a totally disfunctional team or whatnot. This stuff is namespaced. Therefore if some annotation is 'done', there is no need to waste cycles asking other processors to check them.

With that context in mind, your mathematical set interpretation is ludicrous - spending words in the spec to explain exactly what is meant is just a waste of space, and words in specs are not free. Specs are notoriously hard to unit test, and tend to become unworkably useless once they grow so large nobody can read it in a single sitting and grok the whole thing to spot inconsistencies.

In fact, specs that lack clear semantics tend to be terrible (to make that less subjective: Tend to be specs whose implementations tend to conflict in subtle but annoying ways, that require large tutorials and learning curves, and where the amount of bugs that are (partly) caused by spec issues is larger vs. specs that start off with a pithy clear semantic model and then simply fill in the blanks and make clear some esoteric edge cases).

If anything, then, the problem with the Annotation Spec is that it doesn't spend enough time explaining this 'claiming' model in semantic terms. Javadoc in general doesn't lend itself all too well for that - it would be found, generally, in module/package level javadoc.

Except, the spec more or less does that. The type-level javadoc of Processor itself goes into quite a bit of this. For example:

Note that if a processor supports "*" and returns true, all annotations are claimed. Therefore, a universal processor being used to, for example, implement additional validity checks should return false so as to not prevent other such checkers from being able to run.

That clashes trivially with your interpretation.


[1] Not that you can do that - only OpenJDK consortium members can do that, you'd have to file with azul or whatnot to do such a thing.

Read Entire Article