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             default:
 138                 throw new AssertionError("Shouldn't be reached");
 139             }
 140 
 141             // Verify number of type annotations matches expected value
 142             Matcher matcher = annotationRegex.matcher(annotTypeString);
 143             if (expectedAnnotCount > 0) {
 144                 int i = expectedAnnotCount;
 145                 int annotCount = 0;
 146                 while (i > 0) {
 147                     boolean found = matcher.find();
 148                     if (found) {
 149                         i--;
 150                         annotCount++;
 151                     } else {
 152                         errors++;
 153                         System.err.println("\tExpected annotation not found: " + annotTypeString);
 154                     }
 155                 }
 156             }
 157 
 158             boolean found = matcher.find();
 159             if (found) {
 160                 errors++;
 161                 System.err.println("\tAnnotation found unexpectedly: " + annotTypeString);
 162             }
 163 
 164             if (!valid) {
 165                 errors++;
 166                 System.err.println(typeString + "\n" + annotTypeString +
 167                                    "\n " + valid  +
 168                                    "\n\n");
 169             }
 170         }
 171     }
 172 
 173     private static final Pattern annotationRegex = Pattern.compile("@TestObjectMethods\\$AnnotType\\(value=(\\p{Digit})+\\)");
 174 
 175     static void testGetAnnotations(Class<?> clazz, boolean annotationsExpectedOnMethods) {
 176         System.err.println("Testing getAnnotations on methods of class " + clazz.getName());
 177         Method[] methods = clazz.getDeclaredMethods();
 178         for (Method m : methods) {
 179             Type type = m.getGenericReturnType();
 180             AnnotatedType annotType = m.getAnnotatedReturnType();
 181             Annotation[] annotations = annotType.getAnnotations();
 182 
 183             boolean isVoid = "void".equals(type.toString());
 184 
 185             if (annotationsExpectedOnMethods && !isVoid) {
 186                 if (annotations.length == 0 ) {
 187                     errors++;
 188                     System.err.println("Expected annotations missing on " + annotType);
 189                 }
 190             } else {
 191                 if (annotations.length > 0 ) {
 192                     errors++;
 193                     System.err.println("Unexpected annotations present on " + annotType);
 194                 }
 195             }
 196         }
 197     }
 198 
 199     static void testEqualsReflexivity(Class<?> clazz) {
 200         System.err.println("Testing reflexivity of equals on methods of class " + clazz.getName());
 201         Method[] methods = clazz.getDeclaredMethods();
 202         for (Method m : methods) {
 203             checkTypesForEquality(m.getAnnotatedReturnType(),
 204                                   m.getAnnotatedReturnType(),
 205                                   true);
 206         }
 207     }
 208 
 209     private static void checkTypesForEquality(AnnotatedType annotType1,
 210                                               AnnotatedType annotType2,
 211                                               boolean expected) {
 212         boolean comparison = annotType1.equals(annotType2);
 213 
 214         if (comparison) {
 215             int hash1 = annotType1.hashCode();
 216             int hash2 = annotType2.hashCode();
 217             if (hash1 != hash2) {
 218                 errors++;
 219                 System.err.format("Equal AnnotatedTypes with unequal hash codes: %n%s%n%s%n",
 220                                   annotType1.toString(), annotType2.toString());
 221             }
 222         }
 223 
 224         if (comparison != expected) {
 225             errors++;
 226             System.err.println(annotType1);
 227             System.err.println(expected ? " is not equal to " : " is equal to ");
 228             System.err.println(annotType2);
 229             System.err.println();
 230         }
 231     }
 232 
 233     /*
 234      * For each of the type host classes, the return type of a method
 235      * should only equal the return type of that method.
 236      */
 237     static void testEquals(Class<?> clazz) {
 238         Method[] methods = clazz.getDeclaredMethods();
 239 
 240         for (int i = 0; i < methods.length; i++) {
 241             for (int j = 0; j < methods.length; j++) {
 242                 if (i == j)
 243                     continue;
 244                 else {
 245                     checkTypesForEquality(methods[i].getAnnotatedReturnType(),
 246                                           methods[j].getAnnotatedReturnType(),
 247                                           false);
 248                 }
 249             }
 250         }
 251     }
 252 
 253     /**
 254      * Roughly, compare the return types of corresponding methods on
 255      * TypeHost and AnnotatedtypeHost and verify the AnnotatedType
 256      * objects are *not* equal even if their underlying generic types
 257      * are.
 258      */
 259     static void testAnnotationsMatterForEquals(Class<?> clazz1, Class<?> clazz2) {
 260         System.err.println("Testing that presence/absence of annotations matters for equals comparison.");
 261 
 262         String methodName = null;
 263         for (Method method :  clazz1.getDeclaredMethods()) {
 264             if ("void".equals(method.getReturnType().toString())) {
 265                 continue;
 266             }
 267 
 268             methodName = method.getName();
 269             try {
 270                 checkTypesForEquality(method.getAnnotatedReturnType(),
 271                                       clazz2.getDeclaredMethod(methodName).getAnnotatedReturnType(),
 272                                       false);
 273             } catch (Exception e) {
 274                 errors++;
 275                 System.err.println("Method " + methodName + " not found.");
 276             }
 277         }
 278     }
 279 
 280     static void testWildcards() {
 281         System.err.println("Testing wildcards");
 282         // public @AnnotType(10) Set<? extends Number> fooNumberSet() {return null;}
 283         // public @AnnotType(11) Set<@AnnotType(13) ? extends Number> fooNumberSet2() {return null;}
 284         AnnotatedWildcardType awt1 = extractWildcard("fooNumberSet");
 285         AnnotatedWildcardType awt2 = extractWildcard("fooNumberSet2");
 286 
 287         if (!awt1.equals(extractWildcard("fooNumberSet")) ||
 288             !awt2.equals(extractWildcard("fooNumberSet2"))) {
 289             errors++;
 290             System.err.println("Bad equality comparison on wildcards.");
 291         }
 292 
 293         checkTypesForEquality(awt1, awt2, false);
 294 
 295         if (awt2.getAnnotations().length == 0) {
 296             errors++;
 297             System.err.println("Expected annotations not found.");
 298         }
 299     }
 300 
 301     private static AnnotatedWildcardType extractWildcard(String methodName) {
 302         try {
 303             return (AnnotatedWildcardType)
 304                 (((AnnotatedParameterizedType)(AnnotatedTypeHost.class.getMethod(methodName).
 305                                                getAnnotatedReturnType())).
 306                  getAnnotatedActualTypeArguments()[0] );
 307         } catch (Exception e) {
 308             throw new RuntimeException(e);
 309         }
 310     }
 311 
 312     // The TypeHost and AnnotatedTypeHost classes declare methods with
 313     // the same name and signatures but with the AnnotatedTypeHost
 314     // methods having annotations on their return type, where
 315     // possible.
 316 
 317     static class TypeHost<E, F extends Number> {
 318         @AnnotTypeInfo
 319         public void fooVoid() {return;}
 320 
 321         @AnnotTypeInfo
 322         public int foo() {return 0;}
 323 
 324         @AnnotTypeInfo
 325         public String fooString() {return null;}
 326 
 327         @AnnotTypeInfo
 328         public int[] fooIntArray() {return null;}
 329 
 330         @AnnotTypeInfo
 331         public String[] fooStringArray() {return null;}
 332 
 333         @AnnotTypeInfo
 334         public String [][] fooStringArrayArray() {return null;}
 335 
 336         @AnnotTypeInfo
 337         public Set<String> fooSetString() {return null;}
 338 
 339         @AnnotTypeInfo
 340         public Set<Number> fooSetNumber() {return null;}
 341 
 342         @AnnotTypeInfo
 343         public E fooE() {return null;}
 344 
 345         @AnnotTypeInfo
 346         public F fooF() {return null;}
 347 
 348         @AnnotTypeInfo
 349         public <G> G fooG() {return null;}
 350 
 351         @AnnotTypeInfo
 352         public  Set<? extends Number> fooNumberSet() {return null;}
 353 
 354         @AnnotTypeInfo
 355         public  Set<? extends Integer> fooNumberSet2() {return null;}
 356         
 357         @AnnotTypeInfo
 358         public  Set<? extends Long> fooNumberSet3() {return null;}
 359     }
 360 
 361     @Retention(RetentionPolicy.RUNTIME)
 362     @Target(ElementType.TYPE_USE)
 363     static @interface AnnotType {
 364         int value() default 0;
 365     }
 366 
 367     @Retention(RetentionPolicy.RUNTIME)
 368     @Target(ElementType.METHOD)
 369     static @interface AnnotTypeInfo {
 370         /**
 371          * Expected number of @AnnotType 
 372          */
 373         int count() default 0;
 374 
 375         /**
 376          * Relation to genericString output.
 377          */
 378         Relation relation() default Relation.EQUAL;
 379     }
 380     
 381     /**
 382      * Expected relationship of toString output of AnnotatedType to
 383      * toGenericString output of underlying type.
 384      */
 385     static private enum Relation {
 386         EQUAL,
 387 
 388         /**
 389          * The toGenericString output is a postfix of the
 390          * AnnotatedType output; a leading annotation is expected.
 391          */
 392         POSTFIX,
 393 
 394         /**
 395          * If the annotations are stripped from the AnnotatedType
 396          * output and whitespace adjusted accordingly, it should equal
 397          * the toGenericString output.
 398          */
 399         STRIPPED,
 400 
 401         /**
 402          * The output of AnnotatedType for arrays would require more
 403          * extensive transformation to map to toGenericString output.
 404          */
 405         ARRAY;
 406     }
 407 
 408     static class AnnotatedTypeHost<E, F extends Number> {
 409         @AnnotTypeInfo
 410         public /*@AnnotType(0)*/ void fooVoid() {return;} // Illegal to annotate void
 411 
 412         @AnnotTypeInfo(count =1, relation = Relation.POSTFIX)
 413         @AnnotType(1)
 414         public int foo() {return 0;}
 415 
 416         @AnnotTypeInfo(count = 1, relation = Relation.POSTFIX)
 417         @AnnotType(2)
 418         public  String fooString() {return null;}
 419 
 420         @AnnotTypeInfo(count = 1, relation = Relation.ARRAY)
 421         public int @AnnotType(3) [] fooIntArray() {return null;}
 422         
 423         @AnnotTypeInfo(count = 1, relation = Relation.ARRAY)
 424         public String @AnnotType(4) [] fooStringArray() {return null;}
 425 
 426         @AnnotTypeInfo(count = 3, relation = Relation.ARRAY)
 427         @AnnotType(5)
 428         public String  @AnnotType(0) [] @AnnotType(1) [] fooStringArrayArray() {return null;}
 429 
 430         @AnnotTypeInfo(count = 1, relation = Relation.POSTFIX)
 431         @AnnotType(6)
 432         public Set<String> fooSetString() {return null;}
 433         
 434         @AnnotTypeInfo(count = 2, relation = Relation.STRIPPED)
 435         @AnnotType(7)
 436         public Set<@AnnotType(8) Number> fooSetNumber() {return null;}
 437 
 438         @AnnotTypeInfo(count = 1, relation = Relation.POSTFIX)
 439         @AnnotType(9)
 440         public E fooE() {return null;}
 441 
 442         @AnnotTypeInfo(count = 1, relation = Relation.POSTFIX)
 443         @AnnotType(10)
 444         public F fooF() {return null;}
 445 
 446         @AnnotTypeInfo(count = 1, relation = Relation.POSTFIX)
 447         @AnnotType(11)
 448         public <G> G fooG() {return null;}
 449 
 450         @AnnotTypeInfo(count = 1, relation = Relation.POSTFIX)
 451         @AnnotType(12)
 452         public Set<? extends Number> fooNumberSet() {return null;}
 453 
 454         @AnnotTypeInfo(count = 2, relation = Relation.STRIPPED)
 455         @AnnotType(13)
 456         public Set<@AnnotType(14) ? extends Number> fooNumberSet2() {return null;}
 457 
 458         @AnnotTypeInfo(count = 2, relation = Relation.STRIPPED)
 459         @AnnotType(15)
 460         public Set< ? extends @AnnotType(16) Long> fooNumberSet3() {return null;}
 461     }
 462 }