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.juneau.commons.utils;
18  
19  import static org.apache.juneau.commons.utils.AnnotationUtils.hash;
20  import static org.apache.juneau.commons.utils.AnnotationUtils.streamRepeated;
21  import static org.junit.jupiter.api.Assertions.*;
22  
23  import java.lang.annotation.*;
24  import java.util.List;
25  import java.util.stream.Collectors;
26  
27  import org.junit.jupiter.api.*;
28  
29  class AnnotationUtils_Test {
30  
31  	//====================================================================================================
32  	// Constructor (line 31)
33  	//====================================================================================================
34  	@Test
35  	void a00_constructor() {
36  		// Test line 31: class instantiation
37  		// AnnotationUtils has an implicit public no-arg constructor
38  		var instance = new AnnotationUtils();
39  		assertNotNull(instance);
40  	}
41  
42  	//====================================================================================================
43  	// equals(Annotation, Annotation)
44  	//====================================================================================================
45  	@Target({ElementType.TYPE, ElementType.METHOD})
46  	@Retention(RetentionPolicy.RUNTIME)
47  	@interface SimpleAnnotation {
48  		String value() default "";
49  	}
50  
51  	@SimpleAnnotation("test")
52  	static class TestClass1 {}
53  
54  	@SimpleAnnotation("test")
55  	static class TestClass2 {}
56  
57  	@SimpleAnnotation("different")
58  	static class TestClass3 {}
59  
60  	@Target({ElementType.TYPE, ElementType.METHOD})
61  	@Retention(RetentionPolicy.RUNTIME)
62  	@interface DifferentAnnotation {
63  		String value() default "";
64  	}
65  
66  	@DifferentAnnotation("test")
67  	static class TestClass4 {}
68  
69  	@Target({ElementType.TYPE, ElementType.METHOD})
70  	@Retention(RetentionPolicy.RUNTIME)
71  	@interface MultiMemberAnnotation {
72  		String name() default "";
73  		int count() default 0;
74  		boolean active() default false;
75  	}
76  
77  	@MultiMemberAnnotation(name = "test", count = 5, active = true)
78  	static class TestClass5 {}
79  
80  	@MultiMemberAnnotation(name = "test", count = 5, active = true)
81  	static class TestClass6 {}
82  
83  	@MultiMemberAnnotation(name = "test", count = 5, active = false)
84  	static class TestClass7 {}
85  
86  	@MultiMemberAnnotation(name = "different", count = 5, active = true)
87  	static class TestClass8 {}
88  
89  	@MultiMemberAnnotation(name = "test", count = 10, active = true)
90  	static class TestClass9 {}
91  
92  	@Target({ElementType.TYPE, ElementType.METHOD})
93  	@Retention(RetentionPolicy.RUNTIME)
94  	@interface ArrayAnnotation {
95  		String[] values() default {};
96  		int[] numbers() default {};
97  	}
98  
99  	@ArrayAnnotation(values = {"a", "b", "c"}, numbers = {1, 2, 3})
100 	static class TestClass10 {}
101 
102 	@ArrayAnnotation(values = {"a", "b", "c"}, numbers = {1, 2, 3})
103 	static class TestClass11 {}
104 
105 	@ArrayAnnotation(values = {"a", "b"}, numbers = {1, 2, 3})
106 	static class TestClass12 {}
107 
108 	@ArrayAnnotation(values = {"a", "b", "c"}, numbers = {1, 2})
109 	static class TestClass13 {}
110 
111 	@ArrayAnnotation(values = {"a", "b", "d"}, numbers = {1, 2, 3})
112 	static class TestClass14 {}
113 
114 	@ArrayAnnotation(values = {"a", "b", "c"}, numbers = {1, 2, 4})
115 	static class TestClass15 {}
116 
117 	@ArrayAnnotation
118 	static class TestClass16 {}
119 
120 	@Target({ElementType.TYPE, ElementType.METHOD})
121 	@Retention(RetentionPolicy.RUNTIME)
122 	@interface PrimitiveArrayAnnotation {
123 		byte[] bytes() default {};
124 		short[] shorts() default {};
125 		long[] longs() default {};
126 		float[] floats() default {};
127 		double[] doubles() default {};
128 		char[] chars() default {};
129 		boolean[] booleans() default {};
130 	}
131 
132 	@PrimitiveArrayAnnotation(
133 		bytes = {1, 2, 3},
134 		shorts = {10, 20},
135 		longs = {100L, 200L},
136 		floats = {1.0f, 2.0f},
137 		doubles = {1.0, 2.0},
138 		chars = {'a', 'b'},
139 		booleans = {true, false}
140 	)
141 	static class TestClass17 {}
142 
143 	@PrimitiveArrayAnnotation(
144 		bytes = {1, 2, 3},
145 		shorts = {10, 20},
146 		longs = {100L, 200L},
147 		floats = {1.0f, 2.0f},
148 		doubles = {1.0, 2.0},
149 		chars = {'a', 'b'},
150 		booleans = {true, false}
151 	)
152 	static class TestClass18 {}
153 
154 	@PrimitiveArrayAnnotation(
155 		bytes = {1, 2, 4},
156 		shorts = {10, 20},
157 		longs = {100L, 200L},
158 		floats = {1.0f, 2.0f},
159 		doubles = {1.0, 2.0},
160 		chars = {'a', 'b'},
161 		booleans = {true, false}
162 	)
163 	static class TestClass19 {}
164 
165 	@Target({ElementType.TYPE, ElementType.METHOD})
166 	@Retention(RetentionPolicy.RUNTIME)
167 	@interface NestedAnnotation {
168 		SimpleAnnotation nested() default @SimpleAnnotation;
169 	}
170 
171 	@Target({ElementType.TYPE, ElementType.METHOD})
172 	@Retention(RetentionPolicy.RUNTIME)
173 	@interface NestedArrayAnnotation {
174 		SimpleAnnotation[] nested() default {};
175 	}
176 
177 	@NestedAnnotation(nested = @SimpleAnnotation("test"))
178 	static class TestClass20 {}
179 
180 	@NestedAnnotation(nested = @SimpleAnnotation("test"))
181 	static class TestClass21 {}
182 
183 	@NestedAnnotation(nested = @SimpleAnnotation("different"))
184 	static class TestClass22 {}
185 
186 	@NestedArrayAnnotation(nested = {@SimpleAnnotation("a"), @SimpleAnnotation("b")})
187 	static class TestClass23 {}
188 
189 	@NestedArrayAnnotation(nested = {@SimpleAnnotation("a"), @SimpleAnnotation("b")})
190 	static class TestClass24 {}
191 
192 	@NestedArrayAnnotation(nested = {@SimpleAnnotation("a"), @SimpleAnnotation("c")})
193 	static class TestClass25 {}
194 
195 	@Target({ElementType.TYPE, ElementType.METHOD})
196 	@Retention(RetentionPolicy.RUNTIME)
197 	@interface EmptyAnnotation {
198 	}
199 
200 	@EmptyAnnotation
201 	static class TestClass26 {}
202 
203 	@Target({ElementType.TYPE, ElementType.METHOD})
204 	@Retention(RetentionPolicy.RUNTIME)
205 	@interface DefaultValueAnnotation {
206 		String value() default "default";
207 		int count() default 42;
208 	}
209 
210 	@DefaultValueAnnotation
211 	static class TestClass27 {}
212 
213 	@DefaultValueAnnotation(value = "default", count = 42)
214 	static class TestClass28 {}
215 
216 	@Target({ElementType.TYPE, ElementType.METHOD})
217 	@Retention(RetentionPolicy.RUNTIME)
218 	@interface NullableMemberAnnotation {
219 		String value() default "default";
220 	}
221 
222 	@NullableMemberAnnotation
223 	static class TestClass29 {}
224 
225 	@Target({ElementType.TYPE, ElementType.METHOD})
226 	@Retention(RetentionPolicy.RUNTIME)
227 	@interface MemberEqualsTestAnnotation {
228 		String value() default "test";
229 	}
230 
231 	@MemberEqualsTestAnnotation
232 	static class TestClass31 {}
233 
234 	@Test
235 	void a001_equals() {
236 		// Same instance
237 		var a1 = TestClass1.class.getAnnotation(SimpleAnnotation.class);
238 		assertTrue(AnnotationUtils.equals(a1, a1));
239 
240 		// Same value
241 		var a2 = TestClass2.class.getAnnotation(SimpleAnnotation.class);
242 		assertTrue(AnnotationUtils.equals(a1, a2));
243 
244 		// Different value
245 		var a3 = TestClass3.class.getAnnotation(SimpleAnnotation.class);
246 		assertFalse(AnnotationUtils.equals(a1, a3));
247 
248 		// Both null
249 		assertTrue(AnnotationUtils.equals(null, null));
250 
251 		// First null
252 		assertFalse(AnnotationUtils.equals(null, a1));
253 
254 		// Second null
255 		assertFalse(AnnotationUtils.equals(a1, null));
256 
257 		// Different types
258 		var a4 = TestClass4.class.getAnnotation(DifferentAnnotation.class);
259 		assertFalse(AnnotationUtils.equals(a1, a4));
260 
261 		// Multiple members - same
262 		var a5 = TestClass5.class.getAnnotation(MultiMemberAnnotation.class);
263 		var a6 = TestClass6.class.getAnnotation(MultiMemberAnnotation.class);
264 		assertTrue(AnnotationUtils.equals(a5, a6));
265 
266 		// Multiple members - different boolean
267 		var a7 = TestClass7.class.getAnnotation(MultiMemberAnnotation.class);
268 		assertFalse(AnnotationUtils.equals(a5, a7));
269 
270 		// Multiple members - different string
271 		var a8 = TestClass8.class.getAnnotation(MultiMemberAnnotation.class);
272 		assertFalse(AnnotationUtils.equals(a5, a8));
273 
274 		// Multiple members - different int
275 		var a9 = TestClass9.class.getAnnotation(MultiMemberAnnotation.class);
276 		assertFalse(AnnotationUtils.equals(a5, a9));
277 
278 		// Array members - same
279 		var a10 = TestClass10.class.getAnnotation(ArrayAnnotation.class);
280 		var a11 = TestClass11.class.getAnnotation(ArrayAnnotation.class);
281 		assertTrue(AnnotationUtils.equals(a10, a11));
282 
283 		// Array members - different length
284 		var a12 = TestClass12.class.getAnnotation(ArrayAnnotation.class);
285 		assertFalse(AnnotationUtils.equals(a10, a12));
286 
287 		// Array members - different int array length
288 		var a13 = TestClass13.class.getAnnotation(ArrayAnnotation.class);
289 		assertFalse(AnnotationUtils.equals(a10, a13));
290 
291 		// Array members - different string values
292 		var a14 = TestClass14.class.getAnnotation(ArrayAnnotation.class);
293 		assertFalse(AnnotationUtils.equals(a10, a14));
294 
295 		// Array members - different int values
296 		var a15 = TestClass15.class.getAnnotation(ArrayAnnotation.class);
297 		assertFalse(AnnotationUtils.equals(a10, a15));
298 
299 		// Array members - empty arrays
300 		var a16 = TestClass16.class.getAnnotation(ArrayAnnotation.class);
301 		var a16b = TestClass16.class.getAnnotation(ArrayAnnotation.class);
302 		assertTrue(AnnotationUtils.equals(a16, a16b));
303 
304 		// Primitive arrays - same
305 		var a17 = TestClass17.class.getAnnotation(PrimitiveArrayAnnotation.class);
306 		var a18 = TestClass18.class.getAnnotation(PrimitiveArrayAnnotation.class);
307 		assertTrue(AnnotationUtils.equals(a17, a18));
308 
309 		// Primitive arrays - different
310 		var a19 = TestClass19.class.getAnnotation(PrimitiveArrayAnnotation.class);
311 		assertFalse(AnnotationUtils.equals(a17, a19));
312 
313 		// Nested annotation - same
314 		var a20 = TestClass20.class.getAnnotation(NestedAnnotation.class);
315 		var a21 = TestClass21.class.getAnnotation(NestedAnnotation.class);
316 		assertTrue(AnnotationUtils.equals(a20, a21));
317 
318 		// Nested annotation - different
319 		var a22 = TestClass22.class.getAnnotation(NestedAnnotation.class);
320 		assertFalse(AnnotationUtils.equals(a20, a22));
321 
322 		// Nested annotation array - same
323 		var a23 = TestClass23.class.getAnnotation(NestedArrayAnnotation.class);
324 		var a24 = TestClass24.class.getAnnotation(NestedArrayAnnotation.class);
325 		assertTrue(AnnotationUtils.equals(a23, a24));
326 
327 		// Nested annotation array - different
328 		var a25 = TestClass25.class.getAnnotation(NestedArrayAnnotation.class);
329 		assertFalse(AnnotationUtils.equals(a23, a25));
330 
331 		// Default values
332 		var a27 = TestClass27.class.getAnnotation(DefaultValueAnnotation.class);
333 		var a28 = TestClass28.class.getAnnotation(DefaultValueAnnotation.class);
334 		assertTrue(AnnotationUtils.equals(a27, a28));
335 
336 		// Null member values - test line 169
337 		try {
338 			var a29 = TestClass29.class.getAnnotation(NullableMemberAnnotation.class);
339 
340 			// Create annotation proxy with null member
341 			Annotation nullMember1 = (Annotation) java.lang.reflect.Proxy.newProxyInstance(
342 				MemberEqualsTestAnnotation.class.getClassLoader(),
343 				new Class<?>[] { MemberEqualsTestAnnotation.class },
344 				(proxy, method, args) -> {
345 					if (method.getName().equals("value")) {
346 						return null;  // Return null to test line 169
347 					}
348 					if (method.getName().equals("annotationType")) {
349 						return MemberEqualsTestAnnotation.class;
350 					}
351 					if (method.getName().equals("toString")) {
352 						return "@MemberEqualsTestAnnotation(null)";
353 					}
354 					if (method.getName().equals("hashCode")) {
355 						return 0;
356 					}
357 					if (method.getName().equals("equals")) {
358 						return proxy == args[0];
359 					}
360 					return method.invoke(a29, args);
361 				}
362 			);
363 
364 			// Create another annotation proxy with non-null member
365 			Annotation nonNullMember = (Annotation) java.lang.reflect.Proxy.newProxyInstance(
366 				MemberEqualsTestAnnotation.class.getClassLoader(),
367 				new Class<?>[] { MemberEqualsTestAnnotation.class },
368 				(proxy, method, args) -> {
369 					if (method.getName().equals("value")) {
370 						return "test";  // Non-null value
371 					}
372 					if (method.getName().equals("annotationType")) {
373 						return MemberEqualsTestAnnotation.class;
374 					}
375 					if (method.getName().equals("toString")) {
376 						return "@MemberEqualsTestAnnotation(test)";
377 					}
378 					if (method.getName().equals("hashCode")) {
379 						return 0;
380 					}
381 					if (method.getName().equals("equals")) {
382 						return proxy == args[0];
383 					}
384 					return method.invoke(a29, args);
385 				}
386 			);
387 
388 			// Test line 169: memberEquals when o1 == null || o2 == null
389 			assertFalse(AnnotationUtils.equals(nullMember1, nonNullMember));
390 			assertFalse(AnnotationUtils.equals(nonNullMember, nullMember1));
391 		} catch (Exception e) {
392 			// If proxy creation fails, skip this test
393 		}
394 	}
395 
396 	//====================================================================================================
397 	// hash(Annotation)
398 	//====================================================================================================
399 	@Test
400 	void a002_hash() {
401 		// Simple annotation
402 		var a1 = TestClass1.class.getAnnotation(SimpleAnnotation.class);
403 		var a2 = TestClass2.class.getAnnotation(SimpleAnnotation.class);
404 		assertEquals(hash(a1), hash(a2));
405 
406 		// Different values
407 		var a3 = TestClass3.class.getAnnotation(SimpleAnnotation.class);
408 		assertNotEquals(hash(a1), hash(a3));
409 
410 		// Multiple members
411 		var a5 = TestClass5.class.getAnnotation(MultiMemberAnnotation.class);
412 		var a6 = TestClass6.class.getAnnotation(MultiMemberAnnotation.class);
413 		assertEquals(hash(a5), hash(a6));
414 
415 		// Array members
416 		var a10 = TestClass10.class.getAnnotation(ArrayAnnotation.class);
417 		var a11 = TestClass11.class.getAnnotation(ArrayAnnotation.class);
418 		assertEquals(hash(a10), hash(a11));
419 
420 		// Primitive arrays
421 		var a17 = TestClass17.class.getAnnotation(PrimitiveArrayAnnotation.class);
422 		var a18 = TestClass18.class.getAnnotation(PrimitiveArrayAnnotation.class);
423 		assertEquals(hash(a17), hash(a18));
424 
425 		// Nested annotation
426 		var a20 = TestClass20.class.getAnnotation(NestedAnnotation.class);
427 		var a21 = TestClass21.class.getAnnotation(NestedAnnotation.class);
428 		assertEquals(hash(a20), hash(a21));
429 
430 		// Nested annotation array
431 		var a23 = TestClass23.class.getAnnotation(NestedArrayAnnotation.class);
432 		var a24 = TestClass24.class.getAnnotation(NestedArrayAnnotation.class);
433 		assertEquals(hash(a23), hash(a24));
434 
435 		// Consistency with equals
436 		assertTrue(AnnotationUtils.equals(a1, a2));
437 		assertEquals(hash(a1), hash(a2));
438 		assertTrue(AnnotationUtils.equals(a5, a6));
439 		assertEquals(hash(a5), hash(a6));
440 		assertTrue(AnnotationUtils.equals(a10, a11));
441 		assertEquals(hash(a10), hash(a11));
442 
443 		// Empty annotation
444 		var a26 = TestClass26.class.getAnnotation(EmptyAnnotation.class);
445 		int hashCode = hash(a26);
446 		assertTrue(hashCode >= 0 || hashCode < 0); // Just verify it returns a value
447 
448 		// Default values
449 		var a27 = TestClass27.class.getAnnotation(DefaultValueAnnotation.class);
450 		var a28 = TestClass28.class.getAnnotation(DefaultValueAnnotation.class);
451 		assertTrue(AnnotationUtils.equals(a27, a28));
452 		assertEquals(hash(a27), hash(a28));
453 
454 		// Null member values - test line 157
455 		try {
456 			var a29 = TestClass29.class.getAnnotation(NullableMemberAnnotation.class);
457 
458 			// Create a custom annotation proxy that returns null for the value() method
459 			Annotation nullMemberAnnotation = (Annotation) java.lang.reflect.Proxy.newProxyInstance(
460 				NullableMemberAnnotation.class.getClassLoader(),
461 				new Class<?>[] { NullableMemberAnnotation.class },
462 				(proxy, method, args) -> {
463 					if (method.getName().equals("value")) {
464 						return null;  // Return null to test line 157
465 					}
466 					if (method.getName().equals("annotationType")) {
467 						return NullableMemberAnnotation.class;
468 					}
469 					if (method.getName().equals("toString")) {
470 						return "@NullableMemberAnnotation(null)";
471 					}
472 					if (method.getName().equals("hashCode")) {
473 						return 0;
474 					}
475 					if (method.getName().equals("equals")) {
476 						return proxy == args[0];
477 					}
478 					return method.invoke(a29, args);
479 				}
480 			);
481 
482 			// Test that hash() handles null member values correctly (line 157)
483 			int hashNull = hash(nullMemberAnnotation);
484 			// Should not throw, and should return a hash code based on part1 (name.hashCode() * 127)
485 			assertTrue(hashNull != 0 || hashNull == 0); // Just verify it doesn't throw
486 		} catch (Exception e) {
487 			// If proxy creation fails, skip this test
488 		}
489 	}
490 
491 	//====================================================================================================
492 	// streamRepeated(Annotation)
493 	//====================================================================================================
494 	@Test
495 	void a003_streamRepeated() {
496 		// Non-repeatable annotation - should return singleton stream
497 		var a1 = TestClass1.class.getAnnotation(SimpleAnnotation.class);
498 		List<Annotation> result1 = streamRepeated(a1).collect(Collectors.toList());
499 		assertEquals(1, result1.size());
500 		assertSame(a1, result1.get(0));
501 
502 		// Test with empty annotation
503 		var a26 = TestClass26.class.getAnnotation(EmptyAnnotation.class);
504 		List<Annotation> result2 = streamRepeated(a26).collect(Collectors.toList());
505 		assertEquals(1, result2.size());
506 		assertSame(a26, result2.get(0));
507 
508 		// Test with multi-member annotation
509 		var a5 = TestClass5.class.getAnnotation(MultiMemberAnnotation.class);
510 		List<Annotation> result3 = streamRepeated(a5).collect(Collectors.toList());
511 		assertEquals(1, result3.size());
512 		assertSame(a5, result3.get(0));
513 	}
514 }
515