1 /*
   2  * Copyright (c) 2018, 2019, 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 8224012
  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         testFbounds();
  77 
  78         if (errors > 0) {
  79             throw new RuntimeException(errors + " errors");
  80         }
  81     }
  82 
  83     /*
  84      * For non-array types, verify toString version of the annotated
  85      * type ends with the same string as the generic type.
  86      */
  87     static void testToString(Class<?> clazz) {
  88         System.err.println("Testing toString on methods of class " + clazz.getName());
  89         Method[] methods = clazz.getDeclaredMethods();
  90         for (Method m : methods) {
  91             // Expected information about the type annotations stored
  92             // in a *declaration* annotation.
  93             AnnotTypeInfo annotTypeInfo = m.getAnnotation(AnnotTypeInfo.class);
  94             int expectedAnnotCount      = annotTypeInfo.count();
  95             Relation relation           = annotTypeInfo.relation();
  96 
  97             AnnotatedType annotType = m.getAnnotatedReturnType();
  98             String annotTypeString = annotType.toString();
  99 
 100             Type type = m.getGenericReturnType();
 101             String typeString = (type instanceof Class) ?
 102                 type.getTypeName() :
 103                 type.toString();
 104 
 105             boolean isArray = annotType instanceof AnnotatedArrayType;
 106             boolean isVoid = "void".equals(typeString);
 107 
 108             boolean valid;
 109 
 110             switch(relation) {
 111             case EQUAL:
 112                 valid = annotTypeString.equals(typeString);
 113                 break;
 114 
 115             case POSTFIX:
 116                 valid = annotTypeString.endsWith(typeString) &&
 117                     !annotTypeString.startsWith(typeString);
 118                 break;
 119 
 120             case STRIPPED:
 121                 String stripped = annotationRegex.matcher(annotTypeString).replaceAll("");
 122                 valid = typeString.replace(" ", "").equals(stripped.replace(" ", ""));
 123                 break;
 124 
 125             case ARRAY:
 126                 // Find final non-array component type and gets its name.
 127                 typeString = null;
 128 
 129                 AnnotatedType componentType = annotType;
 130                 while (componentType instanceof AnnotatedArrayType) {
 131                     AnnotatedArrayType annotatedArrayType = (AnnotatedArrayType) componentType;
 132                     componentType = annotatedArrayType.getAnnotatedGenericComponentType();
 133                 }
 134 
 135                 String componentName = componentType.getType().getTypeName();
 136                 valid = annotTypeString.contains(componentName);
 137                 break;
 138 
 139             case OTHER:
 140                 // No additional checks
 141                 valid = true;
 142                 break;
 143 
 144             default:
 145                 throw new AssertionError("Shouldn't be reached");
 146             }
 147 
 148             // Verify number of type annotations matches expected value
 149             Matcher matcher = annotationRegex.matcher(annotTypeString);
 150             if (expectedAnnotCount > 0) {
 151                 int i = expectedAnnotCount;
 152                 int annotCount = 0;
 153                 while (i > 0) {
 154                     boolean found = matcher.find();
 155                     if (found) {
 156                         i--;
 157                         annotCount++;
 158                     } else {
 159                         errors++;
 160                         System.err.println("\tExpected annotation not found: " + annotTypeString);
 161                     }
 162                 }
 163             }
 164 
 165             boolean found = matcher.find();
 166             if (found) {
 167                 errors++;
 168                 System.err.println("\tAnnotation found unexpectedly: " + annotTypeString);
 169             }
 170 
 171             if (!valid) {
 172                 errors++;
 173                 System.err.println(typeString + "\n" + annotTypeString +
 174                                    "\n " + valid  +
 175                                    "\n\n");
 176             }
 177         }
 178     }
 179 
 180     private static final Pattern annotationRegex = Pattern.compile("@TestObjectMethods\\$AnnotType\\((\\p{Digit})+\\)");
 181 
 182     static void testGetAnnotations(Class<?> clazz, boolean annotationsExpectedOnMethods) {
 183         System.err.println("Testing getAnnotations on methods of class " + clazz.getName());
 184         Method[] methods = clazz.getDeclaredMethods();
 185         for (Method m : methods) {
 186             Type type = m.getGenericReturnType();
 187             AnnotatedType annotType = m.getAnnotatedReturnType();
 188             Annotation[] annotations = annotType.getAnnotations();
 189 
 190             boolean isVoid = "void".equals(type.toString());
 191 
 192             if (annotationsExpectedOnMethods && !isVoid) {
 193                 if (annotations.length == 0 ) {
 194                     errors++;
 195                     System.err.println("Expected annotations missing on " + annotType);
 196                 }
 197             } else {
 198                 if (annotations.length > 0 ) {
 199                     errors++;
 200                     System.err.println("Unexpected annotations present on " + annotType);
 201                 }
 202             }
 203         }
 204     }
 205 
 206     static void testEqualsReflexivity(Class<?> clazz) {
 207         System.err.println("Testing reflexivity of equals on methods of class " + clazz.getName());
 208         Method[] methods = clazz.getDeclaredMethods();
 209         for (Method m : methods) {
 210             checkTypesForEquality(m.getAnnotatedReturnType(),
 211                                   m.getAnnotatedReturnType(),
 212                                   true);
 213         }
 214     }
 215 
 216     private static void checkTypesForEquality(AnnotatedType annotType1,
 217                                               AnnotatedType annotType2,
 218                                               boolean expected) {
 219         boolean comparison = annotType1.equals(annotType2);
 220 
 221         if (comparison) {
 222             int hash1 = annotType1.hashCode();
 223             int hash2 = annotType2.hashCode();
 224             if (hash1 != hash2) {
 225                 errors++;
 226                 System.err.format("Equal AnnotatedTypes with unequal hash codes: %n%s%n%s%n",
 227                                   annotType1.toString(), annotType2.toString());
 228             }
 229         }
 230 
 231         if (comparison != expected) {
 232             errors++;
 233             System.err.println(annotType1);
 234             System.err.println(expected ? " is not equal to " : " is equal to ");
 235             System.err.println(annotType2);
 236             System.err.println();
 237         }
 238     }
 239 
 240     /*
 241      * For each of the type host classes, the return type of a method
 242      * should only equal the return type of that method.
 243      */
 244     static void testEquals(Class<?> clazz) {
 245         Method[] methods = clazz.getDeclaredMethods();
 246 
 247         for (int i = 0; i < methods.length; i++) {
 248             for (int j = 0; j < methods.length; j++) {
 249                 if (i == j)
 250                     continue;
 251                 else {
 252                     checkTypesForEquality(methods[i].getAnnotatedReturnType(),
 253                                           methods[j].getAnnotatedReturnType(),
 254                                           false);
 255                 }
 256             }
 257         }
 258     }
 259 
 260     /**
 261      * Roughly, compare the return types of corresponding methods on
 262      * TypeHost and AnnotatedtypeHost and verify the AnnotatedType
 263      * objects are *not* equal even if their underlying generic types
 264      * are.
 265      */
 266     static void testAnnotationsMatterForEquals(Class<?> clazz1, Class<?> clazz2) {
 267         System.err.println("Testing that presence/absence of annotations matters for equals comparison.");
 268 
 269         String methodName = null;
 270         for (Method method :  clazz1.getDeclaredMethods()) {
 271             if ("void".equals(method.getReturnType().toString())) {
 272                 continue;
 273             }
 274 
 275             methodName = method.getName();
 276             try {
 277                 checkTypesForEquality(method.getAnnotatedReturnType(),
 278                                       clazz2.getDeclaredMethod(methodName).getAnnotatedReturnType(),
 279                                       false);
 280             } catch (Exception e) {
 281                 errors++;
 282                 System.err.println("Method " + methodName + " not found.");
 283             }
 284         }
 285     }
 286 
 287     static void testWildcards() {
 288         System.err.println("Testing wildcards");
 289         // public @AnnotType(10) Set<? extends Number> fooNumberSet() {return null;}
 290         // public @AnnotType(11) Set<@AnnotType(13) ? extends Number> fooNumberSet2() {return null;}
 291         AnnotatedWildcardType awt1 = extractWildcard("fooNumberSet");
 292         AnnotatedWildcardType awt2 = extractWildcard("fooNumberSet2");
 293 
 294         if (!awt1.equals(extractWildcard("fooNumberSet")) ||
 295             !awt2.equals(extractWildcard("fooNumberSet2"))) {
 296             errors++;
 297             System.err.println("Bad equality comparison on wildcards.");
 298         }
 299 
 300         checkTypesForEquality(awt1, awt2, false);
 301 
 302         if (awt2.getAnnotations().length == 0) {
 303             errors++;
 304             System.err.println("Expected annotations not found.");
 305         }
 306     }
 307 
 308     private static AnnotatedWildcardType extractWildcard(String methodName) {
 309         try {
 310             return (AnnotatedWildcardType)
 311                 (((AnnotatedParameterizedType)(AnnotatedTypeHost.class.getMethod(methodName).
 312                                                getAnnotatedReturnType())).
 313                  getAnnotatedActualTypeArguments()[0] );
 314         } catch (Exception e) {
 315             throw new RuntimeException(e);
 316         }
 317     }
 318 
 319     static void testFbounds() {
 320         // Make sure equals and hashCode work fine for a type
 321         // involving an F-bound, in particular Comparable<E> in
 322         // java.lang.Enum:
 323         //
 324         // class Enum<E extends Enum<E>>
 325         // implements Constable, Comparable<E>, Serializable
 326 
 327         AnnotatedType[] types = Enum.class.getAnnotatedInterfaces();
 328 
 329         for (int i = 0; i < types.length; i ++) {
 330             for (int j = 0; j < types.length; j ++) {
 331                 checkTypesForEquality(types[i], types[j], i == j);
 332             }
 333         }
 334     }
 335 
 336     // The TypeHost and AnnotatedTypeHost classes declare methods with
 337     // the same name and signatures but with the AnnotatedTypeHost
 338     // methods having annotations on their return type, where
 339     // possible.
 340 
 341     static class TypeHost<E, F extends Number> {
 342         @AnnotTypeInfo
 343         public void fooVoid() {return;}
 344 
 345         @AnnotTypeInfo
 346         public int foo() {return 0;}
 347 
 348         @AnnotTypeInfo
 349         public String fooString() {return null;}
 350 
 351         @AnnotTypeInfo
 352         public int[] fooIntArray() {return null;}
 353 
 354         @AnnotTypeInfo
 355         public String[] fooStringArray() {return null;}
 356 
 357         @AnnotTypeInfo
 358         public String [][] fooStringArrayArray() {return null;}
 359 
 360         @AnnotTypeInfo
 361         public Set<String> fooSetString() {return null;}
 362 
 363         @AnnotTypeInfo
 364         public Set<Number> fooSetNumber() {return null;}
 365 
 366         @AnnotTypeInfo
 367         public E fooE() {return null;}
 368 
 369         @AnnotTypeInfo
 370         public F fooF() {return null;}
 371 
 372         @AnnotTypeInfo
 373         public <G> G fooG() {return null;}
 374 
 375         @AnnotTypeInfo
 376         public  Set<? extends Number> fooNumberSet() {return null;}
 377 
 378         @AnnotTypeInfo
 379         public  Set<? extends Integer> fooNumberSet2() {return null;}
 380 
 381         @AnnotTypeInfo
 382         public  Set<? extends Long> fooNumberSet3() {return null;}
 383 
 384         @AnnotTypeInfo
 385         public Set<?> fooObjectSet() {return null;}
 386 
 387         @AnnotTypeInfo
 388         public List<? extends Object> fooObjectList() {return null;}
 389     }
 390 
 391     @Retention(RetentionPolicy.RUNTIME)
 392     @Target(ElementType.TYPE_USE)
 393     static @interface AnnotType {
 394         int value() default 0;
 395     }
 396 
 397     @Retention(RetentionPolicy.RUNTIME)
 398     @Target(ElementType.METHOD)
 399     static @interface AnnotTypeInfo {
 400         /**
 401          * Expected number of @AnnotType
 402          */
 403         int count() default 0;
 404 
 405         /**
 406          * Relation to genericString output.
 407          */
 408         Relation relation() default Relation.EQUAL;
 409     }
 410 
 411     /**
 412      * Expected relationship of toString output of AnnotatedType to
 413      * toGenericString output of underlying type.
 414      */
 415     static private enum Relation {
 416         EQUAL,
 417 
 418         /**
 419          * The toGenericString output is a postfix of the
 420          * AnnotatedType output; a leading annotation is expected.
 421          */
 422         POSTFIX,
 423 
 424         /**
 425          * If the annotations are stripped from the AnnotatedType
 426          * output and whitespace adjusted accordingly, it should equal
 427          * the toGenericString output.
 428          */
 429         STRIPPED,
 430 
 431         /**
 432          * The output of AnnotatedType for arrays would require more
 433          * extensive transformation to map to toGenericString output.
 434          */
 435         ARRAY,
 436 
 437        /**
 438          * Some other, harder to characterize, relationship. Currently
 439          * used for a wildcard where Object in "extends Object" is
 440          * annotated; the "extends Object" is elided in toGenericString.
 441          */
 442         OTHER;
 443     }
 444 
 445     static class AnnotatedTypeHost<E, F extends Number> {
 446         @AnnotTypeInfo
 447         public /*@AnnotType(0)*/ void fooVoid() {return;} // Illegal to annotate void
 448 
 449         @AnnotTypeInfo(count =1, relation = Relation.POSTFIX)
 450         @AnnotType(1)
 451         public int foo() {return 0;}
 452 
 453         @AnnotTypeInfo(count = 1, relation = Relation.POSTFIX)
 454         @AnnotType(2)
 455         public  String fooString() {return null;}
 456 
 457         @AnnotTypeInfo(count = 1, relation = Relation.ARRAY)
 458         public int @AnnotType(3) [] fooIntArray() {return null;}
 459 
 460         @AnnotTypeInfo(count = 1, relation = Relation.ARRAY)
 461         public String @AnnotType(4) [] fooStringArray() {return null;}
 462 
 463         @AnnotTypeInfo(count = 3, relation = Relation.ARRAY)
 464         @AnnotType(5)
 465         public String  @AnnotType(0) [] @AnnotType(1) [] fooStringArrayArray() {return null;}
 466 
 467         @AnnotTypeInfo(count = 1, relation = Relation.POSTFIX)
 468         @AnnotType(6)
 469         public Set<String> fooSetString() {return null;}
 470 
 471         @AnnotTypeInfo(count = 2, relation = Relation.STRIPPED)
 472         @AnnotType(7)
 473         public Set<@AnnotType(8) Number> fooSetNumber() {return null;}
 474 
 475         @AnnotTypeInfo(count = 1, relation = Relation.POSTFIX)
 476         @AnnotType(9)
 477         public E fooE() {return null;}
 478 
 479         @AnnotTypeInfo(count = 1, relation = Relation.POSTFIX)
 480         @AnnotType(10)
 481         public F fooF() {return null;}
 482 
 483         @AnnotTypeInfo(count = 1, relation = Relation.POSTFIX)
 484         @AnnotType(11)
 485         public <G> G fooG() {return null;}
 486 
 487         @AnnotTypeInfo(count = 1, relation = Relation.POSTFIX)
 488         @AnnotType(12)
 489         public Set<? extends Number> fooNumberSet() {return null;}
 490 
 491         @AnnotTypeInfo(count = 2, relation = Relation.STRIPPED)
 492         @AnnotType(13)
 493         public Set<@AnnotType(14) ? extends Number> fooNumberSet2() {return null;}
 494 
 495         @AnnotTypeInfo(count = 2, relation = Relation.STRIPPED)
 496         @AnnotType(15)
 497         public Set< ? extends @AnnotType(16) Long> fooNumberSet3() {return null;}
 498 
 499         @AnnotTypeInfo(count = 2, relation = Relation.STRIPPED)
 500         @AnnotType(16)
 501         public Set<@AnnotType(17) ?> fooObjectSet() {return null;}
 502 
 503         @AnnotTypeInfo(count = 2, relation = Relation.OTHER)
 504         @AnnotType(18)
 505         public List<? extends @AnnotType(19) Object> fooObjectList() {return null;}
 506     }
 507 }