View Javadoc
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 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                 //add a second NestAnnotation to break equality:
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 }