1 /* 2 * Licensed to the Apache Software Foundation (ASF) under one or more 3 * contributor license agreements. See the NOTICE file distributed with 4 * this work for additional information regarding copyright ownership. 5 * The ASF licenses this file to You under the Apache License, Version 2.0 6 * (the "License"); you may not use this file except in compliance with 7 * the License. You may obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 */ 17 package org.apache.commons.lang3; 18 19 import static java.lang.annotation.ElementType.FIELD; 20 import static java.lang.annotation.RetentionPolicy.RUNTIME; 21 import static org.apache.commons.lang3.AnnotationUtilsTest.Stooge.CURLY; 22 import static org.apache.commons.lang3.AnnotationUtilsTest.Stooge.LARRY; 23 import static org.apache.commons.lang3.AnnotationUtilsTest.Stooge.MOE; 24 import static org.apache.commons.lang3.AnnotationUtilsTest.Stooge.SHEMP; 25 import static org.junit.jupiter.api.Assertions.assertEquals; 26 import static org.junit.jupiter.api.Assertions.assertFalse; 27 import static org.junit.jupiter.api.Assertions.assertNotEquals; 28 import static org.junit.jupiter.api.Assertions.assertTimeoutPreemptively; 29 import static org.junit.jupiter.api.Assertions.assertTrue; 30 31 import java.lang.annotation.ElementType; 32 import java.lang.annotation.Retention; 33 import java.lang.annotation.RetentionPolicy; 34 import java.lang.annotation.Target; 35 import java.lang.reflect.Array; 36 import java.lang.reflect.Field; 37 import java.lang.reflect.InvocationHandler; 38 import java.lang.reflect.Proxy; 39 import java.time.Duration; 40 import java.util.Collection; 41 import java.util.Map; 42 43 import org.junit.jupiter.api.BeforeEach; 44 import org.junit.jupiter.api.Test; 45 46 /** 47 */ 48 public class AnnotationUtilsTest extends AbstractLangTest { 49 @TestAnnotation( 50 booleanValue = false, 51 booleanValues = { false }, 52 byteValue = 0, 53 byteValues = { 0 }, 54 charValue = 0, 55 charValues = { 0 }, 56 doubleValue = 0, 57 doubleValues = { 0 }, 58 floatValue = 0, 59 floatValues = { 0 }, 60 intValue = 0, 61 intValues = { 0 }, 62 longValue = 0, 63 longValues = { 0 }, 64 nest = @NestAnnotation( 65 booleanValue = false, 66 booleanValues = { false }, 67 byteValue = 0, 68 byteValues = { 0 }, 69 charValue = 0, 70 charValues = { 0 }, 71 doubleValue = 0, 72 doubleValues = { 0 }, 73 floatValue = 0, 74 floatValues = { 0 }, 75 intValue = 0, 76 intValues = { 0 }, 77 longValue = 0, 78 longValues = { 0 }, 79 shortValue = 0, 80 shortValues = { 0 }, 81 stooge = CURLY, 82 stooges = { MOE, LARRY, SHEMP }, 83 string = "", 84 strings = { "" }, 85 type = Object.class, 86 types = { Object.class } 87 ), 88 nests = { 89 @NestAnnotation( 90 booleanValue = false, 91 booleanValues = { false }, 92 byteValue = 0, 93 byteValues = { 0 }, 94 charValue = 0, 95 charValues = { 0 }, 96 doubleValue = 0, 97 doubleValues = { 0 }, 98 floatValue = 0, 99 floatValues = { 0 }, 100 intValue = 0, 101 intValues = { 0 }, 102 longValue = 0, 103 longValues = { 0 }, 104 shortValue = 0, 105 shortValues = { 0 }, 106 stooge = CURLY, 107 stooges = { MOE, LARRY, SHEMP }, 108 string = "", 109 strings = { "" }, 110 type = Object[].class, 111 types = { Object[].class } 112 ) 113 }, 114 shortValue = 0, 115 shortValues = { 0 }, 116 stooge = SHEMP, 117 stooges = { MOE, LARRY, CURLY }, 118 string = "", 119 strings = { "" }, 120 type = Object.class, 121 types = { Object.class } 122 ) 123 public Object dummy1; 124 125 @TestAnnotation( 126 booleanValue = false, 127 booleanValues = { false }, 128 byteValue = 0, 129 byteValues = { 0 }, 130 charValue = 0, 131 charValues = { 0 }, 132 doubleValue = 0, 133 doubleValues = { 0 }, 134 floatValue = 0, 135 floatValues = { 0 }, 136 intValue = 0, 137 intValues = { 0 }, 138 longValue = 0, 139 longValues = { 0 }, 140 nest = @NestAnnotation( 141 booleanValue = false, 142 booleanValues = { false }, 143 byteValue = 0, 144 byteValues = { 0 }, 145 charValue = 0, 146 charValues = { 0 }, 147 doubleValue = 0, 148 doubleValues = { 0 }, 149 floatValue = 0, 150 floatValues = { 0 }, 151 intValue = 0, 152 intValues = { 0 }, 153 longValue = 0, 154 longValues = { 0 }, 155 shortValue = 0, 156 shortValues = { 0 }, 157 stooge = CURLY, 158 stooges = { MOE, LARRY, SHEMP }, 159 string = "", 160 strings = { "" }, 161 type = Object.class, 162 types = { Object.class } 163 ), 164 nests = { 165 @NestAnnotation( 166 booleanValue = false, 167 booleanValues = { false }, 168 byteValue = 0, 169 byteValues = { 0 }, 170 charValue = 0, 171 charValues = { 0 }, 172 doubleValue = 0, 173 doubleValues = { 0 }, 174 floatValue = 0, 175 floatValues = { 0 }, 176 intValue = 0, 177 intValues = { 0 }, 178 longValue = 0, 179 longValues = { 0 }, 180 shortValue = 0, 181 shortValues = { 0 }, 182 stooge = CURLY, 183 stooges = { MOE, LARRY, SHEMP }, 184 string = "", 185 strings = { "" }, 186 type = Object[].class, 187 types = { Object[].class } 188 ) 189 }, 190 shortValue = 0, 191 shortValues = { 0 }, 192 stooge = SHEMP, 193 stooges = { MOE, LARRY, CURLY }, 194 string = "", 195 strings = { "" }, 196 type = Object.class, 197 types = { Object.class } 198 ) 199 public Object dummy2; 200 201 @TestAnnotation( 202 booleanValue = false, 203 booleanValues = { false }, 204 byteValue = 0, 205 byteValues = { 0 }, 206 charValue = 0, 207 charValues = { 0 }, 208 doubleValue = 0, 209 doubleValues = { 0 }, 210 floatValue = 0, 211 floatValues = { 0 }, 212 intValue = 0, 213 intValues = { 0 }, 214 longValue = 0, 215 longValues = { 0 }, 216 nest = @NestAnnotation( 217 booleanValue = false, 218 booleanValues = { false }, 219 byteValue = 0, 220 byteValues = { 0 }, 221 charValue = 0, 222 charValues = { 0 }, 223 doubleValue = 0, 224 doubleValues = { 0 }, 225 floatValue = 0, 226 floatValues = { 0 }, 227 intValue = 0, 228 intValues = { 0 }, 229 longValue = 0, 230 longValues = { 0 }, 231 shortValue = 0, 232 shortValues = { 0 }, 233 stooge = CURLY, 234 stooges = { MOE, LARRY, SHEMP }, 235 string = "", 236 strings = { "" }, 237 type = Object.class, 238 types = { Object.class } 239 ), 240 nests = { 241 @NestAnnotation( 242 booleanValue = false, 243 booleanValues = { false }, 244 byteValue = 0, 245 byteValues = { 0 }, 246 charValue = 0, 247 charValues = { 0 }, 248 doubleValue = 0, 249 doubleValues = { 0 }, 250 floatValue = 0, 251 floatValues = { 0 }, 252 intValue = 0, 253 intValues = { 0 }, 254 longValue = 0, 255 longValues = { 0 }, 256 shortValue = 0, 257 shortValues = { 0 }, 258 stooge = CURLY, 259 stooges = { MOE, LARRY, SHEMP }, 260 string = "", 261 strings = { "" }, 262 type = Object[].class, 263 types = { Object[].class } 264 ), 265 //add a second NestAnnotation to break equality: 266 @NestAnnotation( 267 booleanValue = false, 268 booleanValues = { false }, 269 byteValue = 0, 270 byteValues = { 0 }, 271 charValue = 0, 272 charValues = { 0 }, 273 doubleValue = 0, 274 doubleValues = { 0 }, 275 floatValue = 0, 276 floatValues = { 0 }, 277 intValue = 0, 278 intValues = { 0 }, 279 longValue = 0, 280 longValues = { 0 }, 281 shortValue = 0, 282 shortValues = { 0 }, 283 stooge = CURLY, 284 stooges = { MOE, LARRY, SHEMP }, 285 string = "", 286 strings = { "" }, 287 type = Object[].class, 288 types = { Object[].class } 289 ) 290 }, 291 shortValue = 0, 292 shortValues = { 0 }, 293 stooge = SHEMP, 294 stooges = { MOE, LARRY, CURLY }, 295 string = "", 296 strings = { "" }, 297 type = Object.class, 298 types = { Object.class } 299 ) 300 public Object dummy3; 301 302 @NestAnnotation( 303 booleanValue = false, 304 booleanValues = { false }, 305 byteValue = 0, 306 byteValues = { 0 }, 307 charValue = 0, 308 charValues = { 0 }, 309 doubleValue = 0, 310 doubleValues = { 0 }, 311 floatValue = 0, 312 floatValues = { 0 }, 313 intValue = 0, 314 intValues = { 0 }, 315 longValue = 0, 316 longValues = { 0 }, 317 shortValue = 0, 318 shortValues = { 0 }, 319 stooge = CURLY, 320 stooges = { MOE, LARRY, SHEMP }, 321 string = "", 322 strings = { "" }, 323 type = Object[].class, 324 types = { Object[].class } 325 ) 326 public Object dummy4; 327 328 @Target(FIELD) 329 @Retention(RUNTIME) 330 public @interface TestAnnotation { string()331 String string(); strings()332 String[] strings(); type()333 Class<?> type(); types()334 Class<?>[] types(); byteValue()335 byte byteValue(); byteValues()336 byte[] byteValues(); shortValue()337 short shortValue(); shortValues()338 short[] shortValues(); intValue()339 int intValue(); intValues()340 int[] intValues(); charValue()341 char charValue(); charValues()342 char[] charValues(); longValue()343 long longValue(); longValues()344 long[] longValues(); floatValue()345 float floatValue(); floatValues()346 float[] floatValues(); doubleValue()347 double doubleValue(); doubleValues()348 double[] doubleValues(); booleanValue()349 boolean booleanValue(); booleanValues()350 boolean[] booleanValues(); stooge()351 Stooge stooge(); stooges()352 Stooge[] stooges(); nest()353 NestAnnotation nest(); nests()354 NestAnnotation[] nests(); 355 } 356 357 @Retention(RUNTIME) 358 public @interface NestAnnotation { string()359 String string(); strings()360 String[] strings(); type()361 Class<?> type(); types()362 Class<?>[] types(); byteValue()363 byte byteValue(); byteValues()364 byte[] byteValues(); shortValue()365 short shortValue(); shortValues()366 short[] shortValues(); intValue()367 int intValue(); intValues()368 int[] intValues(); charValue()369 char charValue(); charValues()370 char[] charValues(); longValue()371 long longValue(); longValues()372 long[] longValues(); floatValue()373 float floatValue(); floatValues()374 float[] floatValues(); doubleValue()375 double doubleValue(); doubleValues()376 double[] doubleValues(); booleanValue()377 boolean booleanValue(); booleanValues()378 boolean[] booleanValues(); stooge()379 Stooge stooge(); stooges()380 Stooge[] stooges(); 381 } 382 383 @Retention(RetentionPolicy.RUNTIME) 384 @Target({ElementType.METHOD}) 385 public @interface TestMethodAnnotation { expected()386 Class<? extends Throwable> expected() default None.class; 387 timeout()388 long timeout() default 0L; 389 390 class None extends Throwable { 391 392 private static final long serialVersionUID = 1L; 393 } 394 } 395 396 public enum Stooge { 397 MOE, LARRY, CURLY, JOE, SHEMP 398 } 399 400 private Field field1; 401 private Field field2; 402 private Field field3; 403 private Field field4; 404 405 @BeforeEach setup()406 public void setup() throws Exception { 407 field1 = getClass().getDeclaredField("dummy1"); 408 field2 = getClass().getDeclaredField("dummy2"); 409 field3 = getClass().getDeclaredField("dummy3"); 410 field4 = getClass().getDeclaredField("dummy4"); 411 } 412 413 @Test testEquivalence()414 public void testEquivalence() { 415 assertTrue(AnnotationUtils.equals(field1.getAnnotation(TestAnnotation.class), field2.getAnnotation(TestAnnotation.class))); 416 assertTrue(AnnotationUtils.equals(field2.getAnnotation(TestAnnotation.class), field1.getAnnotation(TestAnnotation.class))); 417 } 418 419 @Test testSameInstance()420 public void testSameInstance() { 421 assertTrue(AnnotationUtils.equals(field1.getAnnotation(TestAnnotation.class), field1.getAnnotation(TestAnnotation.class))); 422 } 423 424 @Test testNonEquivalentAnnotationsOfSameType()425 public void testNonEquivalentAnnotationsOfSameType() { 426 assertFalse(AnnotationUtils.equals(field1.getAnnotation(TestAnnotation.class), field3.getAnnotation(TestAnnotation.class))); 427 assertFalse(AnnotationUtils.equals(field3.getAnnotation(TestAnnotation.class), field1.getAnnotation(TestAnnotation.class))); 428 } 429 430 @Test testAnnotationsOfDifferingTypes()431 public void testAnnotationsOfDifferingTypes() { 432 assertFalse(AnnotationUtils.equals(field1.getAnnotation(TestAnnotation.class), field4.getAnnotation(NestAnnotation.class))); 433 assertFalse(AnnotationUtils.equals(field4.getAnnotation(NestAnnotation.class), field1.getAnnotation(TestAnnotation.class))); 434 } 435 436 @Test testOneArgNull()437 public void testOneArgNull() { 438 assertFalse(AnnotationUtils.equals(field1.getAnnotation(TestAnnotation.class), null)); 439 assertFalse(AnnotationUtils.equals(null, field1.getAnnotation(TestAnnotation.class))); 440 } 441 442 @Test testBothArgsNull()443 public void testBothArgsNull() { 444 assertTrue(AnnotationUtils.equals(null, null)); 445 } 446 447 @Test testIsValidAnnotationMemberType()448 public void testIsValidAnnotationMemberType() { 449 for (final Class<?> type : new Class[] { byte.class, short.class, int.class, char.class, 450 long.class, float.class, double.class, boolean.class, String.class, Class.class, 451 NestAnnotation.class, TestAnnotation.class, Stooge.class, ElementType.class }) { 452 assertTrue(AnnotationUtils.isValidAnnotationMemberType(type)); 453 assertTrue(AnnotationUtils.isValidAnnotationMemberType(Array.newInstance(type, 0) 454 .getClass())); 455 } 456 for (final Class<?> type : new Class[] { Object.class, Map.class, Collection.class }) { 457 assertFalse(AnnotationUtils.isValidAnnotationMemberType(type)); 458 assertFalse(AnnotationUtils.isValidAnnotationMemberType(Array.newInstance(type, 0) 459 .getClass())); 460 } 461 } 462 463 @Test testGeneratedAnnotationEquivalentToRealAnnotation()464 public void testGeneratedAnnotationEquivalentToRealAnnotation() { 465 assertTimeoutPreemptively(Duration.ofSeconds(666L), () -> { 466 final Test real = getClass().getDeclaredMethod( 467 "testGeneratedAnnotationEquivalentToRealAnnotation").getAnnotation(Test.class); 468 469 final InvocationHandler generatedTestInvocationHandler = (proxy, method, args) -> { 470 if ("equals".equals(method.getName()) && method.getParameterTypes().length == 1) { 471 return Boolean.valueOf(proxy == args[0]); 472 } 473 if ("hashCode".equals(method.getName()) && method.getParameterTypes().length == 0) { 474 return Integer.valueOf(System.identityHashCode(proxy)); 475 } 476 if ("toString".equals(method.getName()) && method.getParameterTypes().length == 0) { 477 return "Test proxy"; 478 } 479 return method.invoke(real, args); 480 }; 481 482 final Test generated = (Test) Proxy.newProxyInstance(Thread.currentThread() 483 .getContextClassLoader(), new Class[]{Test.class}, 484 generatedTestInvocationHandler); 485 assertEquals(real, generated); 486 assertNotEquals(generated, real); 487 assertTrue(AnnotationUtils.equals(generated, real)); 488 assertTrue(AnnotationUtils.equals(real, generated)); 489 490 final Test generated2 = (Test) Proxy.newProxyInstance(Thread.currentThread() 491 .getContextClassLoader(), new Class[]{Test.class}, 492 generatedTestInvocationHandler); 493 assertNotEquals(generated, generated2); 494 assertNotEquals(generated2, generated); 495 assertTrue(AnnotationUtils.equals(generated, generated2)); 496 assertTrue(AnnotationUtils.equals(generated2, generated)); 497 }); 498 } 499 500 @Test testHashCode()501 public void testHashCode() { 502 assertTimeoutPreemptively(Duration.ofSeconds(666L), () -> { 503 final Test test = getClass().getDeclaredMethod("testHashCode").getAnnotation(Test.class); 504 assertEquals(test.hashCode(), AnnotationUtils.hashCode(test)); 505 final TestAnnotation testAnnotation1 = field1.getAnnotation(TestAnnotation.class); 506 assertEquals(testAnnotation1.hashCode(), AnnotationUtils.hashCode(testAnnotation1)); 507 final TestAnnotation testAnnotation3 = field3.getAnnotation(TestAnnotation.class); 508 assertEquals(testAnnotation3.hashCode(), AnnotationUtils.hashCode(testAnnotation3)); 509 }); 510 } 511 512 @Test 513 @TestMethodAnnotation(timeout = 666000) testToString()514 public void testToString() { 515 assertTimeoutPreemptively(Duration.ofSeconds(666L), () -> { 516 final TestMethodAnnotation testAnnotation = 517 getClass().getDeclaredMethod("testToString").getAnnotation(TestMethodAnnotation.class); 518 519 final String annotationString = AnnotationUtils.toString(testAnnotation); 520 assertTrue(annotationString.startsWith("@org.apache.commons.lang3.AnnotationUtilsTest$TestMethodAnnotation(")); 521 assertTrue(annotationString.endsWith(")")); 522 assertTrue(annotationString.contains("expected=class org.apache.commons.lang3.AnnotationUtilsTest$TestMethodAnnotation$None")); 523 assertTrue(annotationString.contains("timeout=666000")); 524 assertTrue(annotationString.contains(", ")); 525 }); 526 } 527 528 } 529