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 }