Type Annotations in core reflection + language model APIs
Alex Buckley
alex.buckley at oracle.com
Thu Sep 13 12:29:06 PDT 2012
Experts,
JSR 308 must model annotations on type uses in both of the reflection
APIs supported by Java SE: core reflection (java.lang and
java.lang.reflect) and the language model (javax.lang.model.** defined
by JSR 269).
Both reflection APIs are limited to _declarations_ outside code blocks.
For example, they allow reflection over the declarations of a method's
formal parameters, but not over the declarations of local variables
within a method. Even though JSR 308 is positioned as concerning type
_uses_, many type uses occur within declarations outside code blocks!
For example, a method declaration includes a return type, on which JSR
308 allows an annotation separate from an annotation on the method
declaration itself.
In other words, the use of a type is often made in the context of a
declaration. It is relatively straightforward to extend the reflection
APIs so that existing entities which represent declarations - such as
java.lang.reflect.Method and javax.lang.model.element.TypeElement -
expose more information about types used within the declarations.
Namely, any annotations on those type uses.
This mail proposes such extensions without upsetting the overall
structure of the reflection APIs. Your comments are most welcome.
In what follows, it is useful to know that:
a) By policy, core reflection does not depend on the rest of the Java SE
API, and so uses array types as aggregates. The language model has no
such constraint, and uses java.util.List and java.util.Set widely.
b) The locations where JSR 308 allows annotations are summarized at
http://types.cs.washington.edu/jsr308/specification/java-annotation-design.html#class-file:ext:target_type.
The "externally visible" targets are precisely the contexts representing
declarations.
c) Where reference is made to repeating annotations, the relevant
specification is http://cr.openjdk.java.net/~abuckley/8misc.pdf as
discussed on the comment-and-evaluation-licensed (not GPL) mailing list
"enhanced-metadata-spec-discuss" at openjdk.java.net.
* Core reflection
Core reflection offers a number of methods for exposing types, such as
Class.getSuperclass(), Field.getType(), and Method.getReturnType(). All
return Class objects, and Class implements AnnotatedElement.
Unfortunately, this is not helpful for JSR 308. A Class object
represents a class declaration, so its AnnotatedElement methods expose
annotations on that declaration.
For example, suppose a field is declared as "@Foo String f;" where Foo
is a TYPE_USE annotation. You call Field.getType() to obtain a Class
object which represents the String class. Since the declaration of
java.lang.String has no annotations, calling getAnnotations() on this
Class object will return nothing. The _use_ of String's name as a type
in a field declaration is not known to the Class object.
Let us turn to the "Generic" methods in core reflection:
Class.getGenericSuperclass(), Field.getGenericType(),
Method.getGenericReturnType(). All return Type objects. Perhaps Type
should implement AnnotatedElement? Again, not helpful for JSR 308. Type
objects are essentially wrappers for Class objects, so can expose only
annotations on class declarations.
For example, suppose a field is declared as "List<? extends @Foo
String>" where Foo is a TYPE_USE annotation. You call
Field.getGenericType() to obtain a Type object, then downcast to
ParameterizedType to call further methods which return Type objects, and
so on, until eventually you obtain a Type object which is instanceof
Class (specifically, the String class). Again, the annotations available
from this Type object (when downcast to Class) are annotations on the
declaration of java.lang.String, not on uses of String's name in the
field declaration.
(Sidebar: It would be possible for Type to implement AnnotatedElement so
that Field.getGenericType() et al could be used to obtain
declaration-site annotations. Type has not been retrofitted in this way
because the Class objects returned by Field.getType() et al already do
the job.)
Since Class and Type are not suitable locations for methods that
directly expose annotations on type uses, there must instead be new
methods on Class, Field, Method, and Constructor that return objects
which in turn expose annotations on type uses. Here are those methods:
// Class extends/implements
Class.getAnnotatedSuperclass() -> AnnotatedType
Class.getAnnotatedInterfaces() -> AnnotatedType[]
// Field type
Field.getAnnotatedType() -> AnnotatedType
// Method return / receiver / parameter / exception types
Method.getAnnotatedReturnType() -> AnnotatedType
Method.getAnnotatedReceiverType() -> AnnotatedType
Method.getAnnotatedParameterTypes() -> AnnotatedType[]
Method.getAnnotatedExceptionTypes() -> AnnotatedType[]
// Constructor result / receiver / parameter / exception types
Constructor.getAnnotatedReturnType() -> AnnotatedType
Constructor.getAnnotatedReceiverType() -> AnnotatedType
Constructor.getAnnotatedParameterTypes() -> AnnotatedType[]
Constructor.getAnnotatedExceptionTypes() -> AnnotatedType[]
What is AnnotatedType? First, we recognize that AnnotatedElement has
grown more complex thanks to repeating annotations, so we should always
prefer to inherit from AnnotatedElement than duplicate its methods.
Then, each kind of java.lang.reflect.Type entity which may appear in a
declaration (e.g. as a superclass, or a type parameter bound, etc) gets
a friend, Annotated*Type*, which exposes annotations:
package java.lang.reflect;
interface AnnotatedType extends AnnotatedElement {}
interface AnnotatedGenericArrayType extends AnnotatedType {
AnnotatedType getAnnotatedGenericComponentType();
}
interface AnnotatedParameterizedType extends AnnotatedType {
AnnotatedType[] getAnnotatedActualTypeArguments();
}
interface AnnotatedTypeVariable extends AnnotatedType {
AnnotatedType[] getAnnotatedBounds();
}
interface AnnotatedWildcardType extends AnnotatedType {
AnnotatedType[] getAnnotatedLowerBounds();
AnnotatedType[] getAnnotatedUpperBounds();
}
In addition to annotations on type uses, JSR 308 allows annotations on
the declarations of type parameters of generic classes and methods. Core
reflection already exposes the declarations of type parameters:
Class.getTypeParameters() -> TypeVariable[]
Method.getTypeParameters() -> TypeVariable[]
so it is appropriate to make TypeVariable implement AnnotatedElement in
order to expose annotations on the declaration. In fact, TypeVariable
has implemented AnnotatedElement since early builds of JDK8, though the
implementation is currently a no-op.
Finally, JSR 308 allows annotations on the bounds of type parameters.
This is a true "type use" location, in contrast to the declaration of
the type parameter itself. Since TypeVariable.getBounds() returns Type
objects, we add a new method which returns AnnotatedType objects:
TypeVariable.getAnnotatedBounds() -> AnnotatedType[]
* Language model
(In what follows, the following "equivalences" between core reflection
and the language model may be helpful:
j.l.Class ~ javax.lang.model.element.TypeElement
j.l.r.Field ~ javax.lang.model.element.VariableElement
j.l.r.Method/Ctor ~ javax.lang.model.element.ExecutableElement
j.l.r.TypeVariable ~ javax.lang.model.element.TypeParameterElement
j.l.r.Annotation ~ javax.lang.model.element.AnnotationMirror
j.l.r.Type ~ javax.lang.model.type.TypeMirror
j.l.r.AnnotatedType ~ javax.lang.model.type.AnnotatedTypeMirror )
As in core reflection, we add methods to Element subclasses to reveal
the annotations on type uses which occur within declarations.
While it is attractive to add methods to TypeMirror and its subclasses
to expose annotations on type uses, we cannot be sure about the
identities of TypeMirror objects. For example, two field declarations
"@Foo String f1; @Bar String f2;" are represented as two VariableElement
objects, but their asType() methods may return the same TypeMirror
object for "String".
Therefore, we introduce AnnotatedTypeMirror as a parallel hierarchy to
TypeMirror, similar to how j.l.r.AnnotatedType is parallel to
j.l.r.Type. AnnotatedTypeMirror exposes annotation mirrors on a ground
type, similar to how j.l.r.AnnotatedType offers getAnnotations() for a
ground type. The subtypes of AnnotatedTypeMirror represent Java's
structural types - parameterized types, array types, type variables, and
wildcard types - similar to the subtypes of j.l.r.AnnotatedType.
// Notation: ATM == AnnotatedTypeMirror
// Notation: L<? ext ...> == List<? extends ...>
// Class extends/implements
TypeElement.getAnnotatedSuperclass() -> ATM
TypeElement.getAnnotatedInterfaces() -> L<? ext ATM>
// Field type
VariableElement.getAnnotatedType() -> ATM
// Method/ctor return / receiver / parameter / exception types
ExecutableElement.getAnnotatedReturnType() -> ATM
ExecutableElement.getAnnotatedReceiverType() -> ATM
ExecutableElement.getAnnotatedParameterTypes() -> L<? ext ATM>
ExecutableElement.getAnnotatedThrownTypes() -> L<? ext ATM>
package javax.lang.model.type;
interface AnnotatedTypeMirror {
List<? extends AnnotationMirror> getAnnotationMirrors();
}
interface AnnotatedArrayTypeMirror extends AnnotatedTypeMirror {
AnnotatedArrayTypeMirror getAnnotatedComponentType();
}
interface AnnotatedDeclaredTypeMirror extends AnnotatedTypeMirror {
List<? extends AnnotatedTypeMirror> getAnnotatedTypeArguments();
}
interface AnnotatedTypeVariableMirror extends AnnotatedTypeMirror {
List<? extends AnnotatedTypeMirror> getAnnotatedLowerBound();
List<? extends AnnotatedTypeMirror> getAnnotatedUpperBound();
}
interface AnnotatedWildcardTypeMirror extends AnnotatedTypeMirror {
List<? extends AnnotatedTypeMirror> getAnnotatedExtendsBound();
List<? extends AnnotatedTypeMirror> getAnnotatedSuperBound();
}
(Notice that ExecutableType, NoType, NullType, and PrimitiveType have no
counterpart in this hierarchy. First, ExecutableType is obtained from
ExecutableElement.asType(), but we are not adding
ExecutableElement.asAnnotatedType() which would necessitate
AnnotatedExecutableTypeMirror. Second, NoType and NullType represent
pseudo-types which cannot be annotated. Third, PrimitiveType has no
structural elements, so an annotation on a primitive type is simply
retrieved with AnnotatedTypeMirror.getAnnotationMirrors().)
For annotations on declarations of type parameters, the language model
already exposes the declarations of type parameters:
TypeElement.getTypeParameters() -> L<? ext TypeParameterElement>
ExecutableElement.getTypeParameters() -> L<? ext TypeParameterElement>
TypeParameterElement has getAnnotationMirrors() from Element, and the
implementation just needs to be filled in. (Contrast with core
reflection, we needed to make TypeVariable implement AnnotatedElement.)
Finally, annotations on the bounds of type parameters are exposed by a
new method on TypeParameterElement, akin to that on j.l.r.TypeVariable:
TypeParameterElement.getAnnotatedBounds() -> L<? ext ATM>
* End *
More information about the type-annotations-spec-experts
mailing list