8005294: Consider default methods for additions to AnnotatedElement
authordarcy
Wed, 30 Oct 2013 17:27:25 -0700
changeset 21419 a842a3bbee57
parent 21418 88cb3367643e
child 21420 a56f40ab71ce
8005294: Consider default methods for additions to AnnotatedElement Reviewed-by: jfranck, plevart, mchung, abuckley, sogoel
jdk/src/share/classes/java/lang/reflect/AnnotatedElement.java
jdk/test/java/lang/reflect/AnnotatedElement/TestAnnotatedElementDefaults.java
--- a/jdk/src/share/classes/java/lang/reflect/AnnotatedElement.java	Wed Oct 30 16:49:35 2013 -0700
+++ b/jdk/src/share/classes/java/lang/reflect/AnnotatedElement.java	Wed Oct 30 17:27:25 2013 -0700
@@ -27,6 +27,15 @@
 
 import java.lang.annotation.Annotation;
 import java.lang.annotation.AnnotationFormatError;
+import java.lang.annotation.Repeatable;
+import java.util.Arrays;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.Objects;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+import sun.reflect.annotation.AnnotationSupport;
+import sun.reflect.annotation.AnnotationType;
 
 /**
  * Represents an annotated element of the program currently running in this
@@ -222,6 +231,18 @@
      * The caller of this method is free to modify the returned array; it will
      * have no effect on the arrays returned to other callers.
      *
+     * @implSpec The default implementation first calls {@link
+     * #getDeclaredAnnotationsByType(Class)} passing {@code
+     * annotationClass} as the argument. If the returned array has
+     * length greater than zero, the array is returned. If the returned
+     * array is zero-length and this {@code AnnotatedElement} is a
+     * class and the argument type is an inheritable annotation type,
+     * and the superclass of this {@code AnnotatedElement} is non-null,
+     * then the returned result is the result of calling {@link
+     * #getAnnotationsByType(Class)} on the superclass with {@code
+     * annotationClass} as the argument. Otherwise, a zero-length
+     * array is returned.
+     *
      * @param <T> the type of the annotation to query for and return if present
      * @param annotationClass the Class object corresponding to the
      *        annotation type
@@ -230,7 +251,29 @@
      * @throws NullPointerException if the given annotation class is null
      * @since 1.8
      */
-    <T extends Annotation> T[] getAnnotationsByType(Class<T> annotationClass);
+    default <T extends Annotation> T[] getAnnotationsByType(Class<T> annotationClass) {
+         /*
+          * Definition of associated: directly or indirectly present OR
+          * neither directly nor indirectly present AND the element is
+          * a Class, the annotation type is inheritable, and the
+          * annotation type is associated with the superclass of the
+          * element.
+          */
+         T[] result = getDeclaredAnnotationsByType(annotationClass);
+
+         if (result.length == 0 && // Neither directly nor indirectly present
+             this instanceof Class && // the element is a class
+             AnnotationType.getInstance(annotationClass).isInherited()) { // Inheritable
+             Class<?> superClass = ((Class<?>) this).getSuperclass();
+             if (superClass != null) {
+                 // Determine if the annotation is associated with the
+                 // superclass
+                 result = superClass.getAnnotationsByType(annotationClass);
+             }
+         }
+
+         return result;
+     }
 
     /**
      * Returns this element's annotation for the specified type if
@@ -239,6 +282,11 @@
      * This method ignores inherited annotations. (Returns null if no
      * annotations are directly present on this element.)
      *
+     * @implSpec The default implementation first performs a null check
+     * and then loops over the results of {@link
+     * #getDeclaredAnnotations} returning the first annotation whose
+     * annotation type matches the argument type.
+     *
      * @param <T> the type of the annotation to query for and return if directly present
      * @param annotationClass the Class object corresponding to the
      *        annotation type
@@ -247,7 +295,18 @@
      * @throws NullPointerException if the given annotation class is null
      * @since 1.8
      */
