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