8212081: AnnotatedType.toString implementation don't print annotations on embedded types
Reviewed-by: jfranck, wmdietl
--- 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;}
}
}