-    <T extends Annotation> T getDeclaredAnnotation(Class<T> annotationClass);
+    default <T extends Annotation> T getDeclaredAnnotation(Class<T> annotationClass) {
+         Objects.requireNonNull(annotationClass);
+         // Loop over all directly-present annotations looking for a matching one
+         for (Annotation annotation : getDeclaredAnnotations()) {
+             if (annotationClass.equals(annotation.annotationType())) {
+                 // More robust to do a dynamic cast at runtime instead
+                 // of compile-time only.
+                 return annotationClass.cast(annotation);
+             }
+         }
+         return null;
+     }
 
     /**
      * Returns this element's annotation(s) for the specified type if
@@ -268,6 +327,22 @@
      * The caller of this method is free to modify the returned array; it will
      * have no effect on the arrays returned to other callers.
      *
+     * @implSpec The default implementation may call {@link
+     * #getDeclaredAnnotation(Class)} one or more times to find a
+     * directly present annotation and, if the annotation type is
+     * repeatable, to find a container annotation. If annotations of
+     * the annotation type {@code annotationClass} are found to be both
+     * directly and indirectly present, then {@link
+     * #getDeclaredAnnotations()} will get called to determine the
+     * order of the elements in the returned array.
+     *
+     * <p>Alternatively, the default implementation may call {@link
+     * #getDeclaredAnnotations()} a single time and the returned array
+     * examined for both directly and indirectly present
+     * annotations. The results of calling {@link
+     * #getDeclaredAnnotations()} are assumed to be consistent with the
+     * results of calling {@link #getDeclaredAnnotation(Class)}.
+     *
      * @param <T> the type of the annotation to query for and return
      * if directly or indirectly present
      * @param annotationClass the Class object corresponding to the
@@ -277,7 +352,16 @@
      * @throws NullPointerException if the given annotation class is null
      * @since 1.8
      */
