--- old/src/java.base/share/classes/sun/reflect/annotation/AnnotatedTypeFactory.java 2018-10-14 23:06:32.020001000 -0700 +++ new/src/java.base/share/classes/sun/reflect/annotation/AnnotatedTypeFactory.java 2018-10-14 23:06:31.852001000 -0700 @@ -207,8 +207,10 @@ @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) { @@ -377,6 +379,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) { @@ -444,6 +453,24 @@ return (ParameterizedType)getType(); } + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append(annotationsToString(getAnnotations(), false)); + + Type t = getParameterizedType().getRawType(); + sb.append(t.getTypeName()); + + StringJoiner sj = new StringJoiner(", ", "<", ">"); + sj.setEmptyValue(""); + for(AnnotatedType typeArg: getAnnotatedActualTypeArguments()) { + sj.add(typeArg.toString()); + } + sb.append(sj.toString()); + + return sb.toString(); + } + @Override public boolean equals(Object o) { if (o instanceof AnnotatedParameterizedType) { @@ -524,6 +551,33 @@ } @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append(annotationsToString(getAnnotations(), false)); + sb.append("?"); + + AnnotatedType[] bounds = getAnnotatedLowerBounds(); + if (bounds.length > 0) { + sb.append(" super "); + } else { + bounds = getAnnotatedUpperBounds(); + if (bounds.length > 0) { + sb.append(" extends "); + } + // With extra work, could check for and elide " extends + // Object" if Object was not annotated. + } + + StringJoiner sj = new StringJoiner(" & "); + for(AnnotatedType bound : bounds) { + sj.add(bound.toString()); + } + sb.append(sj.toString()); + return sb.toString(); + } + + + @Override public boolean equals(Object o) { if (o instanceof AnnotatedWildcardType) { AnnotatedWildcardType that = (AnnotatedWildcardType) o; --- old/test/jdk/java/lang/annotation/typeAnnotations/TestObjectMethods.java 2018-10-14 23:06:32.420001000 -0700 +++ new/test/jdk/java/lang/annotation/typeAnnotations/TestObjectMethods.java 2018-10-14 23:06:32.252001000 -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,51 +82,96 @@ * 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 { - // Find final non-array component type and gets its name. - typeString = null; - AnnotatedType componentType = annotType; - while (componentType instanceof AnnotatedArrayType) { - AnnotatedArrayType annotatedArrayType = (AnnotatedArrayType) componentType; - componentType = annotatedArrayType.getAnnotatedGenericComponentType(); - } - - String componentName = componentType.getType().getTypeName(); - valid = annotTypeString.contains(componentName); - } + 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; + + AnnotatedType componentType = annotType; + while (componentType instanceof AnnotatedArrayType) { + AnnotatedArrayType annotatedArrayType = (AnnotatedArrayType) componentType; + componentType = annotatedArrayType.getAnnotatedGenericComponentType(); + } + + String componentName = componentType.getType().getTypeName(); + valid = annotTypeString.contains(componentName); + 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) { errors++; - System.err.println(typeString + "\n" + annotTypeString + - "\n " + valid + - "\n\n"); + System.err.println(typeString + "\n" + annotTypeString + + "\n " + valid + + "\n\n"); } } } + 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 +277,6 @@ } } - static void testWildcards() { System.err.println("Testing wildcards"); // public @AnnotType(10) Set fooNumberSet() {return null;} @@ -269,22 +315,47 @@ // possible. static class TypeHost { + @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 fooSetString() {return null;} + + @AnnotTypeInfo + public Set fooSetNumber() {return null;} + + @AnnotTypeInfo public E fooE() {return null;} - public F fooF() {return null;} + + @AnnotTypeInfo + public F fooF() {return null;} + + @AnnotTypeInfo public G fooG() {return null;} + @AnnotTypeInfo public Set fooNumberSet() {return null;} + + @AnnotTypeInfo public Set fooNumberSet2() {return null;} + + @AnnotTypeInfo + public Set fooNumberSet3() {return null;} } @Retention(RetentionPolicy.RUNTIME) @@ -293,22 +364,99 @@ int value() default 0; } - static class AnnotatedTypeHost { - public /*@AnnotType(0)*/ void fooVoid() {return;} // Illegal to annotate void + @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, - public @AnnotType(1) int foo() {return 0;} - public @AnnotType(2) String fooString() {return null;} + /** + * 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; + } - 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;} - - public @AnnotType(6) Set fooSetString() {return null;} - public @AnnotType(7) E fooE() {return null;} - public @AnnotType(8) F fooF() {return null;} - public @AnnotType(9) G fooG() {return null;} + static class AnnotatedTypeHost { + @AnnotTypeInfo + public /*@AnnotType(0)*/ void fooVoid() {return;} // Illegal to annotate void - public @AnnotType(10) Set fooNumberSet() {return null;} - public @AnnotType(11) Set<@AnnotType(13) ? extends Number> fooNumberSet2() {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;} + + @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 fooSetString() {return null;} + + @AnnotTypeInfo(count = 2, relation = Relation.STRIPPED) + @AnnotType(7) + public Set<@AnnotType(8) Number> fooSetNumber() {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 fooG() {return null;} + + @AnnotTypeInfo(count = 1, relation = Relation.POSTFIX) + @AnnotType(12) + public Set fooNumberSet() {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;} } }