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 }