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