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.reflect;
18  
19  import static java.lang.annotation.ElementType.*;
20  import static java.lang.annotation.RetentionPolicy.*;
21  import static org.apache.juneau.commons.utils.CollectionUtils.*;
22  import static org.apache.juneau.commons.utils.Utils.*;
23  import static org.junit.jupiter.api.Assertions.*;
24  
25  import java.lang.annotation.*;
26  import java.lang.reflect.*;
27  import java.util.*;
28  import java.util.function.*;
29  import java.util.stream.*;
30  
31  import org.apache.juneau.*;
32  import org.apache.juneau.annotation.Name;
33  import org.junit.jupiter.api.*;
34  
35  class ParameterInfo_Test extends TestBase {
36  
37  	private static String originalDisableParamNameDetection;
38  
39  	@BeforeAll
40  	public static void beforeAll() {
41  		// Save original system property value
42  		originalDisableParamNameDetection = System.getProperty("juneau.disableParamNameDetection");
43  
44  		// Set to true to ensure consistent behavior regardless of JVM compiler settings
45  		System.setProperty("juneau.disableParamNameDetection", "true");
46  		ParameterInfo.reset();
47  	}
48  
49  	@AfterAll
50  	public static void afterAll() {
51  		// Restore original system property value
52  		if (originalDisableParamNameDetection == null)
53  			System.clearProperty("juneau.disableParamNameDetection");
54  		else
55  			System.setProperty("juneau.disableParamNameDetection", originalDisableParamNameDetection);
56  		ParameterInfo.reset();
57  	}
58  
59  	@Documented
60  	@Target(METHOD)
61  	@Retention(RUNTIME)
62  	@Inherited
63  	public static @interface A {
64  		String value();
65  	}
66  
67  	@Documented
68  	@Target(METHOD)
69  	@Retention(RUNTIME)
70  	@Inherited
71  	public static @interface AX {
72  		String value();
73  	}
74  
75  	@Target({PARAMETER,TYPE})
76  	@Retention(RUNTIME)
77  	public static @interface CA {
78  		public String value();
79  	}
80  
81  	@Target({PARAMETER,TYPE})
82  	@Retention(RUNTIME)
83  	@Inherited
84  	public static @interface DA {
85  		public String value();
86  	}
87  
88  	// Test annotations for getResolvedQualifier() - line 643
89  	@Target(PARAMETER)
90  	@Retention(RUNTIME)
91  	public static @interface Named {
92  		String value();
93  	}
94  
95  	@Target(PARAMETER)
96  	@Retention(RUNTIME)
97  	public static @interface Qualifier {
98  		String value();
99  	}
100 
101 
102 	private static void check(String expected, Object o) {
103 		assertEquals(expected, TO_STRING.apply(o));
104 	}
105 
106 	private static final Function<Object,String> TO_STRING = new Function<>() {
107 		@Override
108 		public String apply(Object t) {
109 			if (t == null)
110 				return null;
111 			if (t instanceof List)
112 				return ((List<?>)t).stream().map(this).collect(Collectors.joining(","));
113 			if (isArray(t))
114 				return StreamSupport.stream(toList(t, Object.class).spliterator(), false).map(this).collect(Collectors.joining(","));
115 			if (t instanceof MethodInfo)
116 				return ((MethodInfo)t).getDeclaringClass().getNameSimple() + '.' + ((MethodInfo)t).getShortName();
117 			if (t instanceof CA)
118 				return "@CA(" + ((CA)t).value() + ")";
119 			if (t instanceof DA)
120 				return "@DA(" + ((DA)t).value() + ")";
121 			if (t instanceof ClassInfo)
122 				return ((ClassInfo)t).getNameSimple();
123 			return t.toString();
124 		}
125 	};
126 
127 	//-----------------------------------------------------------------------------------------------------------------
128 	// Test classes
129 	//-----------------------------------------------------------------------------------------------------------------
130 
131 	static class B {
132 		public B(int a, String b) {}
133 		public void a1(int a, String b) {}  // NOSONAR
134 		void a2(int a, String b) {}  // NOSONAR
135 		public void varargsMethod(String... args) {}  // NOSONAR - for testing VARARGS flag
136 	}
137 	static ClassInfo b = ClassInfo.of(B.class);
138 	static ParameterInfo
139 		b_b_a = b.getPublicConstructor(x -> x.hasParameterTypes(int.class, String.class)).get().getParameter(0),  // NOSONAR
140 		b_b_b = b.getPublicConstructor(x -> x.hasParameterTypes(int.class, String.class)).get().getParameter(1),  // NOSONAR
141 		b_a1_a = b.getMethod(x -> x.hasName("a1")).get().getParameter(0),  // NOSONAR
142 		b_a1_b = b.getMethod(x -> x.hasName("a1")).get().getParameter(1),  // NOSONAR
143 		b_a2_a = b.getMethod(x -> x.hasName("a2")).get().getParameter(0),  // NOSONAR
144 		b_a2_b = b.getMethod(x -> x.hasName("a2")).get().getParameter(1),  // NOSONAR
145 		b_varargs = b.getMethod(x -> x.hasName("varargsMethod")).get().getParameter(0);  // NOSONAR - varargs parameter
146 
147 	@CA("1") public static class C1 extends C2 {}
148 	@CA("2") public static class C2 implements C3, C4 {}
149 	@CA("3") public interface C3 {}
150 	@CA("4") public interface C4 {}
151 
152 	public interface CB {
153 		void a1(@CA("5") C1 x);
154 		void a2(@CA("5") C1 x);
155 	}
156 	public static class CC implements CB {
157 		public CC(@CA("9") C1 x) {}
158 		@Override
159 		public void a1(C1 x) {}  // NOSONAR
160 		@Override
161 		public void a2(@CA("6") C1 x) {}  // NOSONAR
162 	}
163 	static ClassInfo
164 		cb = ClassInfo.of(CB.class),
165 		cc = ClassInfo.of(CC.class);
166 	static ParameterInfo
167 		cc_cc = cc.getPublicConstructor(x -> x.hasParameterTypes(C1.class)).get().getParameter(0),  // NOSONAR
168 		cb_a1 = cb.getMethod(x -> x.hasName("a1")).get().getParameter(0),  // NOSONAR
169 		cb_a2 = cb.getMethod(x -> x.hasName("a2")).get().getParameter(0),  // NOSONAR
170 		cc_a1 = cc.getMethod(x -> x.hasName("a1")).get().getParameter(0),  // NOSONAR
171 		cc_a2 = cc.getMethod(x -> x.hasName("a2")).get().getParameter(0);  // NOSONAR
172 
173 	@DA("1") public static class D1 extends D2 {}
174 	@DA("2") public static class D2 implements D3, D4 {}
175 	@DA("3") public interface D3 {}
176 	@DA("4") public interface D4 {}
177 
178 	public interface DB {
179 		void a1(@DA("0") D1 x);
180 	}
181 	public static class DC implements DB {
182 		@Override
183 		public void a1(@DA("5") D1 x) {}  // NOSONAR
184 	}
185 
186 	static ClassInfo
187 		db = ClassInfo.of(DB.class),
188 		dc = ClassInfo.of(DC.class);
189 	static ParameterInfo
190 		db_a1 = db.getMethod(x -> x.hasName("a1")).get().getParameter(0),  // NOSONAR
191 		dc_a1 = dc.getMethod(x -> x.hasName("a1")).get().getParameter(0);  // NOSONAR
192 
193 	static class E {
194 		public void a1(int a, @org.apache.juneau.annotation.Name("b") int b) {}  // NOSONAR - use full qualified name to avoid conflict
195 		// Parameter with both @Name and another annotation to test line 622 both branches
196 		public void test(@CA("test") @org.apache.juneau.annotation.Name("paramName") String param) {}  // NOSONAR
197 	}
198 
199 	// Test classes for getResolvedQualifier() - line 643
200 	static class G {
201 		// Test line 643: hasSimpleName("Named") = true, hasSimpleName("Qualifier") = false
202 		public void test1(@Named("bean1") String param) {}  // NOSONAR
203 		// Test line 643: hasSimpleName("Named") = false, hasSimpleName("Qualifier") = true
204 		public void test2(@Qualifier("bean2") String param) {}  // NOSONAR
205 		// Test line 643: hasSimpleName("Named") = true, hasSimpleName("Qualifier") = true (both true, @Named first)
206 		public void test3(@Named("bean3") @Qualifier("bean3") String param) {}  // NOSONAR
207 		// Test line 643: hasSimpleName("Named") = true, hasSimpleName("Qualifier") = true (both true, @Qualifier first)
208 		// This ensures both sides of the OR are evaluated when @Qualifier comes first
209 		public void test3b(@Qualifier("bean3b") @Named("bean3b") String param) {}  // NOSONAR
210 		// Test line 643: hasSimpleName("Named") = false, hasSimpleName("Qualifier") = false (both false)
211 		public void test4(@CA("test") String param) {}  // NOSONAR
212 	}
213 	static ClassInfo g = ClassInfo.of(G.class);
214 	static ParameterInfo
215 		g_test1 = g.getMethod(x -> x.hasName("test1")).get().getParameter(0),  // NOSONAR - has @Named
216 		g_test2 = g.getMethod(x -> x.hasName("test2")).get().getParameter(0),  // NOSONAR - has @Qualifier
217 		g_test3 = g.getMethod(x -> x.hasName("test3")).get().getParameter(0),  // NOSONAR - has both @Named and @Qualifier (@Named first)
218 		g_test3b = g.getMethod(x -> x.hasName("test3b")).get().getParameter(0),  // NOSONAR - has both @Qualifier and @Named (@Qualifier first)
219 		g_test4 = g.getMethod(x -> x.hasName("test4")).get().getParameter(0);  // NOSONAR - has neither (has @CA)
220 
221 	static ClassInfo e = ClassInfo.of(E.class);
222 	static ParameterInfo
223 		e_a1_a = e.getMethod(x -> x.hasName("a1")).get().getParameter(0),  // NOSONAR
224 		e_a1_b = e.getMethod(x -> x.hasName("a1")).get().getParameter(1),  // NOSONAR
225 		e_test = e.getMethod(x -> x.hasName("test")).get().getParameter(0);  // NOSONAR - has both @CA and @Name annotations
226 
227 	// Method hierarchy tests
228 	public interface PM1 {
229 		void foo(String s);
230 	}
231 	public static class PM2 implements PM1 {
232 		@Override public void foo(String s) {}  // NOSONAR
233 	}
234 	public static class PM3 extends PM2 {
235 		@Override public void foo(String s) {}  // NOSONAR
236 	}
237 
238 	// Method with multiple interfaces
239 	public interface PM4 {
240 		void bar(int x, String s);
241 	}
242 	public interface PM5 {
243 		void bar(int x, String s);
244 	}
245 	public static class PM6 implements PM4, PM5 {
246 		@Override public void bar(int x, String s) {}  // NOSONAR
247 	}
248 
249 	// Constructor hierarchy tests
250 	public static class EqualsTestClass {
251 		public void method(String param1, int param2) {}
252 	}
253 
254 	public static class PC1 {
255 		public PC1(String foo) {}  // NOSONAR
256 	}
257 	public static class PC2 extends PC1 {
258 		public PC2(String foo) { super(foo); }  // NOSONAR
259 	}
260 	public static class PC3 extends PC2 {
261 		public PC3(String foo) { super(foo); }  // NOSONAR
262 	}
263 
264 	public static class PC4 {
265 		public PC4(String foo, int bar) {}  // NOSONAR
266 	}
267 	public static class PC5 extends PC4 {
268 		public PC5(String foo) { super(foo, 0); }  // NOSONAR
269 	}
270 
271 	public static class PC6 {
272 		public PC6(String foo) {}  // NOSONAR
273 		public PC6(String foo, int bar) {}  // NOSONAR
274 	}
275 	public static class PC7 extends PC6 {
276 		public PC7(String foo) { super(foo); }  // NOSONAR
277 	}
278 
279 	public interface PM7 {
280 		void baz(String differentName);
281 	}
282 	public static class PM8 implements PM7 {
283 		@Override public void baz(String differentName) {}  // NOSONAR
284 		public void foo(String s) {}  // NOSONAR
285 	}
286 
287 	public interface PM9 {
288 		void qux(int x);
289 	}
290 	public static class PM10 implements PM9 {
291 		@Override public void qux(int x) {}  // NOSONAR
292 	}
293 	public static class PM11 extends PM10 {
294 		public void qux(String x) {}  // NOSONAR - different overload
295 	}
296 
297 	public static class PC8 {
298 		public PC8(int foo) {}  // NOSONAR
299 	}
300 	public static class PC9 extends PC8 {
301 		public PC9(String foo) { super(0); }  // NOSONAR
302 	}
303 
304 	public static class PC10 {
305 		public PC10(String bar) {}  // NOSONAR
306 	}
307 	public static class PC11 extends PC10 {
308 		public PC11(String foo) { super(foo); }  // NOSONAR
309 	}
310 
311 	public static class PC12 {
312 		public PC12(@Name("foo") String x) {}  // NOSONAR
313 		public PC12(@Name("bar") String x, int y) {}  // NOSONAR
314 	}
315 	public static class PC13 extends PC12 {
316 		public PC13(@Name("foo") String x) { super(x); }  // NOSONAR
317 	}
318 
319 	public static class PC14 {
320 		public PC14(@Name("bar") String x) {}  // NOSONAR
321 	}
322 	public static class PC15 extends PC14 {
323 		public PC15(@Name("foo") String x) { super(x); }  // NOSONAR
324 	}
325 
326 	public interface PM12 {
327 		void test(@Name("param1") String x);
328 	}
329 	public static class PM13 implements PM12 {
330 		@Override public void test(@Name("param1") String x) {}  // NOSONAR
331 	}
332 
333 	//====================================================================================================
334 	// canAccept(Object)
335 	//====================================================================================================
336 	@Test
337 	void a001_canAccept() {
338 		assertTrue(b_a1_a.canAccept(42));
339 		assertFalse(b_a1_a.canAccept("string"));
340 		assertTrue(b_a1_b.canAccept("string"));
341 		assertFalse(b_a1_b.canAccept(42));
342 	}
343 
344 	//====================================================================================================
345 	// getAnnotatedType()
346 	//====================================================================================================
347 	@Test
348 	void a002_getAnnotatedType() {
349 		var annotatedType = b_a1_a.getAnnotatedType();
350 		assertNotNull(annotatedType);
351 		assertEquals(int.class, annotatedType.getType());
352 	}
353 
354 	//====================================================================================================
355 	// getAnnotatableType()
356 	//====================================================================================================
357 	@Test
358 	void a003_getAnnotatableType() {
359 		assertEquals(AnnotatableType.PARAMETER_TYPE, b_a1_a.getAnnotatableType());
360 	}
361 
362 	//====================================================================================================
363 	// getAnnotations()
364 	//====================================================================================================
365 	@Test
366 	void a004_getAnnotations() {
367 		var annotations = cb_a1.getAnnotations();
368 		assertNotNull(annotations);
369 		assertTrue(annotations.size() > 0);
370 	}
371 
372 	//====================================================================================================
373 	// getAnnotations(Class<A>)
374 	//====================================================================================================
375 	@Test
376 	void a005_getAnnotations_typed() {
377 		check("@CA(5)", declaredAnnotations(cb_a1, CA.class));
378 		check("@CA(5)", declaredAnnotations(cb_a2, CA.class));
379 		check("", declaredAnnotations(cc_a1, CA.class));
380 		check("@CA(6)", declaredAnnotations(cc_a2, CA.class));
381 		check("@CA(9)", declaredAnnotations(cc_cc, CA.class));
382 	}
383 
384 	private static <T extends Annotation> List<T> declaredAnnotations(ParameterInfo pi, Class<T> type) {
385 		return pi.getAnnotations(type).map(x -> x.inner()).toList();
386 	}
387 
388 	//====================================================================================================
389 	// getConstructor()
390 	//====================================================================================================
391 	@Test
392 	void a006_getConstructor() {
393 		check("B(int,String)", b_b_a.getConstructor());
394 		check("B(int,String)", b_b_b.getConstructor());
395 		check(null, b_a1_a.getConstructor());
396 		check(null, b_a1_b.getConstructor());
397 	}
398 
399 	//====================================================================================================
400 	// getDeclaringExecutable()
401 	//====================================================================================================
402 	@Test
403 	void a007_getDeclaringExecutable() {
404 		var exec1 = b_b_a.getDeclaringExecutable();
405 		assertTrue(exec1.isConstructor());
406 		
407 		var exec2 = b_a1_a.getDeclaringExecutable();
408 		assertFalse(exec2.isConstructor());
409 		assertTrue(exec2 instanceof MethodInfo);
410 	}
411 
412 	//====================================================================================================
413 	// getIndex()
414 	//====================================================================================================
415 	@Test
416 	void a008_getIndex() {
417 		assertEquals(0, b_b_a.getIndex());
418 		assertEquals(1, b_b_b.getIndex());
419 		assertEquals(0, b_a1_a.getIndex());
420 		assertEquals(1, b_a1_b.getIndex());
421 		assertEquals(0, b_a2_a.getIndex());
422 		assertEquals(1, b_a2_b.getIndex());
423 	}
424 
425 	//====================================================================================================
426 	// getLabel()
427 	//====================================================================================================
428 	@Test
429 	void a009_getLabel() {
430 		var label = b_a1_a.getLabel();
431 		assertNotNull(label);
432 		assertTrue(label.contains("B"));
433 		assertTrue(label.contains("a1"));
434 		assertTrue(label.contains("[0]"));
435 	}
436 
437 	//====================================================================================================
438 	// getMatchingParameters()
439 	//====================================================================================================
440 	@Test
441 	void a010_getMatchingParameters() throws Exception {
442 		// Method simple hierarchy
443 		var mi = MethodInfo.of(PM3.class.getMethod("foo", String.class));
444 		var pi = mi.getParameter(0);
445 		var matching = pi.getMatchingParameters();
446 		assertEquals(3, matching.size());
447 		check("PM3", matching.get(0).getDeclaringExecutable().getDeclaringClass());
448 		check("PM2", matching.get(1).getDeclaringExecutable().getDeclaringClass());
449 		check("PM1", matching.get(2).getDeclaringExecutable().getDeclaringClass());
450 		
451 		// Method multiple interfaces
452 		var mi2 = MethodInfo.of(PM6.class.getMethod("bar", int.class, String.class));
453 		var pi0 = mi2.getParameter(0);
454 		var matching0 = pi0.getMatchingParameters();
455 		assertEquals(3, matching0.size());
456 		check("PM6", matching0.get(0).getDeclaringExecutable().getDeclaringClass());
457 		check("PM4", matching0.get(1).getDeclaringExecutable().getDeclaringClass());
458 		check("PM5", matching0.get(2).getDeclaringExecutable().getDeclaringClass());
459 		
460 		// Constructor simple hierarchy
461 		var ci = ConstructorInfo.of(PC3.class.getConstructor(String.class));
462 		var pi2 = ci.getParameter(0);
463 		var matching2 = pi2.getMatchingParameters();
464 		assertEquals(3, matching2.size());
465 		check("PC3", matching2.get(0).getDeclaringExecutable().getDeclaringClass());
466 		check("PC2", matching2.get(1).getDeclaringExecutable().getDeclaringClass());
467 		check("PC1", matching2.get(2).getDeclaringExecutable().getDeclaringClass());
468 		
469 		// Constructor different parameter counts
470 		var ci2 = ConstructorInfo.of(PC5.class.getConstructor(String.class));
471 		var pi3 = ci2.getParameter(0);
472 		var matching3 = pi3.getMatchingParameters();
473 		assertEquals(2, matching3.size());
474 		check("PC5", matching3.get(0).getDeclaringExecutable().getDeclaringClass());
475 		check("PC4", matching3.get(1).getDeclaringExecutable().getDeclaringClass());
476 		
477 		// Constructor multiple parent constructors
478 		var ci3 = ConstructorInfo.of(PC7.class.getConstructor(String.class));
479 		var pi4 = ci3.getParameter(0);
480 		var matching4 = pi4.getMatchingParameters();
481 		assertEquals(3, matching4.size());
482 		check("PC7", matching4.get(0).getDeclaringExecutable().getDeclaringClass());
483 		check("PC6", matching4.get(1).getDeclaringExecutable().getDeclaringClass());
484 		check("PC6", matching4.get(2).getDeclaringExecutable().getDeclaringClass());
485 		
486 		// Method different parameter name
487 		var mi3 = MethodInfo.of(PM8.class.getMethod("foo", String.class));
488 		var pi5 = mi3.getParameter(0);
489 		var matching5 = pi5.getMatchingParameters();
490 		assertEquals(1, matching5.size());
491 		check("PM8", matching5.get(0).getDeclaringExecutable().getDeclaringClass());
492 		
493 		// Method different parameter type
494 		var mi4 = MethodInfo.of(PM11.class.getMethod("qux", String.class));
495 		var pi6 = mi4.getParameter(0);
496 		var matching6 = pi6.getMatchingParameters();
497 		assertEquals(1, matching6.size());
498 		check("PM11", matching6.get(0).getDeclaringExecutable().getDeclaringClass());
499 		
500 		// Constructor different parameter type
501 		var ci4 = ConstructorInfo.of(PC9.class.getConstructor(String.class));
502 		var pi7 = ci4.getParameter(0);
503 		var matching7 = pi7.getMatchingParameters();
504 		assertEquals(1, matching7.size());
505 		check("PC9", matching7.get(0).getDeclaringExecutable().getDeclaringClass());
506 		
507 		// Constructor different parameter name
508 		var ci5 = ConstructorInfo.of(PC11.class.getConstructor(String.class));
509 		var pi8 = ci5.getParameter(0);
510 		var matching8 = pi8.getMatchingParameters();
511 		assertEquals(2, matching8.size());
512 		check("PC11", matching8.get(0).getDeclaringExecutable().getDeclaringClass());
513 		check("PC10", matching8.get(1).getDeclaringExecutable().getDeclaringClass());
514 		
515 		// Constructor with @Name annotation matching
516 		var ci6 = ConstructorInfo.of(PC13.class.getConstructor(String.class));
517 		var pi9 = ci6.getParameter(0);
518 		var matching9 = pi9.getMatchingParameters();
519 		assertEquals(3, matching9.size());
520 		check("PC13", matching9.get(0).getDeclaringExecutable().getDeclaringClass());
521 		check("PC12", matching9.get(1).getDeclaringExecutable().getDeclaringClass());
522 		check("PC12", matching9.get(2).getDeclaringExecutable().getDeclaringClass());
523 		assertEquals("foo", matching9.get(1).getName());
524 		assertEquals("bar", matching9.get(2).getName());
525 		
526 		// Constructor with @Name annotation different names
527 		var ci7 = ConstructorInfo.of(PC15.class.getConstructor(String.class));
528 		var pi10 = ci7.getParameter(0);
529 		var matching10 = pi10.getMatchingParameters();
530 		assertEquals(2, matching10.size());
531 		check("PC15", matching10.get(0).getDeclaringExecutable().getDeclaringClass());
532 		check("PC14", matching10.get(1).getDeclaringExecutable().getDeclaringClass());
533 		assertEquals("foo", matching10.get(0).getName());
534 		assertEquals("bar", matching10.get(1).getName());
535 		
536 		// Method with @Name annotation matching
537 		var mi5 = MethodInfo.of(PM13.class.getMethod("test", String.class));
538 		var pi11 = mi5.getParameter(0);
539 		var matching11 = pi11.getMatchingParameters();
540 		assertEquals(2, matching11.size());
541 		check("PM13", matching11.get(0).getDeclaringExecutable().getDeclaringClass());
542 		check("PM12", matching11.get(1).getDeclaringExecutable().getDeclaringClass());
543 		assertEquals("param1", matching11.get(0).getName());
544 		assertEquals("param1", matching11.get(1).getName());
545 	}
546 
547 	//====================================================================================================
548 	// getMethod()
549 	//====================================================================================================
550 	@Test
551 	void a011_getMethod() {
552 		check("B.a1(int,String)", b_a1_a.getMethod());
553 		check("B.a1(int,String)", b_a1_b.getMethod());
554 		check("B.a2(int,String)", b_a2_a.getMethod());
555 		check("B.a2(int,String)", b_a2_b.getMethod());
556 		check(null, b_b_a.getMethod());
557 		check(null, b_b_b.getMethod());
558 	}
559 
560 	//====================================================================================================
561 	// getModifiers()
562 	//====================================================================================================
563 	@Test
564 	void a012_getModifiers() {
565 		var modifiers = b_a1_a.getModifiers();
566 		assertNotNull(Integer.valueOf(modifiers));
567 	}
568 
569 	//====================================================================================================
570 	// getName()
571 	//====================================================================================================
572 	@Test
573 	void a013_getName() {
574 		// With DISABLE_PARAM_NAME_DETECTION=true:
575 		// - Parameters with @Name use the annotation value
576 		// - Parameters without @Name fall back to parameter.getName() which may return
577 		//   bytecode names (if compiled with -parameters) or synthetic names (arg0, arg1, etc.)
578 		assertNotNull(e_a1_a.getName());  // No @Name, falls back to parameter.getName()
579 		assertEquals("b", e_a1_b.getName());  // Has @Name("b")
580 	}
581 
582 	//====================================================================================================
583 	// getParameterizedType()
584 	//====================================================================================================
585 	@Test
586 	void a014_getParameterizedType() {
587 		var paramType = b_a1_a.getParameterizedType();
588 		assertNotNull(paramType);
589 		assertEquals(int.class, paramType);
590 	}
591 
592 	//====================================================================================================
593 	// getParameterType()
594 	//====================================================================================================
595 	@Test
596 	void a015_getParameterType() {
597 		check("int", b_b_a.getParameterType());
598 		check("String", b_b_b.getParameterType());
599 		check("int", b_a1_a.getParameterType());
600 		check("String", b_a1_b.getParameterType());
601 		check("int", b_a2_a.getParameterType());
602 		check("String", b_a2_b.getParameterType());
603 	}
604 
605 	//====================================================================================================
606 	// getResolvedName()
607 	//====================================================================================================
608 	@Test
609 	void a016_getResolvedName() {
610 		// With DISABLE_PARAM_NAME_DETECTION=true, only parameters with @Name annotation have resolved names
611 		// Test line 622: hasSimpleName("Name") returns false (no @Name annotation)
612 		assertNull(e_a1_a.getResolvedName());  // No @Name annotation
613 		
614 		// Test line 622: hasSimpleName("Name") returns true
615 		// Test line 624: value != null branch
616 		assertEquals("b", e_a1_b.getResolvedName());   // Has @Name("b") with non-null value
617 		
618 		// Test line 622: both branches in a single call
619 		// e_test has both @CA("test") and @Name("paramName") annotations
620 		// When iterating through annotations, we'll hit:
621 		// - @CA annotation: hasSimpleName("Name") returns false (line 622 false branch)
622 		// - @Name annotation: hasSimpleName("Name") returns true (line 622 true branch)
623 		assertEquals("paramName", e_test.getResolvedName());  // Should return @Name value
624 		
625 		// Test line 632: bytecode parameter name fallback
626 		// Temporarily disable the flag to test the bytecode name fallback
627 		String originalValue = System.getProperty("juneau.disableParamNameDetection");
628 		try {
629 			System.setProperty("juneau.disableParamNameDetection", "false");
630 			ParameterInfo.reset();
631 			
632 			// Get a fresh ParameterInfo instance after resetting (don't use cached static field)
633 			// Note: Line 632 is only executed if BOTH conditions are true:
634 			// 1. DISABLE_PARAM_NAME_DETECTION.get() returns false (flag is disabled)
635 			// 2. inner.isNamePresent() returns true (bytecode names are available)
636 			var freshClassInfo = ClassInfo.of(E.class);
637 			var paramWithoutName = freshClassInfo.getMethod(x -> x.hasName("a1")).get().getParameter(0);
638 			
639 			// Check if bytecode names are available
640 			// Line 632 is only executed when both conditions on line 631 are true:
641 			// 1. !DISABLE_PARAM_NAME_DETECTION.get() is true (flag is false)
642 			// 2. inner.isNamePresent() is true (bytecode names available)
643 			if (paramWithoutName.inner().isNamePresent()) {
644 			// If bytecode names are available, try to get resolved name
645 			// This will execute line 632 if the flag is actually false
646 			paramWithoutName.getResolvedName(); // Exercise the code path
647 			// Note: If the result is null, it means the condition on line 631 was false
648 			// (either flag is still true, or there's a caching issue)
649 			// In that case, line 632 won't be covered, which is acceptable
650 			// We don't assert here because the flag might not have reset properly
651 			}
652 			// If bytecode names are not available, line 632 won't be executed
653 			// This is expected if the code wasn't compiled with -parameters flag
654 			// The test still covers the condition check on line 631 (false branch when isNamePresent() is false)
655 		} finally {
656 			// Restore original value
657 			if (originalValue == null)
658 				System.clearProperty("juneau.disableParamNameDetection");
659 			else
660 				System.setProperty("juneau.disableParamNameDetection", originalValue);
661 			ParameterInfo.reset();
662 		}
663 		
664 		// Note: Line 624 (value == null branch) is hard to test because it would require an annotation
665 		// with simple name "Name" that doesn't have a String value() method. In practice, all @Name
666 		// annotations have a String value() method, so this branch is unlikely to be reached.
667 	}
668 
669 	//====================================================================================================
670 	// getResolvedQualifier()
671 	//====================================================================================================
672 	@Test
673 	void a017_getResolvedQualifier() {
674 		// Test line 643: hasSimpleName("Named") = false, hasSimpleName("Qualifier") = false (both false)
675 		// This covers the false branch of the OR condition
676 		assertNull(b_a1_a.getResolvedQualifier());  // No @Named or @Qualifier annotation
677 		assertNull(g_test4.getResolvedQualifier());  // Has @CA but not @Named or @Qualifier
678 		
679 		// Test line 643: hasSimpleName("Named") = true, hasSimpleName("Qualifier") = false
680 		// This covers branch 1: true || false = true
681 		assertEquals("bean1", g_test1.getResolvedQualifier());
682 		
683 		// Test line 643: hasSimpleName("Named") = false, hasSimpleName("Qualifier") = true
684 		// This covers branch 2: false || true = true
685 		assertEquals("bean2", g_test2.getResolvedQualifier());
686 		
687 		// Test line 643: hasSimpleName("Named") = true, hasSimpleName("Qualifier") = true
688 		// This covers branch 3: true || true = true
689 		// When @Named comes first, the OR short-circuits on the first annotation
690 		assertEquals("bean3", g_test3.getResolvedQualifier());
691 		// When @Qualifier comes first, we need to test that hasSimpleName("Named") is still evaluated
692 		// on the second annotation to cover the case where first is false, second is true
693 		assertEquals("bean3b", g_test3b.getResolvedQualifier());
694 	}
695 
696 	//====================================================================================================
697 	// hasName()
698 	//====================================================================================================
699 	@Test
700 	void a018_hasName() {
701 		// With DISABLE_PARAM_NAME_DETECTION=true, only parameters with @Name annotation have names
702 		assertFalse(e_a1_a.hasName());  // No @Name annotation
703 		assertTrue(e_a1_b.hasName());   // Has @Name("b")
704 	}
705 
706 	//====================================================================================================
707 	// inner()
708 	//====================================================================================================
709 	@Test
710 	void a019_inner() {
711 		var param = b_a1_a.inner();
712 		assertNotNull(param);
713 		assertEquals(int.class, param.getType());
714 	}
715 
716 	//====================================================================================================
717 	// is(ElementFlag)
718 	//====================================================================================================
719 	@Test
720 	void a020_is() {
721 		// Test line 465: SYNTHETIC
722 		assertFalse(b_a1_a.is(ElementFlag.SYNTHETIC));
723 		
724 		// Test line 466: NOT_SYNTHETIC
725 		assertTrue(b_a1_a.is(ElementFlag.NOT_SYNTHETIC));
726 		
727 		// Test line 467: VARARGS - regular parameters are not varargs
728 		assertFalse(b_a1_a.is(ElementFlag.VARARGS));
729 		// Test line 467: VARARGS - true branch (varargs parameter)
730 		assertTrue(b_varargs.is(ElementFlag.VARARGS));
731 		
732 		// Test line 468: NOT_VARARGS - regular parameters
733 		assertTrue(b_a1_a.is(ElementFlag.NOT_VARARGS));
734 		// Test line 468: NOT_VARARGS - false branch (varargs parameter)
735 		assertFalse(b_varargs.is(ElementFlag.NOT_VARARGS));
736 		
737 		// Test line 469: default case - flags that fall through to super.is(flag)
738 		// Test with a modifier flag that's handled by ElementInfo (e.g., PUBLIC, FINAL, etc.)
739 		// Parameters don't have modifiers like PUBLIC/PRIVATE, but we can test the default path
740 		// by using a flag that ParameterInfo doesn't handle directly
741 		assertFalse(b_a1_a.is(ElementFlag.PUBLIC));  // Parameters don't have visibility modifiers
742 		assertFalse(b_a1_a.is(ElementFlag.STATIC));  // Parameters can't be static
743 		assertFalse(b_a1_a.is(ElementFlag.FINAL));   // Test with FINAL flag
744 	}
745 
746 	//====================================================================================================
747 	// isAll(ElementFlag...)
748 	//====================================================================================================
749 	@Test
750 	void a021_isAll() {
751 		assertTrue(b_a1_a.isAll(ElementFlag.NOT_SYNTHETIC, ElementFlag.NOT_VARARGS));
752 		assertFalse(b_a1_a.isAll(ElementFlag.SYNTHETIC, ElementFlag.VARARGS));
753 	}
754 
755 	//====================================================================================================
756 	// isAny(ElementFlag...)
757 	//====================================================================================================
758 	@Test
759 	void a022_isAny() {
760 		assertTrue(b_a1_a.isAny(ElementFlag.NOT_SYNTHETIC, ElementFlag.VARARGS));
761 		assertFalse(b_a1_a.isAny(ElementFlag.SYNTHETIC, ElementFlag.VARARGS));
762 	}
763 
764 	//====================================================================================================
765 	// isImplicit()
766 	//====================================================================================================
767 	@Test
768 	void a023_isImplicit() {
769 		// Regular parameters are not implicit
770 		assertFalse(b_a1_a.isImplicit());
771 	}
772 
773 	//====================================================================================================
774 	// isNamePresent()
775 	//====================================================================================================
776 	@Test
777 	void a024_isNamePresent() {
778 		// This checks if name is present in bytecode, not if it has a resolved name
779 		var namePresent = b_a1_a.isNamePresent();
780 		assertNotNull(namePresent);
781 	}
782 
783 	//====================================================================================================
784 	// isSynthetic()
785 	//====================================================================================================
786 	@Test
787 	void a025_isSynthetic() {
788 		// Regular parameters are not synthetic
789 		assertFalse(b_a1_a.isSynthetic());
790 	}
791 
792 	//====================================================================================================
793 	// isType(Class<?>)
794 	//====================================================================================================
795 	@Test
796 	void a026_isType() {
797 		assertTrue(b_a1_a.isType(int.class));
798 		assertFalse(b_a1_a.isType(String.class));
799 		assertTrue(b_a1_b.isType(String.class));
800 		assertFalse(b_a1_b.isType(int.class));
801 	}
802 
803 	//====================================================================================================
804 	// isVarArgs()
805 	//====================================================================================================
806 	@Test
807 	void a027_isVarArgs() {
808 		// Regular parameters are not varargs
809 		assertFalse(b_a1_a.isVarArgs());
810 	}
811 
812 	//====================================================================================================
813 	// of(Parameter)
814 	//====================================================================================================
815 	@Test
816 	void a028_of() throws NoSuchMethodException {
817 		// Test line 133: Method case (existing test)
818 		// Line 135: for loop entry
819 		// Line 137: wrapped == inner branch (identity check)
820 		var param = b_a1_a.inner();
821 		var pi = ParameterInfo.of(param);
822 		assertNotNull(pi);
823 		assertEquals(b_a1_a.getIndex(), pi.getIndex());
824 		assertEquals(b_a1_a.getParameterType(), pi.getParameterType());
825 		
826 		// Test line 131: Constructor case
827 		// Line 135: for loop entry
828 		// Line 137: wrapped == inner branch (identity check)
829 		var ctorParam = b_b_a.inner();
830 		var ctorPi = ParameterInfo.of(ctorParam);
831 		assertNotNull(ctorPi);
832 		assertEquals(b_b_a.getIndex(), ctorPi.getIndex());
833 		assertEquals(b_b_a.getParameterType(), ctorPi.getParameterType());
834 		
835 		// Test line 137: wrapped.equals(inner) branch
836 		// Get Parameter directly from Method.getParameters() instead of from ParameterInfo
837 		// This ensures we're testing the equals() branch, not just the == branch
838 		var method = B.class.getMethod("a1", int.class, String.class);
839 		var directParam = method.getParameters()[0];
840 		// Even though directParam might be the same reference, we're ensuring the equals() check is covered
841 		var pi2 = ParameterInfo.of(directParam);
842 		assertNotNull(pi2);
843 		assertEquals(0, pi2.getIndex());
844 		
845 		// Test with constructor parameter from direct source
846 		var ctor = B.class.getConstructor(int.class, String.class);
847 		var directCtorParam = ctor.getParameters()[0];
848 		var ctorPi2 = ParameterInfo.of(directCtorParam);
849 		assertNotNull(ctorPi2);
850 		assertEquals(0, ctorPi2.getIndex());
851 		
852 		// Null should throw
853 		assertThrows(IllegalArgumentException.class, () -> ParameterInfo.of(null));
854 		
855 		// Note: Line 140 is defensive code that should be unreachable in practice:
856 		// - A Parameter always belongs to its declaring executable's parameters
857 		// This branch is hard to test without mocking or reflection hacks
858 	}
859 
860 	//====================================================================================================
861 	// toString()
862 	//====================================================================================================
863 	@Test
864 	void a029_toString() {
865 		assertEquals("a1[1]", e_a1_b.toString());
866 	}
867 
868 	//====================================================================================================
869 	// equals(Object) and hashCode()
870 	//====================================================================================================
871 	@Test
872 	void a030_equals_hashCode() throws Exception {
873 		// Get ParameterInfo instances from the same Parameter
874 		Method method = EqualsTestClass.class.getMethod("method", String.class, int.class);
875 		Parameter p1 = method.getParameters()[0];
876 		ParameterInfo pi1a = ParameterInfo.of(p1);
877 		ParameterInfo pi1b = ParameterInfo.of(p1);
878 		
879 		Parameter p2 = method.getParameters()[1];
880 		ParameterInfo pi2 = ParameterInfo.of(p2);
881 
882 		// Same parameter should be equal
883 		assertEquals(pi1a, pi1b);
884 		assertEquals(pi1a.hashCode(), pi1b.hashCode());
885 		
886 		// Different parameters should not be equal
887 		assertNotEquals(pi1a, pi2);
888 		assertNotEquals(pi1a, null);
889 		assertNotEquals(pi1a, "not a ParameterInfo");
890 		
891 		// Reflexive
892 		assertEquals(pi1a, pi1a);
893 		
894 		// Symmetric
895 		assertEquals(pi1a, pi1b);
896 		assertEquals(pi1b, pi1a);
897 		
898 		// Transitive
899 		ParameterInfo pi1c = ParameterInfo.of(p1);
900 		assertEquals(pi1a, pi1b);
901 		assertEquals(pi1b, pi1c);
902 		assertEquals(pi1a, pi1c);
903 		
904 		// HashMap usage - same parameter should map to same value
905 		Map<ParameterInfo, String> map = new HashMap<>();
906 		map.put(pi1a, "value1");
907 		assertEquals("value1", map.get(pi1b));
908 		assertEquals("value1", map.get(pi1c));
909 		
910 		// HashMap usage - different parameters should map to different values
911 		map.put(pi2, "value2");
912 		assertEquals("value2", map.get(pi2));
913 		assertNotEquals("value2", map.get(pi1a));
914 		
915 		// HashSet usage
916 		Set<ParameterInfo> set = new HashSet<>();
917 		set.add(pi1a);
918 		assertTrue(set.contains(pi1b));
919 		assertTrue(set.contains(pi1c));
920 		assertFalse(set.contains(pi2));
921 	}
922 }
923