8212081: AnnotatedType.toString implementation don't print annotations on embedded types
authordarcy
Mon, 29 Oct 2018 11:31:25 -0700
changeset 52317 3c981e581f93
parent 52316 3152b928769d
child 52318 124af9276e44
child 52351 0ecb4e520110
8212081: AnnotatedType.toString implementation don't print annotations on embedded types Reviewed-by: jfranck, wmdietl
src/java.base/share/classes/sun/reflect/annotation/AnnotatedTypeFactory.java
test/jdk/java/lang/annotation/typeAnnotations/TestObjectMethods.java
--- a/src/java.base/share/classes/sun/reflect/annotation/AnnotatedTypeFactory.java	Mon Oct 29 11:05:45 2018 -0700
+++ b/src/java.base/share/classes/sun/reflect/annotation/AnnotatedTypeFactory.java	Mon Oct 29 11:31:25 2018 -0700
@@ -33,6 +33,8 @@
 import java.util.Map;
 import java.util.Objects;
 import java.util.StringJoiner;
+import java.util.stream.Stream;
+import java.util.stream.Collectors;
 
 import static sun.reflect.annotation.TypeAnnotation.*;
 
@@ -125,6 +127,12 @@
             EMPTY_TYPE_ANNOTATION_ARRAY, EMPTY_TYPE_ANNOTATION_ARRAY, null);
     static final AnnotatedType[] EMPTY_ANNOTATED_TYPE_ARRAY = new AnnotatedType[0];
 
