--- old/src/java.base/share/classes/sun/reflect/annotation/AnnotatedTypeFactory.java 2018-10-08 13:46:23.100001000 -0700 +++ new/src/java.base/share/classes/sun/reflect/annotation/AnnotatedTypeFactory.java 2018-10-08 13:46:22.924001000 -0700 @@ -31,6 +31,8 @@ import java.util.Arrays; import java.util.List; import java.util.Map; +import java.util.Objects; +import java.util.StringJoiner; import static sun.reflect.annotation.TypeAnnotation.*; @@ -202,6 +204,65 @@ } + @Override + public String toString() { + // Reusable toString implementation, but needs to be + // specialized for quirks of arrays. + return annotationsToString(getAnnotations(), false) + type.toString(); + } + + protected String annotationsToString(Annotation[] annotations, boolean leadingSpace) { + StringJoiner sj = new StringJoiner(" "); + if (annotations != null && annotations.length > 0) { + if (leadingSpace) { + sj.add(""); // Add a space + } + + for (Annotation annotation : annotations) { + sj.add(annotation.toString()); + } + + if (!leadingSpace) { + sj.add(""); + } + } + return sj.toString(); + } + + protected boolean equalsTypeAndAnnotations(AnnotatedType that) { + return getType().equals(that.getType()) && + // Treat ordering of annotations as significant + Arrays.equals(getAnnotations(), that.getAnnotations()) && + Objects.equals(getAnnotatedOwnerType(), that.getAnnotatedOwnerType()); + } + + int baseHashCode() { + return type.hashCode() ^ + // Acceptable to use Objects.hash rather than + // Arrays.deepHashCode since the elements of the array + // are not themselves arrays. + Objects.hash((Object[])getAnnotations()); + } + + @Override + public boolean equals(Object o) { + if (o instanceof AnnotatedType && + !(o instanceof AnnotatedArrayType) && + !(o instanceof AnnotatedTypeVariable) && + !(o instanceof AnnotatedParameterizedType) && + !(o instanceof AnnotatedWildcardType)) { + AnnotatedType that = (AnnotatedType) o; + return equalsTypeAndAnnotations(that); + } else { + return false; + } + } + + @Override + public int hashCode() { + return baseHashCode(); + } + // Implementation details final LocationInfo getLocation() { return location; @@ -244,6 +305,52 @@ } return ((GenericArrayType)t).getGenericComponentType(); } + + @Override + public String toString() { + // To annotate the full type of an array, the annotations + // are placed between the type and the brackets. For + // example, to annotate an array of Strings, the syntax used is + // + // String @TypeAnnotation [] + // + // and *not* + // + // @TypeAnnotation String[]. + // + // The toString output should strive to be reusable in + // source code. Therefore, the general logic of putting + // the annotations before a textual representation of the + // type need to be overridden for arrays. + StringBuilder sb = new StringBuilder(); + + AnnotatedType componentType = this; + while (componentType instanceof AnnotatedArrayType) { + AnnotatedArrayType annotatedArrayType = (AnnotatedArrayType) componentType; + sb.append(annotationsToString(annotatedArrayType.getAnnotations(), true) + "[]"); + componentType = annotatedArrayType.getAnnotatedGenericComponentType(); + } + + sb.insert(0, componentType.toString()); + return sb.toString(); + } + + @Override + public boolean equals(Object o) { + if (o instanceof AnnotatedArrayType) { + AnnotatedArrayType that = (AnnotatedArrayType) o; + return equalsTypeAndAnnotations(that) && + Objects.equals(getAnnotatedGenericComponentType(), + that.getAnnotatedGenericComponentType()); + } else { + return false; + } + } + + @Override + public int hashCode() { + return baseHashCode() ^ getAnnotatedGenericComponentType().hashCode(); + } } private static final class AnnotatedTypeVariableImpl extends AnnotatedTypeBaseImpl implements AnnotatedTypeVariable { @@ -266,6 +373,23 @@ private TypeVariable getTypeVariable() { return (TypeVariable)getType(); } + + @Override + public boolean equals(Object o) { + if (o instanceof AnnotatedTypeVariable) { + AnnotatedTypeVariable that = (AnnotatedTypeVariable) o; + return equalsTypeAndAnnotations(that) && + Arrays.equals(getAnnotatedBounds(), that.getAnnotatedBounds()); + } else { + return false; + } + } + + @Override + public int hashCode() { + return baseHashCode() ^ + Objects.hash((Object[])getAnnotatedBounds()); + } } private static final class AnnotatedParameterizedTypeImpl extends AnnotatedTypeBaseImpl @@ -316,6 +440,23 @@ private ParameterizedType getParameterizedType() { return (ParameterizedType)getType(); } + + @Override + public boolean equals(Object o) { + if (o instanceof AnnotatedParameterizedType) { + AnnotatedParameterizedType that = (AnnotatedParameterizedType) o; + return equalsTypeAndAnnotations(that) && + Arrays.equals(getAnnotatedActualTypeArguments(), that.getAnnotatedActualTypeArguments()); + } else { + return false; + } + } + + @Override + public int hashCode() { + return baseHashCode() ^ + Objects.hash((Object[])getAnnotatedActualTypeArguments()); + } } private static final class AnnotatedWildcardTypeImpl extends AnnotatedTypeBaseImpl implements AnnotatedWildcardType { @@ -378,5 +519,26 @@ private boolean hasUpperBounds() { return hasUpperBounds; } + + @Override + public boolean equals(Object o) { + if (o instanceof AnnotatedWildcardType) { + AnnotatedWildcardType that = (AnnotatedWildcardType) o; + return equalsTypeAndAnnotations(that) && + // Treats ordering as significant + Arrays.equals(getAnnotatedLowerBounds(), that.getAnnotatedLowerBounds()) && + // Treats ordering as significant + Arrays.equals(getAnnotatedUpperBounds(), that.getAnnotatedUpperBounds()); + } else { + return false; + } + } + + @Override + public int hashCode() { + return baseHashCode() ^ + Objects.hash((Object[])getAnnotatedLowerBounds()) ^ + Objects.hash((Object[])getAnnotatedUpperBounds()); + } } } --- /dev/null 2018-10-05 17:12:40.740000000 -0700 +++ new/test/jdk/java/lang/annotation/typeAnnotations/TestObjectMethods.java 2018-10-08 13:46:23.356001000 -0700 @@ -0,0 +1,311 @@ +/* + * Copyright (c) 2018, 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 8058202 + * @summary Test java.lang.Object methods on AnnotatedType objects. + */ + +import java.lang.annotation.*; +import java.lang.reflect.*; +import java.util.*; + +/** + * Test toString, equals, and hashCode on various AnnotatedType objects. + */ +public class TestObjectMethods { + private static int errors = 0; + + /* + * There are various subtypes of AnnotatedType implementations: + * + * AnnotatedType + * AnnotatedArrayType + * AnnotatedParameterizedType + * AnnotatedTypeVariable + * AnnotatedWildcardType + * + * The implementations of each these implementions are + * examined. Wildcards don't appear as top-level types and need to + * be extracted from bounds. + * + * AnnotatedTypes with and without annotations areexamined as + * well. + */ + public static void main(String... args) { + Class[] testClasses = {TypeHost.class, AnnotatedTypeHost.class}; + + for (Class clazz : testClasses) { + testEqualsReflexivity(clazz); + testEquals(clazz); + } + + testToString(TypeHost.class, false); + testToString(AnnotatedTypeHost.class, true); + + testAnnotationsMatterForEquals(TypeHost.class, AnnotatedTypeHost.class); + + testGetAnnotations(TypeHost.class, false); + testGetAnnotations(AnnotatedTypeHost.class, true); + + testWildcards(); + + if (errors > 0) { + throw new RuntimeException(errors + " errors"); + } + } + + /* + * 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) { + System.err.println("Testing toString on methods of class " + clazz.getName()); + Method[] methods = clazz.getDeclaredMethods(); + for (Method m : methods) { + AnnotatedType annotType = m.getAnnotatedReturnType(); + String annotTypeString = annotType.toString(); + + Type type = m.getGenericReturnType(); + String typeString = 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); + } + + if (!valid) { + errors++; + System.err.println(typeString + "\n" + annotTypeString + + "\n " + valid + + "\n\n"); + } + } + } + + static void testGetAnnotations(Class clazz, boolean annotationsExpectedOnMethods) { + System.err.println("Testing getAnnotations on methods of class " + clazz.getName()); + Method[] methods = clazz.getDeclaredMethods(); + for (Method m : methods) { + Type type = m.getGenericReturnType(); + AnnotatedType annotType = m.getAnnotatedReturnType(); + Annotation[] annotations = annotType.getAnnotations(); + + boolean isVoid = "void".equals(type.toString()); + + if (annotationsExpectedOnMethods && !isVoid) { + if (annotations.length == 0 ) { + errors++; + System.err.println("Expected annotations missing on " + annotType); + + } + } else { + if (annotations.length > 0 ) { + errors++; + System.err.println("Unexpected annotations present on " + annotType); + } + } + } + } + + static void testEqualsReflexivity(Class clazz) { + System.err.println("Testing reflexivity of equals on methods of class " + clazz.getName()); + Method[] methods = clazz.getDeclaredMethods(); + for (Method m : methods) { + AnnotatedType annotType1 = m.getAnnotatedReturnType(); + AnnotatedType annotType2 = m.getAnnotatedReturnType(); + + boolean valid = annotType1.equals(annotType2); + + if (!valid) { + errors++; + System.err.println(annotType1); + System.err.println(" is not equal to "); + System.err.println(annotType2); + System.err.println(); + } + } + } + + /* + * For each of the type host classes, the return type of a method + * should only equal the return type of that method. + */ + static void testEquals(Class clazz) { + Method[] methods = clazz.getDeclaredMethods(); + + for (int i = 0; i < methods.length; i++) { + for (int j = 0; j < methods.length; j++) { + if (i == j) + continue; + else { + AnnotatedType annotType1 = methods[i].getAnnotatedReturnType(); + AnnotatedType annotType2 = methods[j].getAnnotatedReturnType(); + + boolean valid = !annotType1.equals(annotType2); + + if (!valid) { + errors++; + System.err.println(annotType1); + System.err.println(" is equal to "); + System.err.println(annotType2); + System.err.println(); + } + + } + } + } + } + + /** + * Roughly, compare the return types of corresponding methods on + * TypeHost and AnnotatedtypeHost and verify the AnnotatedType + * objects are *not* equal even if their underlying generic types + * are. + */ + static void testAnnotationsMatterForEquals(Class clazz1, Class clazz2) { + Method[] methods1 = clazz1.getDeclaredMethods(); + Method[] methods2 = clazz2.getDeclaredMethods(); + + // Skip 0th element since void cannoted be annotated + for (int i = 1; i < methods1.length; i++) { + AnnotatedType annotType1 = methods1[i].getAnnotatedReturnType(); + AnnotatedType annotType2 = methods2[i].getAnnotatedReturnType(); + + boolean valid = !annotType1.equals(annotType2); + + if (!valid) { + errors++; + System.err.println(annotType1); + System.err.println(" is equal to "); + System.err.println(annotType2); + System.err.println(); + } + } + } + + + static void testWildcards() { + // public @AnnotType(10) Set fooNumberSet() {return null;} + // public @AnnotType(11) Set<@AnnotType(13) ? extends Number> fooNumberSet2() {return null;} + AnnotatedWildcardType awt1 = extractWildcard("fooNumberSet"); + AnnotatedWildcardType awt2 = extractWildcard("fooNumberSet2"); + + if (!awt1.equals(extractWildcard("fooNumberSet")) || + !awt2.equals(extractWildcard("fooNumberSet2"))) { + errors++; + System.err.println("Bad equality comparison on wildcards."); + } + + if (awt1.equals(awt2)) { + errors++; + System.err.println(awt1); + System.err.println(" is equal to "); + System.err.println(awt2); + System.err.println(); + } + + if (awt2.getAnnotations().length == 0) { + errors++; + System.err.println("Expected annotations not found."); + } + + } + + private static AnnotatedWildcardType extractWildcard(String methodName) { + try { + return (AnnotatedWildcardType) + (((AnnotatedParameterizedType)(AnnotatedTypeHost.class.getMethod(methodName). + getAnnotatedReturnType())). + getAnnotatedActualTypeArguments()[0] ); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + static class TypeHost { + public void fooVoid() {return;} + + public int foo() {return 0;} + public String fooString() {return null;} + + public int[] fooIntArray() {return null;} + public String[] fooStringArray() {return null;} + public String [][] fooStringArrayArray() {return null;} + + public Set fooSetString() {return null;} + public E fooE() {return null;} + public F fooF() {return null;} + public G fooG() {return null;} + + public Set fooNumberSet() {return null;} + public Set fooNumberSet2() {return null;} + } + + @Retention(RetentionPolicy.RUNTIME) + @Target(ElementType.TYPE_USE) + static @interface AnnotType { + int value() default 0; + } + + static class AnnotatedTypeHost { + public /*@AnnotType(0)*/ void fooVoid() {return;} // Illegal to annotate void + + public @AnnotType(1) int foo() {return 0;} + public @AnnotType(2) String fooString() {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;} + + 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;} + + public @AnnotType(10) Set fooNumberSet() {return null;} + public @AnnotType(11) Set<@AnnotType(13) ? extends Number> fooNumberSet2() {return null;} + } +}