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.reflect.ConstructorInfo.*;
22  import static org.apache.juneau.commons.utils.CollectionUtils.*;
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.junit.jupiter.api.*;
33  
34  class ConstructorInfo_Test extends TestBase {
35  
36  	private static void check(String expected, Object o) {
37  		assertEquals(expected, TO_STRING.apply(o));
38  	}
39  
40  	private static final Function<Object,String> TO_STRING = new Function<>() {
41  		@Override
42  		public String apply(Object t) {
43  			if (t == null)
44  				return null;
45  			if (t instanceof Iterable)
46  				return StreamSupport.stream(((Iterable<?>)t).spliterator(), false).map(this).collect(Collectors.joining(","));
47  			if (t instanceof ClassInfo)
48  				return ((ClassInfo)t).getNameSimple();
49  			if (t instanceof ConstructorInfo)
50  				return ((ConstructorInfo)t).getShortName();
51  			if (t instanceof Constructor)
52  				return ConstructorInfo.of((Constructor<?>)t).getShortName();
53  			return t.toString();
54  		}
55  	};
56  
57  	private static ConstructorInfo ofc(Class<?> c, Class<?>...pt) {
58  		try {
59  			return of(c.getConstructor(pt));
60  		} catch (NoSuchMethodException | SecurityException e) {
61  			fail(e.getLocalizedMessage());
62  		}
63  		return null;
64  	}
65  
66  	//-----------------------------------------------------------------------------------------------------------------
67  	// Test classes
68  	//-----------------------------------------------------------------------------------------------------------------
69  
70  	static class A {
71  		public A() {}  // NOSONAR
72  	}
73  	static ConstructorInfo a = ofc(A.class);
74  
75  	public static class B {
76  		private String f;
77  		public B() {}
78  		public B(String f) {
79  			this.f = f;
80  		}
81  		public B(String f, String f2) {
82  			this.f = f;
83  		}
84  		protected B(int f) {}  // NOSONAR
85  		@Override
86  		public String toString() {
87  			return f;
88  		}
89  	}
90  	static ClassInfo b = ClassInfo.of(B.class);
91  	static ConstructorInfo
92  		b_c1 = b.getPublicConstructor(cons -> cons.getParameterCount() == 0).get(),
93  		b_c2 = b.getPublicConstructor(x -> x.hasParameterTypes(String.class)).get(),
94  		b_c3 = b.getDeclaredConstructor(x -> x.hasParameterTypes(int.class)).get(),
95  		b_c4 = b.getPublicConstructor(x -> x.hasParameterTypes(String.class, String.class)).get();
96  
97  	@Target({CONSTRUCTOR})
98  	@Retention(RUNTIME)
99  	public static @interface TestAnnotation {
100 		String value() default "";
101 	}
102 
103 	@Target({CONSTRUCTOR})
104 	@Retention(RUNTIME)
105 	public static @interface DeprecatedAnnotation {}
106 
107 	public static class DeprecatedClass {
108 		@Deprecated
109 		public DeprecatedClass() {}
110 	}
111 
112 	public static class VarArgsClass {
113 		public VarArgsClass(String...args) {}
114 	}
115 
116 	public static class ExceptionClass {
117 		public ExceptionClass() throws Exception {}
118 	}
119 
120 	public static class EqualsTestClass {
121 		public EqualsTestClass() {}
122 		public EqualsTestClass(String param) {}
123 	}
124 
125 	//====================================================================================================
126 	// accessible()
127 	//====================================================================================================
128 	@Test
129 	void a001_accessible() throws Exception {
130 		// Make protected constructor accessible
131 		b_c3.accessible();
132 		assertEquals(null, b_c3.newInstanceLenient(123).toString());
133 		
134 		// Verify it returns this for chaining
135 		var result = b_c3.accessible();
136 		assertSame(b_c3, result);
137 	}
138 
139 	//====================================================================================================
140 	// canAccept(Object...)
141 	//====================================================================================================
142 	@Test
143 	void a002_canAccept() {
144 		// Exact match
145 		assertTrue(b_c2.canAccept("test"));
146 		assertFalse(b_c2.canAccept(123));
147 		assertFalse(b_c2.canAccept("test", "extra"));
148 		
149 		// No parameters
150 		assertTrue(b_c1.canAccept());
151 		assertFalse(b_c1.canAccept("test"));
152 		
153 		// Multiple parameters
154 		assertTrue(b_c4.canAccept("test1", "test2"));
155 		assertFalse(b_c4.canAccept("test1"));
156 	}
157 
158 	//====================================================================================================
159 	// compareTo(ConstructorInfo)
160 	//====================================================================================================
161 	@Test
162 	void a003_compareTo() {
163 		var s = new TreeSet<>(l(b_c1, b_c2, b_c3, b_c4, a));
164 		check("A(),B(),B(int),B(String),B(String,String)", s);
165 	}
166 
167 	//====================================================================================================
168 	// getAnnotatedExceptionTypes()
169 	//====================================================================================================
170 	@Test
171 	void a004_getAnnotatedExceptionTypes() {
172 		var ci = ClassInfo.of(ExceptionClass.class);
173 		var ctor = ci.getPublicConstructor(x -> x.getParameterCount() == 0).get();
174 		var types = ctor.getAnnotatedExceptionTypes();
175 		assertNotNull(types);
176 		assertEquals(1, types.length);
177 		assertEquals(Exception.class, types[0].getType());
178 	}
179 
180 	//====================================================================================================
181 	// getAnnotatedParameterTypes()
182 	//====================================================================================================
183 	@Test
184 	void a005_getAnnotatedParameterTypes() {
185 		var types = b_c2.getAnnotatedParameterTypes();
186 		assertNotNull(types);
187 		assertEquals(1, types.length);
188 		assertEquals(String.class, types[0].getType());
189 	}
190 
191 	//====================================================================================================
192 	// getAnnotatedReceiverType()
193 	//====================================================================================================
194 	@Test
195 	void a006_getAnnotatedReceiverType() {
196 		// Top-level class constructor should return null
197 		var receiverType = b_c1.getAnnotatedReceiverType();
198 		assertNull(receiverType);
199 	}
200 
201 	//====================================================================================================
202 	// getAnnotatableType()
203 	//====================================================================================================
204 	@Test
205 	void a007_getAnnotatableType() {
206 		assertEquals(AnnotatableType.CONSTRUCTOR_TYPE, b_c1.getAnnotatableType());
207 	}
208 
209 	//====================================================================================================
210 	// getDeclaredAnnotations()
211 	//====================================================================================================
212 	@Test
213 	void a008_getDeclaredAnnotations() {
214 		var annotations = b_c1.getDeclaredAnnotations();
215 		assertNotNull(annotations);
216 		// Should be empty for unannotated constructor
217 		assertTrue(annotations.isEmpty());
218 	}
219 
220 	//====================================================================================================
221 	// getDeclaredAnnotations(Class<A>)
222 	//====================================================================================================
223 	@Test
224 	void a009_getDeclaredAnnotations_typed() {
225 		var annotations = b_c1.getDeclaredAnnotations(TestAnnotation.class);
226 		assertNotNull(annotations);
227 		assertEquals(0, annotations.count());
228 	}
229 
230 	//====================================================================================================
231 	// getDeclaringClass()
232 	//====================================================================================================
233 	@Test
234 	void a010_getDeclaringClass() {
235 		check("A", a.getDeclaringClass());
236 		check("B", b_c1.getDeclaringClass());
237 	}
238 
239 	//====================================================================================================
240 	// getExceptionTypes()
241 	//====================================================================================================
242 	@Test
243 	void a011_getExceptionTypes() {
244 		var ci = ClassInfo.of(ExceptionClass.class);
245 		var ctor = ci.getPublicConstructor(x -> x.getParameterCount() == 0).get();
246 		var exceptions = ctor.getExceptionTypes();
247 		assertNotNull(exceptions);
248 		assertEquals(1, exceptions.size());
249 		assertEquals(Exception.class, exceptions.get(0).inner());
250 	}
251 
252 	//====================================================================================================
253 	// getFullName()
254 	//====================================================================================================
255 	@Test
256 	void a012_getFullName() {
257 		var fullName = b_c2.getFullName();
258 		assertNotNull(fullName);
259 		assertTrue(fullName.contains("B"));
260 		assertTrue(fullName.contains("String"));
261 	}
262 
263 	//====================================================================================================
264 	// getLabel()
265 	//====================================================================================================
266 	@Test
267 	void a013_getLabel() {
268 		var label = b_c1.getLabel();
269 		assertNotNull(label);
270 		assertTrue(label.contains("B"));
271 		assertTrue(label.contains("()"));
272 	}
273 
274 	//====================================================================================================
275 	// getParameter(int)
276 	//====================================================================================================
277 	@Test
278 	void a014_getParameter() {
279 		var param = b_c2.getParameter(0);
280 		assertNotNull(param);
281 		assertEquals(String.class, param.getParameterType().inner());
282 		
283 		// Index out of bounds
284 		assertThrows(IndexOutOfBoundsException.class, () -> b_c2.getParameter(1));
285 		assertThrows(IndexOutOfBoundsException.class, () -> b_c1.getParameter(0));
286 	}
287 
288 	//====================================================================================================
289 	// getParameterCount()
290 	//====================================================================================================
291 	@Test
292 	void a015_getParameterCount() {
293 		assertEquals(0, b_c1.getParameterCount());
294 		assertEquals(1, b_c2.getParameterCount());
295 		assertEquals(2, b_c4.getParameterCount());
296 	}
297 
298 	//====================================================================================================
299 	// getParameters()
300 	//====================================================================================================
301 	@Test
302 	void a016_getParameters() {
303 		var params = b_c2.getParameters();
304 		assertNotNull(params);
305 		assertEquals(1, params.size());
306 		assertEquals(String.class, params.get(0).getParameterType().inner());
307 		
308 		// Test stream operations
309 		int[] count = {0};
310 		b_c4.getParameters().stream().filter(x -> true).forEach(x -> count[0]++);
311 		assertEquals(2, count[0]);
312 	}
313 
314 	//====================================================================================================
315 	// getShortName()
316 	//====================================================================================================
317 	@Test
318 	void a017_getShortName() {
319 		check("A()", a);
320 		check("B()", b_c1);
321 		check("B(String)", b_c2);
322 		check("B(String,String)", b_c4);
323 	}
324 
325 	//====================================================================================================
326 	// getSimpleName()
327 	//====================================================================================================
328 	@Test
329 	void a018_getSimpleName() {
330 		assertEquals("A", a.getSimpleName());
331 		assertEquals("B", b_c1.getSimpleName());
332 	}
333 
334 	//====================================================================================================
335 	// getTypeParameters()
336 	//====================================================================================================
337 	@Test
338 	void a019_getTypeParameters() {
339 		var typeParams = b_c1.getTypeParameters();
340 		assertNotNull(typeParams);
341 		assertEquals(0, typeParams.length);
342 	}
343 
344 	//====================================================================================================
345 	// hasAnnotation(Class<A>)
346 	//====================================================================================================
347 	@Test
348 	void a020_hasAnnotation() {
349 		assertFalse(b_c1.hasAnnotation(TestAnnotation.class));
350 	}
351 
352 	//====================================================================================================
353 	// hasAnyName(Collection<String>)
354 	//====================================================================================================
355 	@Test
356 	void a021_hasAnyName_collection() {
357 		assertTrue(b_c1.hasAnyName(Arrays.asList("B", "C")));
358 		assertFalse(b_c1.hasAnyName(Arrays.asList("C", "D")));
359 	}
360 
361 	//====================================================================================================
362 	// hasAnyName(String...)
363 	//====================================================================================================
364 	@Test
365 	void a022_hasAnyName_varargs() {
366 		assertTrue(b_c1.hasAnyName("B", "C"));
367 		assertFalse(b_c1.hasAnyName("C", "D"));
368 	}
369 
370 	//====================================================================================================
371 	// hasMatchingParameters(List<ParameterInfo>)
372 	//====================================================================================================
373 	@Test
374 	void a023_hasMatchingParameters() {
375 		var params1 = b_c2.getParameters();
376 		assertTrue(b_c2.hasMatchingParameters(params1));
377 		
378 		var params2 = b_c4.getParameters();
379 		assertFalse(b_c2.hasMatchingParameters(params2));
380 	}
381 
382 	//====================================================================================================
383 	// hasName(String)
384 	//====================================================================================================
385 	@Test
386 	void a024_hasName() {
387 		assertTrue(b_c1.hasName("B"));
388 		assertFalse(b_c1.hasName("A"));
389 	}
390 
391 	//====================================================================================================
392 	// hasNumParameters(int)
393 	//====================================================================================================
394 	@Test
395 	void a025_hasNumParameters() {
396 		assertTrue(b_c1.hasNumParameters(0));
397 		assertTrue(b_c2.hasNumParameters(1));
398 		assertFalse(b_c1.hasNumParameters(1));
399 	}
400 
401 	//====================================================================================================
402 	// hasParameters()
403 	//====================================================================================================
404 	@Test
405 	void a026_hasParameters() {
406 		assertFalse(b_c1.hasParameters());
407 		assertTrue(b_c2.hasParameters());
408 	}
409 
410 	//====================================================================================================
411 	// hasParameterTypeParents(Class<?>...)
412 	//====================================================================================================
413 	@Test
414 	void a027_hasParameterTypeParents_class() {
415 		// String parameter type is a parent of String (exact match)
416 		assertTrue(b_c2.hasParameterTypeParents(String.class));
417 		// String is NOT a parent of Object (Object is the parent of String)
418 		assertFalse(b_c2.hasParameterTypeParents(Object.class));
419 		assertFalse(b_c2.hasParameterTypeParents(Integer.class));
420 	}
421 
422 	//====================================================================================================
423 	// hasParameterTypeParents(ClassInfo...)
424 	//====================================================================================================
425 	@Test
426 	void a028_hasParameterTypeParents_classInfo() {
427 		var stringClass = ClassInfo.of(String.class);
428 		var objectClass = ClassInfo.of(Object.class);
429 		var integerClass = ClassInfo.of(Integer.class);
430 		assertTrue(b_c2.hasParameterTypeParents(stringClass));
431 		assertFalse(b_c2.hasParameterTypeParents(objectClass));
432 		assertFalse(b_c2.hasParameterTypeParents(integerClass));
433 	}
434 
435 	//====================================================================================================
436 	// hasParameterTypes(Class<?>...)
437 	//====================================================================================================
438 	@Test
439 	void a029_hasParameterTypes_class() {
440 		assertTrue(b_c2.hasParameterTypes(String.class));
441 		assertFalse(b_c2.hasParameterTypes(Integer.class));
442 		assertFalse(b_c2.hasParameterTypes(String.class, String.class));
443 	}
444 
445 	//====================================================================================================
446 	// hasParameterTypes(ClassInfo...)
447 	//====================================================================================================
448 	@Test
449 	void a030_hasParameterTypes_classInfo() {
450 		var stringClass = ClassInfo.of(String.class);
451 		var integerClass = ClassInfo.of(Integer.class);
452 		assertTrue(b_c2.hasParameterTypes(stringClass));
453 		assertFalse(b_c2.hasParameterTypes(integerClass));
454 	}
455 
456 	//====================================================================================================
457 	// hasParameterTypesLenient(Class<?>...)
458 	//====================================================================================================
459 	@Test
460 	void a031_hasParameterTypesLenient_class() {
461 		// Exact match
462 		assertTrue(b_c2.hasParameterTypesLenient(String.class));
463 		// String parameter type is NOT a parent of Object
464 		assertFalse(b_c2.hasParameterTypesLenient(Object.class));
465 		assertFalse(b_c2.hasParameterTypesLenient(Integer.class));
466 	}
467 
468 	//====================================================================================================
469 	// hasParameterTypesLenient(ClassInfo...)
470 	//====================================================================================================
471 	@Test
472 	void a032_hasParameterTypesLenient_classInfo() {
473 		var stringClass = ClassInfo.of(String.class);
474 		var objectClass = ClassInfo.of(Object.class);
475 		var integerClass = ClassInfo.of(Integer.class);
476 		assertTrue(b_c2.hasParameterTypesLenient(stringClass));
477 		assertFalse(b_c2.hasParameterTypesLenient(objectClass));
478 		assertFalse(b_c2.hasParameterTypesLenient(integerClass));
479 	}
480 
481 	//====================================================================================================
482 	// inner()
483 	//====================================================================================================
484 	@Test
485 	void a033_inner() {
486 		var ctor = b_c1.inner();
487 		assertNotNull(ctor);
488 		assertEquals(B.class, ctor.getDeclaringClass());
489 	}
490 
491 	//====================================================================================================
492 	// is(ElementFlag)
493 	//====================================================================================================
494 	@Test
495 	void a034_is() {
496 		assertTrue(b_c1.is(ElementFlag.CONSTRUCTOR));
497 		assertFalse(b_c1.is(ElementFlag.NOT_CONSTRUCTOR));
498 		assertTrue(b_c1.is(ElementFlag.HAS_NO_PARAMS));
499 		assertFalse(b_c1.is(ElementFlag.HAS_PARAMS));
500 		assertTrue(b_c2.is(ElementFlag.HAS_PARAMS));
501 		assertFalse(b_c2.is(ElementFlag.HAS_NO_PARAMS));
502 	}
503 
504 	//====================================================================================================
505 	// isAccessible()
506 	//====================================================================================================
507 	@Test
508 	void a035_isAccessible() {
509 		// Test isAccessible() before and after setAccessible()
510 		var privateBefore = b_c3.isAccessible();
511 		
512 		// Make it accessible
513 		b_c3.setAccessible();
514 		
515 		// After setAccessible(), it should be accessible (if Java 9+)
516 		var privateAfter = b_c3.isAccessible();
517 		
518 		// Verify the method doesn't throw and returns a boolean
519 		assertTrue(privateAfter || !privateBefore, "After setAccessible(), isAccessible() should return true (Java 9+) or false (Java 8)");
520 		
521 		// Public constructors might already be accessible
522 		var publicAccessible = b_c1.isAccessible();
523 		assertNotNull(publicAccessible);
524 	}
525 
526 	//====================================================================================================
527 	// isAll(ElementFlag...)
528 	//====================================================================================================
529 	@Test
530 	void a036_isAll() {
531 		assertTrue(b_c1.isAll(ElementFlag.CONSTRUCTOR, ElementFlag.HAS_NO_PARAMS));
532 		assertFalse(b_c1.isAll(ElementFlag.CONSTRUCTOR, ElementFlag.HAS_PARAMS));
533 	}
534 
535 	//====================================================================================================
536 	// isAny(ElementFlag...)
537 	//====================================================================================================
538 	@Test
539 	void a037_isAny() {
540 		assertTrue(b_c1.isAny(ElementFlag.CONSTRUCTOR, ElementFlag.HAS_PARAMS));
541 		assertFalse(b_c1.isAny(ElementFlag.HAS_PARAMS, ElementFlag.SYNTHETIC));
542 	}
543 
544 	//====================================================================================================
545 	// isConstructor()
546 	//====================================================================================================
547 	@Test
548 	void a038_isConstructor() {
549 		assertTrue(b_c1.isConstructor());
550 	}
551 
552 	//====================================================================================================
553 	// isDeprecated()
554 	//====================================================================================================
555 	@Test
556 	void a039_isDeprecated() {
557 		var ci = ClassInfo.of(DeprecatedClass.class);
558 		var ctor = ci.getPublicConstructor(x -> x.getParameterCount() == 0).get();
559 		assertTrue(ctor.isDeprecated());
560 		assertFalse(b_c1.isDeprecated());
561 	}
562 
563 	//====================================================================================================
564 	// isNotDeprecated()
565 	//====================================================================================================
566 	@Test
567 	void a040_isNotDeprecated() {
568 		var ci = ClassInfo.of(DeprecatedClass.class);
569 		var ctor = ci.getPublicConstructor(x -> x.getParameterCount() == 0).get();
570 		assertFalse(ctor.isNotDeprecated());
571 		assertTrue(b_c1.isNotDeprecated());
572 	}
573 
574 	//====================================================================================================
575 	// isSynthetic()
576 	//====================================================================================================
577 	@Test
578 	void a041_isSynthetic() {
579 		// Regular constructors are not synthetic
580 		assertFalse(b_c1.isSynthetic());
581 	}
582 
583 	//====================================================================================================
584 	// isVarArgs()
585 	//====================================================================================================
586 	@Test
587 	void a042_isVarArgs() {
588 		var ci = ClassInfo.of(VarArgsClass.class);
589 		var ctor = ci.getPublicConstructor(x -> x.hasParameterTypes(String[].class)).get();
590 		assertTrue(ctor.isVarArgs());
591 		assertFalse(b_c1.isVarArgs());
592 	}
593 
594 	//====================================================================================================
595 	// isVisible(Visibility)
596 	//====================================================================================================
597 	@Test
598 	void a043_isVisible() {
599 		// Public constructor
600 		assertTrue(b_c1.isVisible(Visibility.PUBLIC));
601 		assertTrue(b_c1.isVisible(Visibility.PROTECTED));
602 		assertTrue(b_c1.isVisible(Visibility.PRIVATE)); // PRIVATE includes all
603 		assertTrue(b_c1.isVisible(Visibility.DEFAULT));
604 		
605 		// Protected constructor
606 		assertFalse(b_c3.isVisible(Visibility.PUBLIC));
607 		assertTrue(b_c3.isVisible(Visibility.PROTECTED));
608 		assertTrue(b_c3.isVisible(Visibility.PRIVATE)); // PRIVATE includes all
609 		assertTrue(b_c3.isVisible(Visibility.DEFAULT));
610 	}
611 
612 	//====================================================================================================
613 	// newInstance(Object...)
614 	//====================================================================================================
615 	@Test
616 	void a044_newInstance() throws Exception {
617 		assertEquals(null, b_c1.newInstance().toString());
618 		assertEquals("foo", b_c2.newInstance("foo").toString());
619 	}
620 
621 	//====================================================================================================
622 	// newInstanceLenient(Object...)
623 	//====================================================================================================
624 	@Test
625 	void a045_newInstanceLenient() throws Exception {
626 		assertEquals(null, b_c1.newInstanceLenient().toString());
627 		assertEquals("foo", b_c2.newInstanceLenient("foo").toString());
628 	}
629 
630 	//====================================================================================================
631 	// of(ClassInfo, Constructor)
632 	//====================================================================================================
633 	@Test
634 	void a046_of_withDeclaringClass() {
635 		check("A()", ConstructorInfo.of(ClassInfo.of(A.class), a.inner()));
636 	}
637 
638 	//====================================================================================================
639 	// of(Constructor)
640 	//====================================================================================================
641 	@Test
642 	void a047_of_noDeclaringClass() {
643 		check("A()", a.inner());
644 		
645 		// Null should throw
646 		assertThrows(IllegalArgumentException.class, () -> ConstructorInfo.of((Constructor<?>)null));
647 		assertThrows(IllegalArgumentException.class, () -> ConstructorInfo.of((ClassInfo)null, null));
648 	}
649 
650 	//====================================================================================================
651 	// parameterMatchesLenientCount(Class<?>...)
652 	//====================================================================================================
653 	@Test
654 	void a048_parameterMatchesLenientCount_class() {
655 		// Exact match - String parameter type is a parent of String
656 		assertEquals(1, b_c2.parameterMatchesLenientCount(String.class));
657 		// String is NOT a parent of Object, so this should return -1
658 		assertEquals(-1, b_c2.parameterMatchesLenientCount(Object.class));
659 		// No match
660 		assertEquals(-1, b_c2.parameterMatchesLenientCount(Integer.class));
661 	}
662 
663 	//====================================================================================================
664 	// parameterMatchesLenientCount(ClassInfo...)
665 	//====================================================================================================
666 	@Test
667 	void a049_parameterMatchesLenientCount_classInfo() {
668 		var stringClass = ClassInfo.of(String.class);
669 		var objectClass = ClassInfo.of(Object.class);
670 		var integerClass = ClassInfo.of(Integer.class);
671 		
672 		assertEquals(1, b_c2.parameterMatchesLenientCount(stringClass));
673 		// String is NOT a parent of Object, so this should return -1
674 		assertEquals(-1, b_c2.parameterMatchesLenientCount(objectClass));
675 		assertEquals(-1, b_c2.parameterMatchesLenientCount(integerClass));
676 	}
677 
678 	//====================================================================================================
679 	// parameterMatchesLenientCount(Object...)
680 	//====================================================================================================
681 	@Test
682 	void a050_parameterMatchesLenientCount_object() {
683 		// String parameter can accept String object
684 		assertEquals(1, b_c2.parameterMatchesLenientCount("test"));
685 		// String parameter can accept Object (String.isAssignableFrom(Object) is false, but canAcceptArg checks if Object can be assigned to String)
686 		// Actually, wait - canAcceptArg checks if the parameter type can accept the argument value
687 		// String parameter cannot accept an Object instance (without casting), so this should return -1
688 		assertEquals(-1, b_c2.parameterMatchesLenientCount(new Object()));
689 		assertEquals(-1, b_c2.parameterMatchesLenientCount(123));
690 	}
691 
692 	//====================================================================================================
693 	// setAccessible()
694 	//====================================================================================================
695 	@Test
696 	void a051_setAccessible() throws Exception {
697 		// Make protected constructor accessible
698 		var result = b_c3.setAccessible();
699 		assertTrue(result);
700 		assertEquals(null, b_c3.newInstanceLenient(123).toString());
701 	}
702 
703 	//====================================================================================================
704 	// toGenericString()
705 	//====================================================================================================
706 	@Test
707 	void a052_toGenericString() {
708 		var str = b_c2.toGenericString();
709 		assertNotNull(str);
710 		assertTrue(str.contains("B"));
711 		assertTrue(str.contains("String"));
712 	}
713 
714 	//====================================================================================================
715 	// toString()
716 	//====================================================================================================
717 	@Test
718 	void a053_toString() {
719 		check("A()", a.toString());
720 		check("B()", b_c1.toString());
721 		check("B(String)", b_c2.toString());
722 	}
723 
724 	//====================================================================================================
725 	// equals(Object) and hashCode()
726 	//====================================================================================================
727 	@Test
728 	void a054_equals_hashCode() throws Exception {
729 		// Get ConstructorInfo instances from the same Constructor
730 		Constructor<?> c1 = EqualsTestClass.class.getConstructor();
731 		ConstructorInfo ci1a = ConstructorInfo.of(c1);
732 		ConstructorInfo ci1b = ConstructorInfo.of(c1);
733 		
734 		Constructor<?> c2 = EqualsTestClass.class.getConstructor(String.class);
735 		ConstructorInfo ci2 = ConstructorInfo.of(c2);
736 
737 		// Same constructor should be equal
738 		assertEquals(ci1a, ci1b);
739 		assertEquals(ci1a.hashCode(), ci1b.hashCode());
740 		
741 		// Different constructors should not be equal
742 		assertNotEquals(ci1a, ci2);
743 		assertNotEquals(ci1a, null);
744 		assertNotEquals(ci1a, "not a ConstructorInfo");
745 		
746 		// Reflexive
747 		assertEquals(ci1a, ci1a);
748 		
749 		// Symmetric
750 		assertEquals(ci1a, ci1b);
751 		assertEquals(ci1b, ci1a);
752 		
753 		// Transitive
754 		ConstructorInfo ci1c = ConstructorInfo.of(c1);
755 		assertEquals(ci1a, ci1b);
756 		assertEquals(ci1b, ci1c);
757 		assertEquals(ci1a, ci1c);
758 		
759 		// HashMap usage - same constructor should map to same value
760 		Map<ConstructorInfo, String> map = new HashMap<>();
761 		map.put(ci1a, "value1");
762 		assertEquals("value1", map.get(ci1b));
763 		assertEquals("value1", map.get(ci1c));
764 		
765 		// HashMap usage - different constructors should map to different values
766 		map.put(ci2, "value2");
767 		assertEquals("value2", map.get(ci2));
768 		assertNotEquals("value2", map.get(ci1a));
769 		
770 		// HashSet usage
771 		Set<ConstructorInfo> set = new HashSet<>();
772 		set.add(ci1a);
773 		assertTrue(set.contains(ci1b));
774 		assertTrue(set.contains(ci1c));
775 		assertFalse(set.contains(ci2));
776 	}
777 }
778