1 /*
   2  * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.
   8  *
   9  * This code is distributed in the hope that it will be useful, but WITHOUT
  10  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  11  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  12  * version 2 for more details (a copy is included in the LICENSE file that
  13  * accompanied this code).
  14  *
  15  * You should have received a copy of the GNU General Public License version
  16  * 2 along with this work; if not, write to the Free Software Foundation,
  17  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  18  *
  19  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  20  * or visit www.oracle.com if you need additional information or have any
  21  * questions.
  22  */
  23 
  24 /*
  25  * @test
  26  * @bug 8058202 8212081
  27  * @summary Test java.lang.Object methods on AnnotatedType objects.
  28  */
  29 
  30 import java.lang.annotation.*;
  31 import java.lang.reflect.*;
  32 import java.util.*;
  33 import java.util.regex.*;
  34 
  35 /**
  36  * Test toString, equals, and hashCode on various AnnotatedType objects.
  37  */
  38 
  39 public class TestObjectMethods {
  40     private static int errors = 0;
  41 
  42     /*
  43      * There are various subtypes of AnnotatedType implementations:
  44      *
  45      * AnnotatedType
  46      * AnnotatedArrayType
  47      * AnnotatedParameterizedType
  48      * AnnotatedTypeVariable
  49      * AnnotatedWildcardType
  50      *
  51      * The implementations of each these implementations are
  52      * examined. Wildcards don't appear as top-level types and need to
  53      * be extracted from bounds.
  54      *
  55      * AnnotatedTypes with and without annotations are examined as
  56      * well.
  57      */
  58     public static void main(String... args) {
  59         Class<?>[] testClasses = {TypeHost.class, AnnotatedTypeHost.class};
  60 
  61         for (Class<?> clazz : testClasses) {
  62             testEqualsReflexivity(clazz);
  63             testEquals(clazz);
  64         }
  65 
  66         testToString(TypeHost.class);
  67         testToString(AnnotatedTypeHost.class);
  68 
  69         testAnnotationsMatterForEquals(TypeHost.class, AnnotatedTypeHost.class);
  70 
  71         testGetAnnotations(TypeHost.class, false);
  72         testGetAnnotations(AnnotatedTypeHost.class, true);
  73 
  74         testWildcards();
  75 
  76         if (errors > 0) {
  77             throw new RuntimeException(errors + " errors");
  78         }
  79     }
  80 
  81     /*
  82      * For non-array types, verify toString version of the annotated
  83      * type ends with the same string as the generic type.
  84      */
  85     static void testToString(Class<?> clazz) {
  86         System.err.println("Testing toString on methods of class " + clazz.getName());
  87         Method[] methods = clazz.getDeclaredMethods();
  88         for (Method m : methods) {
  89             // Expected information about the type annotations stored
  90             // in a *declaration* annotation.
  91             AnnotTypeInfo annotTypeInfo = m.getAnnotation(AnnotTypeInfo.class);
  92             int expectedAnnotCount      = annotTypeInfo.count();
  93             Relation relation           = annotTypeInfo.relation();
  94 
  95             AnnotatedType annotType = m.getAnnotatedReturnType();
  96             String annotTypeString = annotType.toString();
  97 
  98             Type type = m.getGenericReturnType();
  99             String typeString = (type instanceof Class) ?
 100                 type.getTypeName() :
 101                 type.toString();
 102 
 103             boolean isArray = annotType instanceof AnnotatedArrayType;
 104             boolean isVoid = "void".equals(typeString);
 105 
 106             boolean valid;
 107 
 108             switch(relation) {
 109             case EQUAL:
 110                 valid = annotTypeString.equals(typeString);
 111                 break;
 112 
 113             case POSTFIX:
 114                 valid = annotTypeString.endsWith(typeString) &&
 115                     !annotTypeString.startsWith(typeString);
 116                 break;
 117 
 118             case STRIPPED:
 119                 String stripped = annotationRegex.matcher(annotTypeString).replaceAll("");
 120                 valid = typeString.replace(" ", "").equals(stripped.replace(" ", ""));
 121                 break;
 122 
 123             case ARRAY:
 124                 // Find final non-array component type and gets its name.
 125                 typeString = null;
 126 
 127                 AnnotatedType componentType = annotType;
 128                 while (componentType instanceof AnnotatedArrayType) {
 129                     AnnotatedArrayType annotatedArrayType = (AnnotatedArrayType) componentType;
 130                     componentType = annotatedArrayType.getAnnotatedGenericComponentType();
 131                 }
 132 
 133                 String componentName = componentType.getType().getTypeName();
 134                 valid = annotTypeString.contains(componentName);
 135                 break;
 136 
 137             case OTHER:
 138                 // No additional checks
 139                 valid = true;
 140                 break;
 141 
 142             default:
 143                 throw new AssertionError("Shouldn't be reached");
 144             }
 145 
 146             // Verify number of type annotations matches expected value
 147             Matcher matcher = annotationRegex.matcher(annotTypeString);
 148             if (expectedAnnotCount > 0) {
 149                 int i = expectedAnnotCount;
 150                 int annotCount = 0;
 151                 while (i > 0) {
 152                     boolean found = matcher.find();
 153                     if (found) {
 154                         i--;
 155                         annotCount++;
 156                     } else {
 157                         errors++;
 158                         System.err.println("\tExpected annotation not found: " + annotTypeString);
 159                     }
 160                 }
 161             }
 162 
 163             boolean found = matcher.find();
 164             if (found) {
 165                 errors++;
 166                 System.err.println("\tAnnotation found unexpectedly: " + annotTypeString);
 167             }
 168 
 169             if (!valid) {
 170                 errors++;
 171                 System.err.println(typeString + "\n" + annotTypeString +
 172                                    "\n " + valid  +
 173                                    "\n\n");
 174             }
 175         }
 176     }
 177 
 178     private static final Pattern annotationRegex = Pattern.compile("@TestObjectMethods\\$AnnotType\\(value=(\\p{Digit})+\\)");
 179 
 180     static void testGetAnnotations(Class<?> clazz, boolean annotationsExpectedOnMethods) {
 181         System.err.println("Testing getAnnotations on methods of class " + clazz.getName());
 182         Method[] methods = clazz.getDeclaredMethods();
 183         for (Method m : methods) {
 184             Type type = m.getGenericReturnType();
 185             AnnotatedType annotType = m.getAnnotatedReturnType();
 186             Annotation[] annotations = annotType.getAnnotations();
 187 
 188             boolean isVoid = "void".equals(type.toString());
 189 
 190             if (annotationsExpectedOnMethods && !isVoid) {
 191                 if (annotations.length == 0 ) {
 192                     errors++;
 193                     System.err.println("Expected annotations missing on " + annotType);
 194                 }
 195             } else {
 196                 if (annotations.length > 0 ) {
 197                     errors++;
 198                     System.err.println("Unexpected annotations present on " + annotType);
 199                 }
 200             }
 201         }
 202     }
 203 
 204     static void testEqualsReflexivity(Class<?> clazz) {
 205         System.err.println("Testing reflexivity of equals on methods of class " + clazz.getName());
 206         Method[] methods = clazz.getDeclaredMethods();
 207         for (Method m : methods) {
 208             checkTypesForEquality(m.getAnnotatedReturnType(),
 209                                   m.getAnnotatedReturnType(),
 210                                   true);
 211         }
 212     }
 213 
 214     private static void checkTypesForEquality(AnnotatedType annotType1,
 215                                               AnnotatedType annotType2,
 216                                               boolean expected) {
 217         boolean comparison = annotType1.equals(annotType2);
 218 
 219         if (comparison) {
 220             int hash1 = annotType1.hashCode();
 221             int hash2 = annotType2.hashCode();
 222             if (hash1 != hash2) {
 223                 errors++;
 224                 System.err.format("Equal AnnotatedTypes with unequal hash codes: %n%s%n%s%n",
 225                                   annotType1.toString(), annotType2.toString());
 226             }
 227         }
 228 
 229         if (comparison != expected) {
 230             errors++;
 231             System.err.println(annotType1);
 232             System.err.println(expected ? " is not equal to " : " is equal to ");
 233             System.err.println(annotType2);
 234             System.err.println();
 235         }
 236     }
 237 
 238     /*
 239      * For each of the type host classes, the return type of a method
 240      * should only equal the return type of that method.
 241      */
 242     static void testEquals(Class<?> clazz) {
 243         Method[] methods = clazz.getDeclaredMethods();
 244 
 245         for (int i = 0; i < methods.length; i++) {
 246             for (int j = 0; j < methods.length; j++) {
 247                 if (i == j)
 248                     continue;
 249                 else {
 250                     checkTypesForEquality(methods[i].getAnnotatedReturnType(),
 251                                           methods[j].getAnnotatedReturnType(),
 252                                           false);
 253                 }
 254             }
 255         }
 256     }
 257 
 258     /**
 259      * Roughly, compare the return types of corresponding methods on
 260      * TypeHost and AnnotatedtypeHost and verify the AnnotatedType
 261      * objects are *not* equal even if their underlying generic types
 262      * are.
 263      */
 264     static void testAnnotationsMatterForEquals(Class<?> clazz1, Class<?> clazz2) {
 265         System.err.println("Testing that presence/absence of annotations matters for equals comparison.");
 266 
 267         String methodName = null;
 268         for (Method method :  clazz1.getDeclaredMethods()) {
 269             if ("void".equals(method.getReturnType().toString())) {
 270                 continue;
 271             }
 272 
 273             methodName = method.getName();
 274             try {
 275                 checkTypesForEquality(method.getAnnotatedReturnType(),
 276                                       clazz2.getDeclaredMethod(methodName).getAnnotatedReturnType(),
 277                                       false);
 278             } catch (Exception e) {
 279                 errors++;
 280                 System.err.println("Method " + methodName + " not found.");
 281             }
 282         }
 283     }
 284 
 285     static void testWildcards() {
 286         System.err.println("Testing wildcards");
 287         // public @AnnotType(10) Set<? extends Number> fooNumberSet() {return null;}
 288         // public @AnnotType(11) Set<@AnnotType(13) ? extends Number> fooNumberSet2() {return null;}
 289         AnnotatedWildcardType awt1 = extractWildcard("fooNumberSet");
 290         AnnotatedWildcardType awt2 = extractWildcard("fooNumberSet2");
 291 
 292         if (!awt1.equals(extractWildcard("fooNumberSet")) ||
 293             !awt2.equals(extractWildcard("fooNumberSet2"))) {
 294             errors++;
 295             System.err.println("Bad equality comparison on wildcards.");
 296         }
 297 
 298         checkTypesForEquality(awt1, awt2, false);
 299 
 300         if (awt2.getAnnotations().length == 0) {
 301             errors++;
 302             System.err.println("Expected annotations not found.");
 303         }
 304     }
 305 
 306     private static AnnotatedWildcardType extractWildcard(String methodName) {
 307         try {
 308             return (AnnotatedWildcardType)
 309                 (((AnnotatedParameterizedType)(AnnotatedTypeHost.class.getMethod(methodName).
 310                                                getAnnotatedReturnType())).
 311                  getAnnotatedActualTypeArguments()[0] );
 312         } catch (Exception e) {
 313             throw new RuntimeException(e);
 314         }
 315     }
 316 
 317     // The TypeHost and AnnotatedTypeHost classes declare methods with
 318     // the same name and signatures but with the AnnotatedTypeHost
 319     // methods having annotations on their return type, where
 320     // possible.
 321 
 322     static class TypeHost<E, F extends Number> {
 323         @AnnotTypeInfo
 324         public void fooVoid() {return;}
 325 
 326         @AnnotTypeInfo
 327         public int foo() {return 0;}
 328 
 329         @AnnotTypeInfo
 330         public String fooString() {return null;}
 331 
 332         @AnnotTypeInfo
 333         public int[] fooIntArray() {return null;}
 334 
 335         @AnnotTypeInfo
 336         public String[] fooStringArray() {return null;}
 337 
 338         @AnnotTypeInfo
 339         public String [][] fooStringArrayArray() {return null;}
 340 
 341         @AnnotTypeInfo
 342         public Set<String> fooSetString() {return null;}
 343 
 344         @AnnotTypeInfo
 345         public Set<Number> fooSetNumber() {return null;}
 346 
 347         @AnnotTypeInfo
 348         public E fooE() {return null;}
 349 
 350         @AnnotTypeInfo
 351         public F fooF() {return null;}
 352 
 353         @AnnotTypeInfo
 354         public <G> G fooG() {return null;}
 355 
 356         @AnnotTypeInfo
 357         public  Set<? extends Number> fooNumberSet() {return null;}
 358 
 359         @AnnotTypeInfo
 360         public  Set<? extends Integer> fooNumberSet2() {return null;}
 361 
 362         @AnnotTypeInfo
 363         public  Set<? extends Long> fooNumberSet3() {return null;}
 364 
 365         @AnnotTypeInfo
 366         public Set<?> fooObjectSet() {return null;}
 367 
 368         @AnnotTypeInfo
 369         public List<? extends Object> fooObjectList() {return null;}
 370     }
 371 
 372     @Retention(RetentionPolicy.RUNTIME)
 373     @Target(ElementType.TYPE_USE)
 374     static @interface AnnotType {
 375         int value() default 0;
 376     }
 377 
 378     @Retention(RetentionPolicy.RUNTIME)
 379     @Target(ElementType.METHOD)
 380     static @interface AnnotTypeInfo {
 381         /**
 382          * Expected number of @AnnotType
 383          */
 384         int count() default 0;
 385 
 386         /**
 387          * Relation to genericString output.
 388          */
 389         Relation relation() default Relation.EQUAL;
 390     }
 391 
 392     /**
 393      * Expected relationship of toString output of AnnotatedType to
 394      * toGenericString output of underlying type.
 395      */
 396     static private enum Relation {
 397         EQUAL,
 398 
 399         /**
 400          * The toGenericString output is a postfix of the
 401          * AnnotatedType output; a leading annotation is expected.
 402          */
 403         POSTFIX,
 404 
 405         /**
 406          * If the annotations are stripped from the AnnotatedType
 407          * output and whitespace adjusted accordingly, it should equal
 408          * the toGenericString output.
 409          */
 410         STRIPPED,
 411 
 412         /**
 413          * The output of AnnotatedType for arrays would require more
 414          * extensive transformation to map to toGenericString output.
 415          */
 416         ARRAY,
 417 
 418        /**
 419          * Some other, harder to characterize, relationship. Currently
 420          * used for a wildcard where Object in "extends Object" is
 421          * annotated; the "extends Object" is elided in toGenericString.
 422          */
 423         OTHER;
 424     }
 425 
 426     static class AnnotatedTypeHost<E, F extends Number> {
 427         @AnnotTypeInfo
 428         public /*@AnnotType(0)*/ void fooVoid() {return;} // Illegal to annotate void
 429 
 430         @AnnotTypeInfo(count =1, relation = Relation.POSTFIX)
 431         @AnnotType(1)
 432         public int foo() {return 0;}
 433 
 434         @AnnotTypeInfo(count = 1, relation = Relation.POSTFIX)
 435         @AnnotType(2)
 436         public  String fooString() {return null;}
 437 
 438         @AnnotTypeInfo(count = 1, relation = Relation.ARRAY)
 439         public int @AnnotType(3) [] fooIntArray() {return null;}
 440 
 441         @AnnotTypeInfo(count = 1, relation = Relation.ARRAY)
 442         public String @AnnotType(4) [] fooStringArray() {return null;}
 443 
 444         @AnnotTypeInfo(count = 3, relation = Relation.ARRAY)
 445         @AnnotType(5)
 446         public String  @AnnotType(0) [] @AnnotType(1) [] fooStringArrayArray() {return null;}
 447 
 448         @AnnotTypeInfo(count = 1, relation = Relation.POSTFIX)
 449         @AnnotType(6)
 450         public Set<String> fooSetString() {return null;}
 451 
 452         @AnnotTypeInfo(count = 2, relation = Relation.STRIPPED)
 453         @AnnotType(7)
 454         public Set<@AnnotType(8) Number> fooSetNumber() {return null;}
 455 
 456         @AnnotTypeInfo(count = 1, relation = Relation.POSTFIX)
 457         @AnnotType(9)
 458         public E fooE() {return null;}
 459 
 460         @AnnotTypeInfo(count = 1, relation = Relation.POSTFIX)
 461         @AnnotType(10)
 462         public F fooF() {return null;}
 463 
 464         @AnnotTypeInfo(count = 1, relation = Relation.POSTFIX)
 465         @AnnotType(11)
 466         public <G> G fooG() {return null;}
 467 
 468         @AnnotTypeInfo(count = 1, relation = Relation.POSTFIX)
 469         @AnnotType(12)
 470         public Set<? extends Number> fooNumberSet() {return null;}
 471 
 472         @AnnotTypeInfo(count = 2, relation = Relation.STRIPPED)
 473         @AnnotType(13)
 474         public Set<@AnnotType(14) ? extends Number> fooNumberSet2() {return null;}
 475 
 476         @AnnotTypeInfo(count = 2, relation = Relation.STRIPPED)
 477         @AnnotType(15)
 478         public Set< ? extends @AnnotType(16) Long> fooNumberSet3() {return null;}
 479 
 480         @AnnotTypeInfo(count = 2, relation = Relation.STRIPPED)
 481         @AnnotType(16)
 482         public Set<@AnnotType(17) ?> fooObjectSet() {return null;}
 483 
 484         @AnnotTypeInfo(count = 2, relation = Relation.OTHER)
 485         @AnnotType(18)
 486         public List<? extends @AnnotType(19) Object> fooObjectList() {return null;}
 487     }
 488 }