6

I would like to write an annotation processor that generates source code based on the set of JavaBeans properties of processed types.

This works in general, but I am struggling with doing so correctly if other annotation processors are around. Specifically, such other processor may generate a super-class for a type processed by my processor, so I need to take that super-type's properties into account as well. In a subsequent round, a super-class for that super-class may be generated and so on.

This means I must not generate my source code until the hierarchy of the type I am interested is stable, i.e. no further super-types will be generated in subsequent rounds (or in the same round, after my processor has been run) by other processors.

How may I find out if that is the case? I am aware of RoundEnvironment#processingOver() and the possibility of generating my code in that last final round, but I understand this to be a bad practice (the compiler will issue a warning).

Gunnar
  • 18,095
  • 1
  • 53
  • 73
  • Your answerer asked "Can you please provide more details on which other annotation processor come into play and which annotation it uses?" in the answer. I think you want to be robust against all other processors, but you didn't explicitly say so. – Jeffrey Bosboom Dec 15 '15 at 06:29
  • When you say you want to trigger source generation based on the set of JavaBeans properties, I assume you mean pairs of getter/setter (plus change handler?). Am I correct? – Arnaud Tournier Dec 15 '15 at 06:30
  • @JeffreyBosboom Yes, exactly, this question is about correct processor design in general, it's not tied to specific ones. The one I am writing should be working together with others, without knowing them or making any assumptions about their behavior. – Gunnar Dec 15 '15 at 07:29
  • @ArnaudTournier Yes, I mean getter/setter pairs, but that's not fundamental to the question. Really I want to generate code on the entirety of an annotated type's hierarchy, which may be amended by other processors and I am looking for a pattern to reliably do so. – Gunnar Dec 15 '15 at 07:33
  • So my only idea is to declare your processor as processing all classes (with the wildcard). But fundamentally, JSR 269 is not thought to coordinate nor to ease collaboration between processors. Maybe best person to ask to would be Joe Darcy who conceived this API. – Arnaud Tournier Dec 15 '15 at 07:54

2 Answers2

3

To answer my own question:

An annotated type can be considered stable or complete if all its super-types are non-erroneous. An example:

@GenClass("Base")
class MyAnnotatedType extends Base {}

Let's assume there is one annotation processor A for @GenBase which generates the specified class, Base in this case. And another processor B is interested in the entirety of MyAnnotatedType's hierarchy, e.g. it wants to generate some kind of descriptor for all of MyAnnotatedTypemethods, including inherited ones.

If B runs before A, the Base class will not yet exist, so when B examines the hierarchy of MyAnnotatedType, the super-class type mirror will have type kind ERROR. B can take this as indication to defer the handling of MyAnnotatedType to a later round.

As A runs, it will generate the Base class, leading to another processing round.

If now B runs the second time, it can process all the types deferred from the previous round. As Base exists now, it will not have type kind ERROR any more. For that I noticed (using javac) that it is important to get a fresh Element representing the type and not keep the one from the first round which still contains the reference to the erroneous super-type.

If Base has no erroneous super-types itself, the hierarchy for MyAnnotatedType is complete and B can proceed to handle it. Otherwise, the processing would again have to be deferred until the hierarchy finally is complete. If a super-type never is generated, the compilation will end up with an error anyways, for B it should be alright to not generate its code in this case, too.

Gunnar
  • 18,095
  • 1
  • 53
  • 73
  • I get your idea, but your processor might not be called by javac after the round where the other processor runs because this processor will not generate a class annotated with your annotation. So this would again involve declaring your processor as processing the "*" annotation in order to be called on each round where new classes are generated. What do you think ? – Arnaud Tournier Dec 16 '15 at 09:01
  • Yes, probably it should be declared to process "*" to be on the safe side. Although I see (with javac) my processor also is invoked in the second round if its limited to a specific annotation type and the newly generated files don't have that annotation. The set of annotations passed to process is empty in that case. So I will not process any new files in that round, only the ones scheduled for deferred processing from the previous round. – Gunnar Dec 16 '15 at 11:32
  • Yes it is confirmed by the javac source code ! it reads in an internal comment of the JavacProcessingEnvironment : *If a processor has been used on a prior round, its process method is called on all subsequent rounds, perhaps with an empty set of annotations to process.* That's on line 500 of the JavacProcessingEnvironment.java file in the jdk7 langtools repository. – Arnaud Tournier Dec 16 '15 at 20:40
0

When you say you want to trigger source generation based on the set of JavaBeans properties, I assume you mean pairs of getter/setter, anyway that's not your question's topic.

Generally the JSR 269 specification is not made so that several annotation processors can be coordinated. This is by design, and all processors should be idempotent (meaning same entry -> same output).

So things like "I must not generate my source code until the hierarchy of the type is stable" is just not possible to achieve without hacking the legal process. It is javac's responsibility to detect stability.

Generating code in the last round is forbidden because the only aim of the last round is for your processor to free unreleased resources. If other processors do the same thing, you are in danger! So that's a no-go for me.

The only possible solution i can think about would be to declare that your processor is processing all annotations (that's possible by specifying "*" as your processed annotation). I am not sure if that would not block the other processor, that has to be verified. But if that works, your processor would be called each time new files are generated. You would then process them normally and the convergence thing would be detected normally by javac.

I am not 100% sure about those this solution, but they might be worth a try.

Arnaud Tournier
  • 984
  • 11
  • 11
  • To ask for clarification on a question, comment on the question. "Thanks" is not necessary here; upvote questions you like. – Jeffrey Bosboom Dec 15 '15 at 06:27
  • Thanks for your answer; Regarding "things like 'I must not generate my source code until the hierarchy of the type is stable' is just not possible to achieve", I don't think that's true actually (it'd be a severe flaw in the JSR 269 design); See my own answer for a possible way. – Gunnar Dec 16 '15 at 08:13