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