+    /*
+     * Note that if additional subclasses of AnnotatedTypeBaseImpl are
+     * added, the equals methods of AnnotatedTypeBaseImpl will need to
+     * be updated to properly implement the equals contract.
+     */
+
     private static class AnnotatedTypeBaseImpl implements AnnotatedType {
         private final Type type;
         private final AnnotatedElement decl;
@@ -207,25 +215,26 @@
         @Override // java.lang.Object
         public String toString() {
             // Reusable toString implementation, but needs to be
-            // specialized for quirks of arrays.
-            return annotationsToString(getAnnotations(), false) + type.toString();
+            // specialized for quirks of arrays and interior types of
+            // wildcards, etc.
+            return annotationsToString(getAnnotations(), false) +
+                ((type instanceof Class) ? type.getTypeName(): type.toString());
         }
 
         protected String annotationsToString(Annotation[] annotations, boolean leadingSpace) {
             if (annotations != null && annotations.length > 0) {
-                StringJoiner sj = new StringJoiner(" ");
-                if (leadingSpace) {
-                    sj.add(""); // Add a space
-                }
+                StringBuffer sb = new StringBuffer();
+
+                sb.append(Stream.of(annotations).
+                          map(Annotation::toString).
+                          collect(Collectors.joining(" ")));
 
-                for (Annotation annotation : annotations) {
-                    sj.add(annotation.toString());
-                }
+                if (leadingSpace)
+                    sb.insert(0, " ");
+                else
+                    sb.append(" ");
 
-                if (!leadingSpace) {
-                    sj.add("");
-                }
-                return sj.toString();
+                return sb.toString();
             } else {
                 return "";
             }
@@ -377,6 +386,13 @@
             return (TypeVariable)getType();
         }
 
+        // For toString, the declaration of a type variable should
+        // including information about its bounds, etc. However, the
+        // use of a type variable should not. For that reason, it is
+        // acceptable for the toString implementation of
+        // AnnotatedTypeVariableImpl to use the inherited
+        // implementation from AnnotatedTypeBaseImpl.
+
         @Override
         public boolean equals(Object o) {
             if (o instanceof AnnotatedTypeVariable) {
@@ -445,6 +461,23 @@
         }
 
         @Override
+        public String toString() {
+            StringBuilder sb = new StringBuilder();
+            sb.append(annotationsToString(getAnnotations(), false));
+
+            Type t = getParameterizedType().getRawType();
+            sb.append(t.getTypeName());
+
+            AnnotatedType[] typeArgs = getAnnotatedActualTypeArguments();
+            if (typeArgs.length > 0) {
+                sb.append(Stream.of(typeArgs).map(AnnotatedType::toString).
+                          collect(Collectors.joining(", ", "<", ">")));
+            }
+
+            return sb.toString();
+        }
+
+        @Override
         public boolean equals(Object o) {
             if (o instanceof AnnotatedParameterizedType) {
                 AnnotatedParameterizedType that = (AnnotatedParameterizedType) o;
@@ -524,6 +557,42 @@
         }
 
         @Override
+        public String toString() {
+            StringBuilder sb = new StringBuilder();
+            sb.append(annotationsToString(getAnnotations(), false));
+            sb.append("?");
+
+            // Note that the wildcard API is written to accommodate
+            // multiple bounds for wildcards, but at the time of
+            // writing only a single bound is allowed in the
+            // language.
+            AnnotatedType[] bounds = getAnnotatedLowerBounds();
+            if (bounds.length > 0) {
+                sb.append(" super ");
+            } else {
+                bounds = getAnnotatedUpperBounds();
+                if (bounds.length > 0) {
+                    if (bounds.length == 1) {
+                        // Check for and elide " extends java.lang.Object" if a lone
+                        // Object bound is not annotated.
+                        AnnotatedType bound = bounds[0];
+                        if (bound.getType().equals(Object.class) &&
+                            bound.getAnnotations().length == 0) {
+                            return sb.toString();
+                        }
+                    }
+                    sb.append(" extends ");
+                }
+            }
+
+            sb.append(Stream.of(bounds).map(AnnotatedType::toString).
+                      collect(Collectors.joining(" & ")));
+
+            return sb.toString();
+        }
+
+
+        @Override
         public boolean equals(Object o) {
             if (o instanceof AnnotatedWildcardType) {
                 AnnotatedWildcardType that = (AnnotatedWildcardType) o;
--- a/test/jdk/java/lang/annotation/typeAnnotations/TestObjectMethods.java	Mon Oct 29 11:05:45 2018 -0700
+++ b/test/jdk/java/lang/annotation/typeAnnotations/TestObjectMethods.java	Mon Oct 29 11:31:25 2018 -0700
@@ -23,17 +23,19 @@
 
 /*
  * @test
- * @bug 8058202
+ * @bug 8058202 8212081
  * @summary Test java.lang.Object methods on AnnotatedType objects.
  */
 
 import java.lang.annotation.*;
 import java.lang.reflect.*;
 import java.util.*;
+import java.util.regex.*;
 
 /**
  * Test toString, equals, and hashCode on various AnnotatedType objects.
  */
+
 public class TestObjectMethods {
     private static int errors = 0;
 
@@ -61,8 +63,8 @@
             testEquals(clazz);
         }
 
-        testToString(TypeHost.class, false);
-        testToString(AnnotatedTypeHost.class, true);
+        testToString(TypeHost.class);
+        testToString(AnnotatedTypeHost.class);
 
         testAnnotationsMatterForEquals(TypeHost.class, AnnotatedTypeHost.class);
 
@@ -80,29 +82,45 @@
      * For non-array types, verify toString version of the annotated
      * type ends with the same string as the generic type.
      */
-    static void testToString(Class<?> clazz, boolean leadingAnnotations) {
+    static void testToString(Class<?> clazz) {
         System.err.println("Testing toString on methods of class " + clazz.getName());
         Method[] methods = clazz.getDeclaredMethods();
         for (Method m : methods) {
+            // Expected information about the type annotations stored
+            // in a *declaration* annotation.
+            AnnotTypeInfo annotTypeInfo = m.getAnnotation(AnnotTypeInfo.class);
+            int expectedAnnotCount      = annotTypeInfo.count();
+            Relation relation           = annotTypeInfo.relation();
+
             AnnotatedType annotType = m.getAnnotatedReturnType();
             String annotTypeString = annotType.toString();
 
             Type type = m.getGenericReturnType();
-            String typeString = type.toString();
+            String typeString = (type instanceof Class) ?
+                type.getTypeName() :
+                type.toString();
 
             boolean isArray = annotType instanceof AnnotatedArrayType;
             boolean isVoid = "void".equals(typeString);
 
             boolean valid;
-            if (!isArray) {
-                if (leadingAnnotations && !isVoid) {
-                    valid =
-                        annotTypeString.endsWith(typeString) &&
-                        !annotTypeString.startsWith(typeString);
-                } else {
-                    valid = annotTypeString.equals(typeString);
-                }
-            } else {
+
+            switch(relation) {
+            case EQUAL:
+                valid = annotTypeString.equals(typeString);
+                break;
+
+            case POSTFIX:
+                valid = annotTypeString.endsWith(typeString) &&
+                    !annotTypeString.startsWith(typeString);
+                break;
+
+            case STRIPPED:
+                String stripped = annotationRegex.matcher(annotTypeString).replaceAll("");
+                valid = typeString.replace(" ", "").equals(stripped.replace(" ", ""));
+                break;
+
+            case ARRAY:
                 // Find final non-array component type and gets its name.
                 typeString = null;
 
@@ -114,6 +132,38 @@
 
                 String componentName = componentType.getType().getTypeName();
                 valid = annotTypeString.contains(componentName);
+                break;
+
+            case OTHER:
+                // No additional checks
+                valid = true;
+                break;
+
+            default:
+                throw new AssertionError("Shouldn't be reached");
+            }
+
+            // Verify number of type annotations matches expected value
+            Matcher matcher = annotationRegex.matcher(annotTypeString);
+            if (expectedAnnotCount > 0) {
+                int i = expectedAnnotCount;
+                int annotCount = 0;
+                while (i > 0) {
+                    boolean found = matcher.find();
+                    if (found) {
+                        i--;
+                        annotCount++;
+                    } else {
+                        errors++;
+                        System.err.println("\tExpected annotation not found: " + annotTypeString);
+                    }
+                }
+            }
+
+            boolean found = matcher.find();
+            if (found) {
+                errors++;
+                System.err.println("\tAnnotation found unexpectedly: " + annotTypeString);
             }
 
             if (!valid) {
@@ -125,6 +175,8 @@
         }
     }
 
+    private static final Pattern annotationRegex = Pattern.compile("@TestObjectMethods\\$AnnotType\\(value=(\\p{Digit})+\\)");
+
     static void testGetAnnotations(Class<?> clazz, boolean annotationsExpectedOnMethods) {
         System.err.println("Testing getAnnotations on methods of class " + clazz.getName());
         Method[] methods = clazz.getDeclaredMethods();
@@ -230,7 +282,6 @@
         }
     }
 
-
     static void testWildcards() {
         System.err.println("Testing wildcards");
         // public @AnnotType(10) Set<? extends Number> fooNumberSet() {return null;}
@@ -269,22 +320,53 @@
     // possible.
 
     static class TypeHost<E, F extends Number> {
+        @AnnotTypeInfo
         public void fooVoid() {return;}
 
+        @AnnotTypeInfo
         public int foo() {return 0;}
+
+        @AnnotTypeInfo
         public String fooString() {return null;}
 
+        @AnnotTypeInfo
         public int[] fooIntArray() {return null;}
+
+        @AnnotTypeInfo
         public String[] fooStringArray() {return null;}
+
+        @AnnotTypeInfo
         public String [][] fooStringArrayArray() {return null;}
 
+        @AnnotTypeInfo
         public Set<String> fooSetString() {return null;}
+
+        @AnnotTypeInfo
+        public Set<Number> fooSetNumber() {return null;}
+
+        @AnnotTypeInfo
         public E fooE() {return null;}
+
+        @AnnotTypeInfo
         public F fooF() {return null;}
+
+        @AnnotTypeInfo
         public <G> G fooG() {return null;}
 
+        @AnnotTypeInfo
         public  Set<? extends Number> fooNumberSet() {return null;}
+
+        @AnnotTypeInfo
         public  Set<? extends Integer> fooNumberSet2() {return null;}
+
+        @AnnotTypeInfo
+        public  Set<? extends Long> fooNumberSet3() {return null;}
+
+        @AnnotTypeInfo
+        public Set<?> fooObjectSet() {return null;}
+
+        @AnnotTypeInfo
+        public List<? extends Object> fooObjectList() {return null;}
     }
 
     @Retention(RetentionPolicy.RUNTIME)
@@ -293,22 +375,114 @@
         int value() default 0;
     }
 
+    @Retention(RetentionPolicy.RUNTIME)
+    @Target(ElementType.METHOD)
+    static @interface AnnotTypeInfo {
+        /**
+         * Expected number of @AnnotType
+         */
+        int count() default 0;
+
+        /**
+         * Relation to genericString output.
+         */
+        Relation relation() default Relation.EQUAL;
+    }
+
+    /**
+     * Expected relationship of toString output of AnnotatedType to
+     * toGenericString output of underlying type.
+     */
+    static private enum Relation {
+        EQUAL,
+
+        /**
+         * The toGenericString output is a postfix of the
+         * AnnotatedType output; a leading annotation is expected.
+         */
+        POSTFIX,
+
+        /**
+         * If the annotations are stripped from the AnnotatedType
+         * output and whitespace adjusted accordingly, it should equal
+         * the toGenericString output.
+         */
+        STRIPPED,
+
+        /**
+         * The output of AnnotatedType for arrays would require more
+         * extensive transformation to map to toGenericString output.
+         */
+        ARRAY,
+
+       /**
+         * Some other, harder to characterize, relationship. Currently
+         * used for a wildcard where Object in "extends Object" is
+         * annotated; the "extends Object" is elided in toGenericString.
+         */
+        OTHER;
+    }
+
     static class AnnotatedTypeHost<E, F extends Number> {
+        @AnnotTypeInfo
         public /*@AnnotType(0)*/ void fooVoid() {return;} // Illegal to annotate void
 
-        public @AnnotType(1) int foo() {return 0;}
-        public @AnnotType(2) String fooString() {return null;}
+        @AnnotTypeInfo(count =1, relation = Relation.POSTFIX)
+        @AnnotType(1)
+        public int foo() {return 0;}
+
+        @AnnotTypeInfo(count = 1, relation = Relation.POSTFIX)
+        @AnnotType(2)
+        public  String fooString() {return null;}
+
+        @AnnotTypeInfo(count = 1, relation = Relation.ARRAY)
+        public int @AnnotType(3) [] fooIntArray() {return null;}
 
-        public  int @AnnotType(3) [] fooIntArray() {return null;}
-        public  String @AnnotType(4) [] fooStringArray() {return null;}
-        public  @AnnotType(5) String  @AnnotType(0) [] @AnnotType(1) [] fooStringArrayArray() {return null;}
+        @AnnotTypeInfo(count = 1, relation = Relation.ARRAY)
+        public String @AnnotType(4) [] fooStringArray() {return null;}
+
+        @AnnotTypeInfo(count = 3, relation = Relation.ARRAY)
+        @AnnotType(5)
+        public String  @AnnotType(0) [] @AnnotType(1) [] fooStringArrayArray() {return null;}
+
+        @AnnotTypeInfo(count = 1, relation = Relation.POSTFIX)
+        @AnnotType(6)
+        public Set<String> fooSetString() {return null;}
+
+        @AnnotTypeInfo(count = 2, relation = Relation.STRIPPED)
+        @AnnotType(7)
+        public Set<@AnnotType(8) Number> fooSetNumber() {return null;}
 
-        public @AnnotType(6) Set<String> fooSetString() {return null;}
-        public @AnnotType(7) E fooE() {return null;}
-        public @AnnotType(8) F fooF() {return null;}
-        public @AnnotType(9) <G> G fooG() {return null;}
+        @AnnotTypeInfo(count = 1, relation = Relation.POSTFIX)
+        @AnnotType(9)
+        public E fooE() {return null;}
+
+        @AnnotTypeInfo(count = 1, relation = Relation.POSTFIX)
+        @AnnotType(10)
+        public F fooF() {return null;}
+
+        @AnnotTypeInfo(count = 1, relation = Relation.POSTFIX)
+        @AnnotType(11)
+        public <G> G fooG() {return null;}
+
+        @AnnotTypeInfo(count = 1, relation = Relation.POSTFIX)
+        @AnnotType(12)
+        public Set<? extends Number> fooNumberSet() {return null;}
 
-        public @AnnotType(10) Set<? extends Number> fooNumberSet() {return null;}
-        public @AnnotType(11) Set<@AnnotType(13) ? extends Number> fooNumberSet2() {return null;}
+        @AnnotTypeInfo(count = 2, relation = Relation.STRIPPED)
+        @AnnotType(13)
+        public Set<@AnnotType(14) ? extends Number> fooNumberSet2() {return null;}
+
+        @AnnotTypeInfo(count = 2, relation = Relation.STRIPPED)
+        @AnnotType(15)
+        public Set< ? extends @AnnotType(16) Long> fooNumberSet3() {return null;}
+
+        @AnnotTypeInfo(count = 2, relation = Relation.STRIPPED)
+        @AnnotType(16)
+        public Set<@AnnotType(17) ?> fooObjectSet() {return null;}
+
+        @AnnotTypeInfo(count = 2, relation = Relation.OTHER)
+        @AnnotType(18)
+        public List<? extends @AnnotType(19) Object> fooObjectList() {return null;}
     }
 }