A simpler model for repeating annotations

Alex Buckley alex.buckley at oracle.com
Fri Dec 28 12:17:10 PST 2012


Feedback from Java EE architects indicates a strong preference for 100% 
behavioral compatibility for legacy core reflection methods.

Summarizing Mike Keith's comments: 1) getAnnotation(Class) must not 
return one @Foo annotation if multiple @Foo annotations were present in 
source, and 2) getAnnotations() must not return multiple @Foo 
annotations if they present in source.

These requirements imply keeping containment at the heart of 
repeatability. Why? Assume there were no containers, and you could 
simply write:

   @Repeatable @interface Foo {}

such that @Foo @Foo on class A is compiled directly into A.class. This 
scheme would impose high complexity on legacy core reflection methods. 
getAnnotation(Foo.class) would have to read _all_ annotations (declared 
and inherited) to detect multiple @Foo annotations then return null or 
throw an exception. (Remember, returning one of the multiple annotations 
is "wrong".) getAnnotations() would have similar trouble deciding what 
to return if multiple @Foo annotations are found. In essence, the legacy 
core reflection methods are morally incompatible with uncontained 
multiple annotations. The solution is to compile multiple annotations 
with containers, and have the legacy core reflection methods behave 
identically to Java SE 7 (no look through).

To support new reflective clients who understand repeating annotations, 
we can keep the previously-proposed getAnnotations(Class) which looks 
through containers. That is, getAnnotations(Foo.class) returns @Foo @Foo 
because it a) detects that Foo.class is a repeatable annotation type 
with a containing annotation type of FooContainer, then b) looks through 
any @FooContainer present.

It is clear that a repeatable annotation type must still nominate its 
containing annotation type, but the reverse is no longer true. That is, 
the declaration of FooContainer no longer needs to say 
@ContainerFor(Foo.class). This is a direct consequence of dropping look 
through for the legacy core reflection methods. Also, the difficult 
question about calling getAnnotations() on a subclass - should it look 
through an inherited container, or return it? - goes away, as normal 
overriding policy gives good answers.

Here's a simple model that Joe, Joel, and I are happy with:

- @Repeatable(FooContainer.class) on @interface Foo {} causes
   @Foo @Foo in source to be containerized in the class file.
   (Rename @ContainedBy to @Repeatable.)

- The rules for a well-formed relationship between Foo and
   FooContainer remain.

- Core reflection's legacy methods return what's in the class file:
   - get[Declared]Annotation(Foo.class) returns nothing, like today.
   - get[Declared]Annotations() returns @FooContainer, like today.

- Core reflection's new typed methods look through containers:
   - get[Declared]Annotations(Foo.class) returns @Foo @Foo.

- An annotation writer may manually rewrite @FooContainer(...) as
   @Foo @Foo, and it will be containerized with perfect compatibility.

- Language model's legacy methods follow core reflection:
   - Element.getAnnotation(Foo.class) returns nothing, like today.
   - Element[s].get[All]AnnotationMirrors() returns @FooContainer, like 
today

- Language model's new typed method looks through:
   - Element.getAnnotations(Foo.class) returns @Foo @Foo.
   - We may add Element.getAnnotationMirrors(TypeElement) in future.

I think the reduced semantic burden for an annotation type owner (no 
more @ContainedFor) and the stronger compatibility story for an 
annotation reader (no look through for legacy methods) are real 
improvements. We will specify and implement the model over the next month.

Alex



More information about the enhanced-metadata-spec-discuss mailing list