8212081: AnnotatedType.toString implementation don't print annotations on embedded types
Reviewed-by: jfranck, wmdietl
This commit is contained in:
parent
e0911eacd6
commit
7e19a09742
src/java.base/share/classes/sun/reflect/annotation
test/jdk/java/lang/annotation/typeAnnotations
@ -33,6 +33,8 @@ import java.util.List;
|
||||
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 @@ public final class AnnotatedTypeFactory {
|
||||
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 @@ public final class AnnotatedTypeFactory {
|
||||
@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();
|
||||
|
||||
for (Annotation annotation : annotations) {
|
||||
sj.add(annotation.toString());
|
||||
}
|
||||
sb.append(Stream.of(annotations).
|
||||
map(Annotation::toString).
|
||||
collect(Collectors.joining(" ")));
|
||||
|
||||
if (!leadingSpace) {
|
||||
sj.add("");
|
||||
}
|
||||
return sj.toString();
|
||||
if (leadingSpace)
|
||||
sb.insert(0, " ");
|
||||
else
|
||||
sb.append(" ");
|
||||
|
||||
return sb.toString();
|
||||
} else {
|
||||
return "";
|
||||
}
|
||||
@ -377,6 +386,13 @@ public final class AnnotatedTypeFactory {
|
||||
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 +460,23 @@ public final class AnnotatedTypeFactory {
|
||||
return (ParameterizedType)getType();
|
||||
}
|
||||
|
||||
@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) {
|
||||
@ -523,6 +556,42 @@ public final class AnnotatedTypeFactory {
|
||||
return hasUpperBounds;
|
||||
}
|
||||
|
||||
@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) {
|
||||
|
@ -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 @@ public class TestObjectMethods {
|
||||
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 @@ public class TestObjectMethods {
|
||||
* 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 @@ public class TestObjectMethods {
|
||||
|
||||
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 @@ public class TestObjectMethods {
|
||||
}
|
||||
}
|
||||
|
||||
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 @@ public class TestObjectMethods {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static void testWildcards() {
|
||||
System.err.println("Testing wildcards");
|
||||
// public @AnnotType(10) Set<? extends Number> fooNumberSet() {return null;}
|
||||
@ -269,22 +320,53 @@ public class TestObjectMethods {
|
||||
// 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 @@ public class TestObjectMethods {
|
||||
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;}
|
||||
|
||||
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.POSTFIX)
|
||||
@AnnotType(2)
|
||||
public String fooString() {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.ARRAY)
|
||||
public int @AnnotType(3) [] fooIntArray() {return null;}
|
||||
|
||||
public @AnnotType(10) Set<? extends Number> fooNumberSet() {return null;}
|
||||
public @AnnotType(11) Set<@AnnotType(13) ? extends Number> fooNumberSet2() {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;}
|
||||
|
||||
@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;}
|
||||
|
||||
@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;}
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user