AnyAnnotation Proposal
Ben Evans
benjamin.john.evans at gmail.com
Mon Feb 6 23:48:38 PST 2012
Hi Reinier,
Is this the kind of proposal that should really happen as an OpenJDK JEP?
Thanks,
Ben
On Tue, Feb 7, 2012 at 12:40 AM, Reinier Zwitserloot
<reinier at zwitserloot.com> wrote:
> # The AnyAnnotation feature
>
> ___v1.0___
>
> Currently, the JLS specifies that only a direct subtype of
> `java.lang.annotation.Annotation`, defined using the `public @interface`
> syntax, is actually an annotation type, and that the return type of any
> member of an annotation declaration can only be an annotation type (or a
> primitive, String, Class, or a 1-dimensional array). It is therefore not
> possible in java 1.7 to create a member of an annotation declaration that
> implies 'any annotation is legal here'.
>
> This proposal addresses the lack of this feature. It also moves java closer
> towards supporting hierarchical annotations (the ability to have one
> annotation declaration extend something other than
> `java.lang.annotation.Annotation` itself). In a nutshell, the proposed
> changes:
>
> * Allow `java.lang.annotation.Annotation` as a valid return type for
> annotation declaration members (in JDK 1.7 this is not legal). Using this
> class as return type means any annotation is intended to be legal.
> * Update the JLS to reflect this change (no changes to javadoc, reflection
> core API, and JVM specification needed).
> * Patch an assert statement in the reflection core API, and 2 lines in
> javac, to support the spec.
> * Add a new loop detection scheme to avoid an endless loop in the `default`
> construction of an annotation declaration member.
> * Expand on the impact this change will have, both in regards to which
> tooling options now become possible and what impact this change can have on
> existing tools.
>
> ## Authors
>
> * Reinier Zwitserloot (r.zwitserloot at projectlombok.org)
> * Roel Spilker (r.spilker at projectlombok.org)
> * Sander Koning (s.koning at projectlombok.org)
> * Robbert Jan Grootjans (r.grootjans at projectlombok.org)
>
> ## Try it right now!
>
> A special try-it-out javac agent is available here:
>
> [
> http://projectlombok.org/anyannotation](http://projectlombok.org/anyannotation)
>
> This agent 'live patches' any javac7 in memory only, so you can start
> experimenting right away with this feature.
>
> ## Changes required in the JDK and associated documentation
>
> ### JVM Specification: Zero changes required
>
> _Based on [Java Virtual Machine Specification SE7][JVMS]_
>
> 1. For annotations themselves: Section 4.7.16 - **The
> RuntimeVisibleAnnotations attribute** through Section 4.7.19 - **The
> RuntimeInvisibleParameterAnnotations attribute** specify how annotations
> should be encoded in the class file. These sections say the only way to
> store an annotation inside an annotation is as a new annotation block, and
> such a block _includes_ the type of the annotation. Thus, in `@Foo(@Bar)`,
> there is always a pointer to the constant pool entry with
> `com.package.Bar`. It is therefore not necessary to rely on the signatures
> available in the `Foo` type to understand its parameter, and therefore no
> change is necessary to the class file format to support the case where
> `@Foo`'s parameter is of type `Annotation`.
>
> 2. For annotation type declarations, the JVMS basically contains no
> specifications. They are just plain interfaces with a bit set
> (`ACC_ANNOTATION` - see section 4.1 `access_flags`). The only new aspect
> (other than the `ACC_ANNOTATION` bit, which is not affected by this
> proposal) is the classfile encoding of the `default` value feature of an
> annotation declaration member, which is described in section 4.7.20 - **The
> AnnotationDefault attribute**. The format of this attribute is explained in
> terms of section 4.7.16's annotation structure, which already supports this
> proposal without any changes required. There is absolutely no mention that
> the value of an _AnnotationDefault_ block has to match the type of the
> `method_info`'s return type (that aspect of the annotation spec is covered
> in the JLS only). In other words, the JVMS doesn't actually consider `int
> foo() default "Hello, World!";` illegal, though javac obviously would
> refuse to emit a class file if you tried it. This means that `Annotation
> onMethod() default @Deprecated;` isn't treated as illegal by the JVMS
> either, and thus the JVMS needs no updates to reflect that this would be a
> legal construct now. There is furthermore no commentary about the fact that
> the return type of an element of an `ACC_ANNOTATION` type can only be a
> primitive, String, Class, an annotation type, or a 1-dimensional array of
> those. Therefore, no comment needs to be added to explain that
> `java.lang.annotation.Annotation` is also legal even though it is not an
> annotation type according to the JLS.
>
> ### JLS Specification: A few changes required
>
> _based on [Java Language Specification SE7][JLS]_
>
> 1. 9.6 - **Annotation Types** This introductory section describes the
> actual declaration of an annotation type; no changes needed.
>
> 2. 9.6.1 - **Annotation Type Elements** requires two changes:
>
> _old:_
>
> It is a compile-time error if the return type of a method declared in an
> annotation
> type is not one of the following: a primitive type, String, Class, any
> parameterized
> invocation of Class, an enum type (S8.9), an annotation type, or an array
> type
> (S10) whose element type is one of the preceding types.
>
> _new:_
> It is a compile-time error if the return type of a method declared in an
> annotation
> type is not one of the following: a primitive type, String, Class, any
> parameterized
> invocation of Class, an enum type (S8.9), an annotation type,
> `java.lang.annotation.Annotation`,
> or an array type (S10) whose element type is one of the preceding types.
> [smaller]
> If the return type of an annotation method is declared to be
> `java.lang.annotation.Annotation` (or its array), any annotation (S9.7) is
> a valid value:
> @interface Getter {
> Annotation[] onMethod() default {@NonNull};
> }
> [/smaller]
> _old:_
> It is a compile-time error if an annotation type declaration T contains an
> element
> of type T, either directly or indirectly.
> [smaller]
> For example, this is illegal:
> @interface SelfRef { SelfRef value(); }
> and so is this:
> @interface Ping { Pong value(); }
> @interface Pong { Ping value(); }
> [/smaller]
> _new (optional change; add an example to clarify):_
> [smaller]
> For example, this is illegal:
> @interface SelfRef { SelfRef value(); }
> and so is this:
> @interface Ping { Pong value(); }
> @interface Pong { Ping value(); }
> as is this:
> @interface SelfRef { Annotation value() default @SelfRef; }
> [/smaller]
>
> 3. 9.6.2 - **Defaults for Annotation Type Elements** requires no changes.
> The relevant paragraph reads:
>
> It is a compile-time error if the type of the element is not commensurate
> (S9.7) with
> the default value specified.
>
> After applying the required changes to sections S9.6.1 and S9.7.1, this
> statement is still valid.
>
> 4. 9.6.3 - **Predefined Annotation Types** requires no changes.
>
> 5. 9.7 - **Annotations** requires no changes (this is an introduction
> section).
>
> 6. 9.7.1 - **Normal Annotations** requires one change and one optional
> change.
>
> The production rule for _ElementValue_ does not need changing because it
> already mentions it can consist of an _Annotation_.
> _old_:
> The return type of this method defines the element type of the
> element-value pair.
> _new_:
> The return type of this method defines the element type of the
> element-value pair.
> If the return type is `java.lang.annotation.Annotation`, any annotation is
> a valid
> value.
> _old_:
> The type of V is assignment compatible (S5.2) with T, and furthermore:
> * If T is a primitive type or String, and V is a constant expression
> (S15.28).
> * V is not null.
> * If T is Class, or an invocation of Class, and V is a class literal
> (S15.8.2).
> * If T is an enum type, and V is an enum constant.
> _append an item to the list as clarification:_
> * If T is `java.lang.annotation.Annotation`, and V is an annotation.
>
> 7. 9.7.2 - **Marker Annotations** only describes a shorthand notation;
> needs no changes.
>
> 8. 9.7.3 - **Single-Element Annotations** only describes a shorthand
> notation; needs no changes.
>
> 9. 13.5.7 - **Evolution of Annotation Types** needs no changes.
>
> ### Changes to javadoc of existing java.* API: No changes required
>
> 1. method `java.lang.reflect.Method.getDefaultValue()`'s javadoc does not
> mention anything about annotation member's return types being restricted to
> a subset of legal types, therefore no update to include
> `java.lang.annotation.Annotation` in this subset is required.
>
> 2. `java.lang.Class.isAnnotation()` (and `getModifiers() &
> java.lang.reflect.Modifier.ANNOTATION`) is the only aspect of `Class` which
> is different / relevant for annotation types, and it also makes no mention
> of the return type limitations.
>
> 3. Existing annotations in the java base library itself (`@Override`,
> `@Deprecated`, etc - listed in JLS sections 9.6.3.1-9.6.3.7) do not have
> any methods which are an annotation type, nor do any of these types seem
> like they could use one. No updates are required or suggested for any of
> them.
>
> 4. The javadoc on `java.lang.annotation.Annotation` itself remains valid.
> It might be prudent to expand it with a section that explains that it can
> be used as a return type for an annotation method, but the other legal
> return types for annotation declaration members don't have this either.
> Therefore, for consistency's sake, this proposal does not include a change
> to this javadoc.
>
> ### Changes / additions to any of the method signatures of the reflection
> API or any other part of java base: No changes required
>
> * `java.lang.reflect.Method.getDefaultValue()` already returns
> `java.lang.Object` and thus needs no changes.
>
> * No new API is required to reflectively determine that a given annotation
> declaration member's return type is `Annotation`, because the way this
> return type is reflected is via a `java.lang.Class` return type, which is
> already capable of conveying `Annotation` as a value. This part is one of
> the few ways existing tools might break, as they may erroneously assume
> this return value can only be `java.lang.Class.class`,
> `java.lang.String.class`, any of the primitive wrappers, or a type which is
> an annotation type. This method could now also return
> `java.lang.annotation.Annotation` which is not itself an annotation type.
>
> * No new API is required to reflectively read out annotation values, as
> these will still be specific instances of annotations.
>
> * No new API is required to reflectively read out annotation defaults, as
> these, if present, will still be specific instances of annotations.
>
> ### Changes / additions to JVM library reflection core (java.*): No changes
> needed
>
> 1. `java.lang.reflect.Method.getDefaultValue()` delegates work to internal
> implementations and does not contain any code that would cause issues with
> a `java.lang.annotation.Annotation` return type. It does first acquire the
> return type of the method and then asks for an instance of the value that
> 'fits' this return type, but it leaves all checking to the internal
> implementations that provide both of these values (both the return type and
> an instance for the value given the byte array containing the raw
> `annotationDefault` data). The work is deferred to
> `sun.reflect.annotation.AnnotationParser` and
> `sun.reflection.annotation.AnnotationType`, which do need patches (see
> below).
>
> 2. `java.lang.reflect.Method.getAnnotation(java.lang.Class
> annotationClass)` defers work to `sun.reflect.annotation.AnnotationParser`
> and `sun.reflect.annotation.AnnotationType` as well. Same for
> `java.lang.reflect.Field.getAnnotation`, and
> `java.lang.Class.getAnnotation`. `java.lang.Package.getAnnotation` is a
> wrapper around a dummy `java.lang.Class` instance that holds annotation
> data (and thus, it too defers the work). These internal helpers do need
> patches (see below).
>
> ### Changes / additions to internal support classes used by reflective
> core: One tiny change needed to a system assertion
>
> 1. `sun.reflect.annotation.AnnotationType` - no changes necessary.
>
> 2. `sun.reflect.annotation.AnnotationInvocationHandler` - no changes
> necessary. In particular, the `equals()` implementation does not use the
> return type, only member values; all special handling defaults to
> `equals()`, `toString()`, `hashCode()` etc of the member value if the
> member value is an annotation type. annotation instances already have
> working `equals`, `hashCode`, `toString`, etc implementations, therefore no
> changes are necessary.
>
> 3. `sun.reflect.annotation.AnnotationParser` - Minor changes necessary.
>
> _based on [Revision 9b8c96f96a0f of
> AnnotationParser.java][AnnotationParser]_
>
> * method `parseMemberValue` does NOT provide the expected type (parameter
> `memberType`) to the `parseAnnotation` helper method. Therefore, the fact
> that expected type is a previously invalid value
> (`java.lang.annotation.Annotation`) does not have any effect on parsing the
> annotation in the class file data.
>
> * method `parseSig` does not check if the provided type is an annotation
> type. (It really can't, as the type is not guaranteed to be on the
> classpath, and therefore it has no way of knowing if the provided type is
> actually an annotation type. Nevertheless, the code does indeed include no
> such check).
>
> * method 'parseArray' (helper of `parseMemberValue`) contains an assertion
> (which can be enabled with the `-esa` javac option) on line 485 which needs
> updating:
>
> - assert componentType.isAnnotation();
> + assert componentType.isAnnotation() || componentType ==
> java.lang.annotation.Annotation.class;
>
> ### Changes to javac: Some changes needed
>
> * javac checks that an annotation declaration member's return type is one
> of the allowed types. This check needs to be extended to consider
> `java.lang.annotation.Annotation` a legal return type value as well. No
> change in the way javac builds the class file to represent the annotation
> declaration is required:
>
> In com.sun.tools.javac.comp.Check:2267
> _based on [Revision ce654f4ecfd8 of Check.java][Check]_
>
> - if ((type.tsym.flags() & Flags.ANNOTATION) != 0) {
> + if ((type.tsym.flags() & Flags.ANNOTATION) != 0 || ||
> types.isSameType(type, syms.annotationType)) {
>
> * Loop detection: It is now possible to create 'loops' in default values,
> where the default value of an annotation is itself, or, indirectly, some
> other annotation, one of whose methods contains a default value that points
> back itself. Prior to the introduction of this feature, the rule that the
> return types of annotation methods cannot contain a cyclic reference would
> make it impossible for the default value to contain such a loop, but now
> this is no longer true, so a separate loop detection scheme needs to be
> implemented for default values.
>
> NB: The `checkAnnotationResType` method has been renamed in this patch to
> `checkAnnotationElementType` because it is now no longer used just to check
> return types, but also to check the types in a `default` value.
>
> This change is a bit more involved and thus the full patch is listed here
> in posix diff format:
>
> Full patch of com.sun.tools.javac.comp.Check:
> _based on [Revision ce654f4ecfd8 of Check.java][Check]_
>
> 29d28
> < import java.util.Set;
> 33a33,34
> > import com.sun.tools.javac.tree.JCTree.JCAnnotation;
>> import com.sun.tools.javac.tree.JCTree.JCExpression;
> 38a40
>> import com.sun.tools.javac.code.Attribute.Array;
> 40a43
> > import com.sun.tools.javac.code.Type;
> 2267c2270
> < if ((type.tsym.flags() & Flags.ANNOTATION) != 0) return;
> ---
>> if ((type.tsym.flags() & Flags.ANNOTATION) != 0 ||
> types.isSameType(type, syms.annotationType)) return;
> 2497c2500,2501
> < checkAnnotationResType(meth.pos(), meth.restype.type);
> ---
>> checkAnnotationElementType(meth.pos(), meth.restype.type);
> > checkNonCyclicAnnotationDefaultValues(meth);
> 2518c2522,2523
> < checkAnnotationResType(pos,
> ((MethodSymbol)s).type.getReturnType());
> ---
> > checkAnnotationElementType(pos,
> ((MethodSymbol)s).type.getReturnType());
>> checkNonCyclicAnnotationDefaultValues(pos,
> (MethodSymbol)s);
> 2526c2531
> < void checkAnnotationResType(DiagnosticPosition pos, Type type) {
> ---
>> void checkAnnotationElementType(DiagnosticPosition pos, Type type) {
> 2533c2538
> < checkAnnotationResType(pos, types.elemtype(type));
> ---
>> checkAnnotationElementType(pos, types.elemtype(type));
> 2539a2545,2579
> > private void checkNonCyclicAnnotationDefaultValues(JCMethodDecl
> meth) {
>> if (!isAnnotationType(meth.restype.type)) return;
> > if (meth.defaultValue == null) return;
>> meth.defaultValue.accept(new TreeScanner() {
> > @Override public void visitAnnotation(JCAnnotation tree) {
>> checkAnnotationElementType(tree.pos(), tree.type);
> > super.visitAnnotation(tree);
>> }
>> });
> > }
>>
>> private void checkNonCyclicAnnotationDefaultValues(final
> DiagnosticPosition pos, MethodSymbol meth) {
> > if (!isAnnotationType(meth.type.getReturnType())) return;
>> if (meth.defaultValue == null) return;
> > if (meth.defaultValue.type.tag == TypeTags.ARRAY) {
>> for (Attribute a : ((Array)meth.defaultValue).values) {
> > checkAnnotationElementType(pos, a.type);
>> }
> > }
>> else {
>> checkAnnotationElementType(pos, meth.defaultValue.type);
> > }
>> }
>>
> > private boolean isAnnotationType(Type type) {
>> switch (type.tag) {
> > case TypeTags.CLASS:
>> return types.isSameType(type, syms.annotationType);
> > case TypeTags.ARRAY:
>> return types.isSameType(types.elemtype(type),
> syms.annotationType);
> > default:
>> return false;
>> }
> > }
>>
>
> * The error message with key 'cyclic.annotation.element' doesn't need
> changing.
>
> * javac checks that an annotation's parameter is type compatible with the
> annotation's declaration. It does this using an 'assignment compatible'
> check, which will work fine with `java.lang.annotation.Annotation` as
> return type of the annotation declaration member method. However, this
> check is entered using an `if` statement which needs to be expanded:
>
> In com.sun.tools.javac.comp.Annotate:224
> _based on [Revision ce654f4ecfd8 of Annotate.java][Annotate]_
>
> - if ((expected.tsym.flags() & Flags.ANNOTATION) != 0) {
> + if ((expected.tsym.flags() & Flags.ANNOTATION) != 0 ||
> types.isSameType(expected, syms.annotationType)) {
>
> * No other changes are required. The error message with key
> `invalid.annotation.member.type` may need to be expanded to explain that
> `Annotation` is also a legal type.
>
> ### Changes to javax.lang.model: Some changes needed
>
> * javax.lang.model mostly requires no changes, except for the feature where
> one can ask javax.lang.model to create an instance of a given annotation
> class, i.e.:
>
> for (Element elem : roundEnv.getRootElements()) {
> SomeAnnotation instanceOfAnnotation =
> elem.getAnnotation(SomeAnnotation.class);
> }
>
> The implementation of this feature in OpenJDK's javac obtains
> `java.lang.Class` instances (needed to create the proxies) entirely from
> traversing `SomeAnnotation.class` using reflection. As the return types of
> the methods in `SomeAnnotation.class` would be
> `java.lang.annotation.Annotation`, this mechanism is no longer useful.
> Instead, the 'flat name' of the annotation argument itself needs to be
> turned into a `java.lang.Class` by using `Class.forName()`. Also, it is now
> possible for an annotation argument to contain an annotation that is not on
> the classpath of the annotation processor. The right approach is for this
> annotation instance to throw a `TypeMirrorException` as late as is feasible
> (when the annotation method is invoked that would have to return an
> instance of a type that is not available, but not when i.e. `toString()` is
> invoked). A full patch to the appropriate class is listed here (note that
> the alternate strategy of using `Class.forName` is only used for the new
> feature; existing annotations that do not use it are still created by
> traversing the annototation type via reflection):
>
> In com.sun.tools.javac.model.AnnotationProxyMaker
> _based on [Revision ce654f4ecfd8 of
> AnnotationProxyMaker.java][AnnotationProxyMaker]_
>
> 62c62,64
> < private final Class<? extends Annotation> annoType;
> ---
>> private Class<? extends Annotation> annoType;
> > private final Class<?> context;
>> private ClassLoader classLoader;
> 66c68
> < Class<? extends Annotation> annoType) {
> ---
>> Class<? extends Annotation> annoType,
> Class<?> context) {
> 68a71
>> this.context = context;
> 77,78c80
> < AnnotationProxyMaker apm = new AnnotationProxyMaker(anno,
> annoType);
> < return annoType.cast(apm.generateAnnotation());
> ---
>> return annoType.cast(generateAnnotationInner(anno, annoType,
> annoType, null));
> 80a83,88
>> private static Object generateAnnotationInner(
>> Attribute.Compound anno, Class<? extends Annotation>
> annoType, Class<?> context, ClassLoader classLoader) {
> > AnnotationProxyMaker apm = new AnnotationProxyMaker(anno,
> annoType, context);
>> apm.classLoader = classLoader;
> > return apm.generateAnnotation();
>> }
> 81a90,100
> > private ClassLoader getAnnotationClassLoader() {
>> if (classLoader == null) {
> > ClassLoader cl = context.getClassLoader();
>> // Line above Could cause security exception, but
> > // no other part of javac uses doPrivileged;
>> // in particular line 259 of AnnotationParser also doesn't
> bother,
> > // and is in the same boat.
>> this.classLoader = cl != null ? cl :
> ClassLoader.getSystemClassLoader();
> > }
>> return classLoader;
>> }
> 85c104,116
> < private Annotation generateAnnotation() {
> ---
> > @SuppressWarnings("unchecked")
>> private Object generateAnnotation() {
> > if (annoType == Annotation.class) {
>> try {
>> Class<? extends Annotation> clazz = (Class<? extends
> Annotation>)
> >
> getAnnotationClassLoader().loadClass(anno.type.tsym.flatName().toString());
>> annoType = clazz;
> > return AnnotationParser.annotationForMap(clazz,
>> getAllReflectedValues());
> > } catch (ClassNotFoundException e) {
>> return new MirroredTypeExceptionProxy(anno.type);
> > }
>> }
> 170a202
> >
> 239c271
> < value = generateAnnotation(c, nested);
> ---
>> value = generateAnnotationInner(c, nested, context,
> classLoader);
>
> ## Source and binary compatibility
>
> This proposal introduces no new or changed behaviour for any source code
> which is legal today, and no changes to the class file format. Therefore,
> existing source files which compile on javac 1.7 are not affected.
>
> Existing tools also notice no changes for existing source and class files.
> However, newly compiled annotation declarations may now start using
> `java.lang.annotation.Annotation` as return type for members, and some
> existing tools may have assumed the legal set of return values couldn't
> change. These tools will need to be updated.
>
> Technically, it is possible for existing annotations to be 'widened' - any
> return types which used to be a specific annotation type can be generalized
> to `java.lang.annotation.Annotation`, but this might cause problems with
> consumers of this annotation. The same problem occurs for any library
> update where signatures are changed, however.
>
> ## Use cases for this feature
>
> One obvious use case for annotations is generating code. If this feature is
> added to the JDK, it is possible to specify a list of annotations that
> should be put in the generated code. For example, an annotation that will
> generate a POJO with implementations for `equals`, `hashCode`, et cetera:
>
> @GeneratePOJO
> @AddAnnotations(onClass=@SuppressWarnings("all"))
> public class StudentTemplate {
> @AddAnnotations(onGetter={@NonNull, @javax.persistence.Id})
> private int unid;
> }
>
> This feature also takes a step towards allowing hierarchical annotation
> definitions (where an annotation extends another annotation type, instead
> of `java.lang.annotation.Annotation`.
>
> ## Testing the feature
>
> These patches, plus a test suite as well as a way to build a 'live patching
> agent' which allows experimenting with these patches, are available here:
>
> * [github repository](http://github.com/rspilker/jdk-proposal.anyannotation)
> - source repository
> * [live agent](http://projectlombok.org/anyannotation) - direct download of
> the agent which allows immediate experimentation with any javac7.
>
> [JVMS]: http://docs.oracle.com/javase/7/specs/jvms/JVMS-JavaSE7.pdf
> [JLS]: http://docs.oracle.com/javase/7/specs/jls/JLS-JavaSE7.pdf
> [AnnotationParser]:
> http://hg.openjdk.java.net/jdk7/jdk7/jdk/file/9b8c96f96a0f/src/share/classes/sun/reflect/annotation/AnnotationParser.java
> [Check]:
> http://hg.openjdk.java.net/jdk7/jdk7/langtools/file/ce654f4ecfd8/src/share/classes/com/sun/tools/javac/comp/Check.java
> [Annotate]:
> http://hg.openjdk.java.net/jdk7/jdk7/langtools/file/ce654f4ecfd8/src/share/classes/com/sun/tools/javac/comp/Annotate.java
> [AnnotationProxyMaker]:
> http://hg.openjdk.java.net/jdk7/jdk7/langtools/file/ce654f4ecfd8/src/share/classes/com/sun/tools/javac/model/AnnotationProxyMaker.java
>
More information about the coin-dev
mailing list