ARTICLE AD BOX
Round 1 of 3 occurs because the initial set of inputs is not empty: javac is being asked to compile at least one file here. Because this file is annotated with a thing p0 is interested in, p0 is added to the set of annotation processors that are part of this compilation process and will be invoked on all future rounds because of it, whether there are any further elements annotated with stuff it is interested in (@X in this example), or not. Why? Because spec says so: From the javadoc of javax.annotation.processing.Processor:
If a processor was asked to process on a given round, it will be asked to process on subsequent rounds, including the last round, even if there are no annotations for it to process.
Round 2 of 3 occurs because source files were generated in round 1.
Stop thinking in terms of your processor and instead think in terms of the compilation process as a whole.
If any annotation processor (whether p0 did so or any other processor that is part of the process did so) generates new source files, that means a new round must occur. The compiler is in that sense itself part of the round system.
Finally, round 3 of 3 occurs because there is always a final round. That final round has no source files and has processingOver = true. If, as part of this round, an AP generates new source files, the filer will throw an exception because that is no longer possible (that would imply the final round can't be the final round and at least 2 more rounds need to happen).
It has to happen separately from round 2 of 3 because javac is not going to combine these:
Some annotation processes could generate more source files in round 2 of 3. With hindsight we can say: Ah, but, that won't happen, but javac cannot know that when it starts round 2. Therefore round 2 is not the final round. The fact that after round 2 is complete, javac can conclude: Aw, crud, I could have made that the final round - does not matter. javac does not have a time machine and cannot go back. Instead it just starts the final round after round 2.
The reason it can do that is because round 2 generated nothing. If round 2 had generated more source files, round 3 would not have been the final round (and there is always a final round, thus, there'd be at least 4 rounds).
Summary
Not sure what mystifies you, but:
If your question is: Why am I being invoked in round 2 when nothing in round 2 is 'interesting' (annotated with @X) to p0?
The answer is: Cuz spec says so. Once you are invoked you continue to be invoked in every round, but with the Set<? extends TypeElement> annotations parameter being the empty set.
If your question is: Why is there a third round when this process could have been done in 2?
Then the answer is: Because of the need to invoke all processors with processingOver() = true, javac needs to know at the start of a round if it is the final round. It cannot know that when round 2 starts (maybe your AP will generate a file even though it is being invoked with zero elements for annotations. You are allowed to do that as per spec. Javac does not know that you won't and is not in the business of trying to solve the halting problem and figure it out by analysing your code). Thus, round 2 is not the final round even though it could have been, and therefore a third round happens.
