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\\(value=(\\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 
 309     private static AnnotatedWildcardType extractWildcard(String methodName) {
 310         try {
 311             return (AnnotatedWildcardType)
 312                 (((AnnotatedParameterizedType)(AnnotatedTypeHost.class.getMethod(methodName).
 313                                                getAnnotatedReturnType())).
 314                  getAnnotatedActualTypeArguments()[0] );
 315         } catch (Exception e) {
 316             throw new RuntimeException(e);
 317         }
 318     }
 319 
 320     static void testFbounds() {
 321         // Make sure equals and hashCode work fine for a type
 322         // involving an F-bound, in particular Comparable<E> in
 323         // java.lang.Enum:
 324         //
 325         // class Enum<E extends Enum<E>>
 326         // implements Constable, Comparable<E>, Serializable
 327 
 328         AnnotatedType[] types = Enum.class.getAnnotatedInterfaces();
 329 
 330         for (int i = 0; i < types.length; i ++) {
 331             for (int j = 0; j < types.length; j ++) {
 332                 checkTypesForEquality(types[i], types[j], i == j);
 333             }
 334         }
 335     }
 336 
 337     // The TypeHost and AnnotatedTypeHost classes declare methods with
 338     // the same name and signatures but with the AnnotatedTypeHost
 339     // methods having annotations on their return type, where
 340     // possible.
 341 
 342     static class TypeHost<E, F extends Number> {
 343         @AnnotTypeInfo
 344         public void fooVoid() {return;}
 345 
 346         @AnnotTypeInfo
 347         public int foo() {return 0;}
 348 
 349         @AnnotTypeInfo
 350         public String fooString() {return null;}
 351 
 352         @AnnotTypeInfo
 353         public int[] fooIntArray() {return null;}
 354 
 355         @AnnotTypeInfo
 356         public String[] fooStringArray() {return null;}
 357 
 358         @AnnotTypeInfo
 359         public String [][] fooStringArrayArray() {return null;}
 360 
 361         @AnnotTypeInfo
 362         public Set<String> fooSetString() {return null;}
 363 
 364         @AnnotTypeInfo
 365         public Set<Number> fooSetNumber() {return null;}
 366 
 367         @AnnotTypeInfo
 368         public E fooE() {return null;}
 369 
 370         @AnnotTypeInfo
 371         public F fooF() {return null;}
 372 
 373         @AnnotTypeInfo
 374         public <G> G fooG() {return null;}
 375 
 376         @AnnotTypeInfo
 377         public  Set<? extends Number> fooNumberSet() {return null;}
 378 
 379         @AnnotTypeInfo
 380         public  Set<? extends Integer> fooNumberSet2() {return null;}
 381 
 382         @AnnotTypeInfo
 383         public  Set<? extends Long> fooNumberSet3() {return null;}
 384 
 385         @AnnotTypeInfo
 386         public Set<?> fooObjectSet() {return null;}
 387 
 388         @AnnotTypeInfo
 389         public List<? extends Object> fooObjectList() {return null;}
 390     }
 391 
 392     @Retention(RetentionPolicy.RUNTIME)
 393     @Target(ElementType.TYPE_USE)
 394     static @interface AnnotType {
 395         int value() default 0;
 396     }
 397 
 398     @Retention(RetentionPolicy.RUNTIME)
 399     @Target(ElementType.METHOD)
 400     static @interface AnnotTypeInfo {
 401         /**
 402          * Expected number of @AnnotType
 403          */
 404         int count() default 0;
 405 
 406         /**
 407          * Relation to genericString output.
 408          */
 409         Relation relation() default Relation.EQUAL;
 410     }
 411 
 412     /**
 413      * Expected relationship of toString output of AnnotatedType to
 414      * toGenericString output of underlying type.
 415      */
 416     static private enum Relation {
 417         EQUAL,
 418 
 419         /**
 420          * The toGenericString output is a postfix of the
 421          * AnnotatedType output; a leading annotation is expected.
 422          */
 423         POSTFIX,
 424 
 425         /**
 426          * If the annotations are stripped from the AnnotatedType
 427          * output and whitespace adjusted accordingly, it should equal
 428          * the toGenericString output.
 429          */
 430         STRIPPED,
 431 
 432         /**
 433          * The output of AnnotatedType for arrays would require more
 434          * extensive transformation to map to toGenericString output.
 435          */
 436         ARRAY,
 437 
 438        /**
 439          * Some other, harder to characterize, relationship. Currently
 440          * used for a wildcard where Object in "extends Object" is
 441          * annotated; the "extends Object" is elided in toGenericString.
 442          */
 443         OTHER;
 444     }
 445 
 446     static class AnnotatedTypeHost<E, F extends Number> {
 447         @AnnotTypeInfo
 448         public /*@AnnotType(0)*/ void fooVoid() {return;} // Illegal to annotate void
 449 
 450         @AnnotTypeInfo(count =1, relation = Relation.POSTFIX)
 451         @AnnotType(1)
 452         public int foo() {return 0;}
 453 
 454         @AnnotTypeInfo(count = 1, relation = Relation.POSTFIX)
 455         @AnnotType(2)
 456         public  String fooString() {return null;}
 457 
 458         @AnnotTypeInfo(count = 1, relation = Relation.ARRAY)
 459         public int @AnnotType(3) [] fooIntArray() {return null;}
 460 
 461         @AnnotTypeInfo(count = 1, relation = Relation.ARRAY)
 462         public String @AnnotType(4) [] fooStringArray() {return null;}
 463 
 464         @AnnotTypeInfo(count = 3, relation = Relation.ARRAY)
 465         @AnnotType(5)
 466         public String  @AnnotType(0) [] @AnnotType(1) [] fooStringArrayArray() {return null;}
 467 
 468         @AnnotTypeInfo(count = 1, relation = Relation.POSTFIX)
 469         @AnnotType(6)
 470         public Set<String> fooSetString() {return null;}
 471 
 472         @AnnotTypeInfo(count = 2, relation = Relation.STRIPPED)
 473         @AnnotType(7)
 474         public Set<@AnnotType(8) Number> fooSetNumber() {return null;}
 475 
 476         @AnnotTypeInfo(count = 1, relation = Relation.POSTFIX)
 477         @AnnotType(9)
 478         public E fooE() {return null;}
 479 
 480         @AnnotTypeInfo(count = 1, relation = Relation.POSTFIX)
 481         @AnnotType(10)
 482         public F fooF() {return null;}
 483 
 484         @AnnotTypeInfo(count = 1, relation = Relation.POSTFIX)
 485         @AnnotType(11)
 486         public <G> G fooG() {return null;}
 487 
 488         @AnnotTypeInfo(count = 1, relation = Relation.POSTFIX)
 489         @AnnotType(12)
 490         public Set<? extends Number> fooNumberSet() {return null;}
 491 
 492         @AnnotTypeInfo(count = 2, relation = Relation.STRIPPED)
 493         @AnnotType(13)
 494         public Set<@AnnotType(14) ? extends Number> fooNumberSet2() {return null;}
 495 
 496         @AnnotTypeInfo(count = 2, relation = Relation.STRIPPED)
 497         @AnnotType(15)
 498         public Set< ? extends @AnnotType(16) Long> fooNumberSet3() {return null;}
 499 
 500         @AnnotTypeInfo(count = 2, relation = Relation.STRIPPED)
 501         @AnnotType(16)
 502         public Set<@AnnotType(17) ?> fooObjectSet() {return null;}
 503 
 504         @AnnotTypeInfo(count = 2, relation = Relation.OTHER)
 505         @AnnotType(18)
 506         public List<? extends @AnnotType(19) Object> fooObjectList() {return null;}
 507     }
 508 }