-    <T extends Annotation> T[] getDeclaredAnnotationsByType(Class<T> annotationClass);
+    default <T extends Annotation> T[] getDeclaredAnnotationsByType(Class<T> annotationClass) {
+        Objects.requireNonNull(annotationClass);
+        return AnnotationSupport.
+            getDirectlyAndIndirectlyPresent(Arrays.stream(getDeclaredAnnotations()).
+                                            collect(Collectors.toMap(Annotation::annotationType,
+                                                                     Function.identity(),
+                                                                     ((first,second) -> first),
+                                                                     LinkedHashMap::new)),
+                                            annotationClass);
+    }
 
     /**
      * Returns annotations that are <em>directly present</em> on this element.
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/java/lang/reflect/AnnotatedElement/TestAnnotatedElementDefaults.java	Wed Oct 30 17:27:25 2013 -0700
@@ -0,0 +1,339 @@
+/*
+ * Copyright (c) 2013, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+/*
+ * @test
+ * @bug 8005294
+ * @summary Check behavior of default methods of AnnotatedElement
+ * @author Joseph D. Darcy
+ */
+
+import java.lang.annotation.*;
+import java.lang.reflect.*;
+import java.util.*;
+
+/**
+ * For annotation type tokens including, null, DirectlyPresent.class,
+ * IndirectlyPresent.class, etc. the behavior of
+ * AnnotedElementDelegate.foo(arg) is compared for equality to
+ * baseAnnotatedElement.foo(arg) on various kinds of annotated
+ * elements.
+ */
+public class TestAnnotatedElementDefaults {
+    public static void main(String... args) throws SecurityException {
+        int failures = 0;
+
+        for (AnnotatedElement annotElement : elementsToTest()) {
+            System.out.println(annotElement);
+            AnnotatedElementDelegate delegate = new AnnotatedElementDelegate(annotElement);
+            failures += testNullHandling(delegate);
+            for (Class<? extends Annotation> annotType : annotationsToTest()) {
+                failures += AnnotatedElementDelegate.testDelegate(delegate, annotType);
+            }
+        }
+
+        if (failures > 0) {
+            System.err.printf("%d failures%n", failures);
+            throw new RuntimeException();
+        }
+    }
+
+    private static List<AnnotatedElement> elementsToTest() {
+        List<AnnotatedElement> annotatedElements = new ArrayList<>();
+        annotatedElements.add(TestClass1Super.class);
+        annotatedElements.add(TestClass1.class);
+        for (Method method : TestClass1.class.getDeclaredMethods()) {
+            annotatedElements.add(method);
+        }
+        return annotatedElements;
+    }
+
+    private static List<Class<? extends Annotation>> annotationsToTest() {
+        List<Class<? extends Annotation>> annotations = new ArrayList<>();
+        annotations.add(Missing.class);
+
+        annotations.add(MissingRepeatable.class);
+
+        annotations.add(DirectlyPresent.class);
+
+        annotations.add(IndirectlyPresent.class);
+        annotations.add(IndirectlyPresentContainer.class);
+
+        annotations.add(DirectlyAndIndirectlyPresent.class);
+        annotations.add(DirectlyAndIndirectlyPresentContainer.class);
+
+        annotations.add(AssociatedDirectOnSuperClass.class);
+        annotations.add(AssociatedDirectOnSuperClassContainer.class);
+
+        annotations.add(AssociatedDirectOnSuperClassIndirectOnSubclass.class);
+        annotations.add(AssociatedDirectOnSuperClassIndirectOnSubclassContainer.class);
+
+        annotations.add(AssociatedIndirectOnSuperClassDirectOnSubclass.class);
+        annotations.add(AssociatedIndirectOnSuperClassDirectOnSubclassContainer.class);
+        return annotations;
+    }
+
+    private static int testNullHandling(AnnotatedElementDelegate delegate) {
+        int failures = 0;
+        try {
+            Object result = delegate.getDeclaredAnnotationsByType(null);
+            failures++;
+        } catch (NullPointerException npe) {
+            ; // Expected
+        }
+
+        try {
+            Object result = delegate.getAnnotationsByType(null);
+            failures++;
+        } catch (NullPointerException npe) {
+            ; // Expected
+        }
+
+        try {
+            Object result = delegate.getDeclaredAnnotation(null);
+            failures++;
+        } catch (NullPointerException npe) {
+            ; // Expected
+        }
+
+        return failures;
+    }
+
+}
+
+// -----------------------------------------------------
+
+@AssociatedDirectOnSuperClass(123)
+@AssociatedIndirectOnSuperClass(234) @AssociatedIndirectOnSuperClass(345)
+@AssociatedDirectOnSuperClassIndirectOnSubclass(987)
+@AssociatedIndirectOnSuperClassDirectOnSubclass(1111) @AssociatedIndirectOnSuperClassDirectOnSubclass(2222)
+class TestClass1Super {}
+
+@DirectlyPresent(1)
+@IndirectlyPresent(10) @IndirectlyPresent(11)
+@AssociatedDirectOnSuperClassIndirectOnSubclass(876) @AssociatedDirectOnSuperClassIndirectOnSubclass(765)
+@AssociatedIndirectOnSuperClassDirectOnSubclass(3333)
+class TestClass1 extends TestClass1Super {
+
+    @DirectlyPresent(2)
+    @IndirectlyPresentContainer({@IndirectlyPresent(12)})
+    @DirectlyAndIndirectlyPresentContainer({@DirectlyAndIndirectlyPresent(84), @DirectlyAndIndirectlyPresent(96)})
+    public void foo() {return ;}
+
+    @IndirectlyPresentContainer({})
+    @DirectlyAndIndirectlyPresentContainer({@DirectlyAndIndirectlyPresent(11), @DirectlyAndIndirectlyPresent(22)})
+    @DirectlyAndIndirectlyPresent(33)
+    public void bar()  {return ;}
+}
+
+// -----------------------------------------------------
+
+@Retention(RetentionPolicy.RUNTIME)
+@interface Missing {
+    int value();
+}
+
+// -----------------------------------------------------
+
+@Retention(RetentionPolicy.RUNTIME)
+@Repeatable(MissingRepeatableContainer.class)
+@interface MissingRepeatable {
+    int value();
+}
+
+@Retention(RetentionPolicy.RUNTIME)
+@interface MissingRepeatableContainer {
+    MissingRepeatable[] value();
+}
+
+// -----------------------------------------------------
+
+@Retention(RetentionPolicy.RUNTIME)
+@interface DirectlyPresent {
+    int value();
+}
+
+// -----------------------------------------------------
+
+@Retention(RetentionPolicy.RUNTIME)
+@Repeatable(IndirectlyPresentContainer.class)
+@interface IndirectlyPresent {
+    int value();
+}
+
+@Retention(RetentionPolicy.RUNTIME)
+@interface IndirectlyPresentContainer {
+    IndirectlyPresent[] value();
+}
+
+// -----------------------------------------------------
+
+@Retention(RetentionPolicy.RUNTIME)
+@Repeatable(DirectlyAndIndirectlyPresentContainer.class)
+@interface DirectlyAndIndirectlyPresent {
+    int value();
+
+}
+
+@Retention(RetentionPolicy.RUNTIME)
+@interface DirectlyAndIndirectlyPresentContainer {
+    DirectlyAndIndirectlyPresent[] value();
+}
+
+// -----------------------------------------------------
+
+@Retention(RetentionPolicy.RUNTIME)
+@Repeatable(AssociatedDirectOnSuperClassContainer.class)
+@interface AssociatedDirectOnSuperClass {
+    int value();
+}
+
+@Retention(RetentionPolicy.RUNTIME)
+@interface AssociatedDirectOnSuperClassContainer {
+    AssociatedDirectOnSuperClass[] value();
+}
+
+// -----------------------------------------------------
+
+@Retention(RetentionPolicy.RUNTIME)
+@Repeatable(AssociatedIndirectOnSuperClassContainer.class)
+@interface AssociatedIndirectOnSuperClass {
+    int value();
+}
+
+@Retention(RetentionPolicy.RUNTIME)
+@interface AssociatedIndirectOnSuperClassContainer {
+    AssociatedIndirectOnSuperClass[] value();
+}
+
+// -----------------------------------------------------
+
+@Retention(RetentionPolicy.RUNTIME)
+@Repeatable(AssociatedDirectOnSuperClassIndirectOnSubclassContainer.class)
+@interface  AssociatedDirectOnSuperClassIndirectOnSubclass {
+    int value();
+}
+
+@Retention(RetentionPolicy.RUNTIME)
+@interface AssociatedDirectOnSuperClassIndirectOnSubclassContainer {
+    AssociatedDirectOnSuperClassIndirectOnSubclass[] value();
+}
+
+// -----------------------------------------------------
+
+@Retention(RetentionPolicy.RUNTIME)
+@Repeatable(AssociatedIndirectOnSuperClassDirectOnSubclassContainer.class)
+@interface  AssociatedIndirectOnSuperClassDirectOnSubclass {
+    int value();
+}
+
+@Retention(RetentionPolicy.RUNTIME)
+@interface AssociatedIndirectOnSuperClassDirectOnSubclassContainer {
+    AssociatedIndirectOnSuperClassDirectOnSubclass[] value();
+}
+
+// -----------------------------------------------------
+
+/**
+ * Helper class to ease calling the default methods of {@code
+ * AnnotatedElement} and comparing the results to other
+ * implementation.
+ */
+class AnnotatedElementDelegate implements AnnotatedElement {
+    private AnnotatedElement base;
+
+    public AnnotatedElementDelegate(AnnotatedElement base) {
+        Objects.requireNonNull(base);
+        this.base = base;
+    }
+
+    // Delegate to base implemenetation of AnnotatedElement methods
+    // without defaults.
+    @Override
+    public <T extends Annotation> T getAnnotation(Class<T> annotationClass) {
+        return base.getAnnotation(annotationClass);
+    }
+
+    @Override
+    public Annotation[] getAnnotations() {
+        return base.getAnnotations();
+    }
+
+    @Override
+    public Annotation[] getDeclaredAnnotations() {
+        return base.getDeclaredAnnotations();
+    }
+
+    public AnnotatedElement getBase() {
+        return base;
+    }
+
+    static int testDelegate(AnnotatedElementDelegate delegate,
+                            Class<? extends Annotation> annotationClass) {
+        int failures = 0;
+        AnnotatedElement base = delegate.getBase();
+
+        // System.out.println("\tTesting " + delegate + "\ton\t" + annotationClass);
+
+        // <T extends Annotation> T[] getDeclaredAnnotationsByType(Class<T> annotationClass)
+        failures += annotationArrayCheck(delegate.getDeclaredAnnotationsByType(annotationClass),
+                                         base.getDeclaredAnnotationsByType(annotationClass),
+                                         annotationClass,
+                                         "Equality failure on getDeclaredAnnotationsByType(%s) on %s)%n");
+
+        // <T extends Annotation> T[] getAnnotationsByType(Class<T> annotationClass)
+        failures += annotationArrayCheck(delegate.getAnnotationsByType(annotationClass),
+                                         base.getAnnotationsByType(annotationClass),
+                                         annotationClass,
+                                         "Equality failure on getAnnotationsByType(%s) on %s)%n");
+
+        // <T extends Annotation> T getDeclaredAnnotation(Class<T> annotationClass)
+        if (!Objects.equals(delegate.getDeclaredAnnotation(annotationClass),
+                            base.getDeclaredAnnotation(annotationClass))) {
+            failures++;
+            System.err.printf("Equality failure on getDeclaredAnnotation(%s) on %s)%n",
+                              annotationClass, delegate);
+        }
+        return failures;
+    }
+    private static <T extends Annotation> int annotationArrayCheck(T[] delegate,
+                                                           T[] base,
+                                                           Class<? extends Annotation> annotationClass,
+                                                           String message) {
+        int failures = 0;
+
+        if (!Objects.deepEquals(delegate,base)) {
+            failures = 1;
+
+            System.err.printf(message,
+                              annotationClass,
+                              delegate);
+
+            System.err.println("Base result:\t" + Arrays.toString(base));
+            System.err.println("Delegate result:\t " + Arrays.toString(delegate));
+            System.err.println();
+        }
+
+        return failures;
+    }
+}