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.TestUtils.*;
22  import static org.apache.juneau.commons.reflect.ElementFlag.*;
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  
30  import org.apache.juneau.*;
31  import org.junit.jupiter.api.*;
32  
33  class FieldInfo_Test extends TestBase {
34  
35  	@Documented
36  	@Target(FIELD)
37  	@Retention(RUNTIME)
38  	@Inherited
39  	public static @interface A {
40  		String value();
41  	}
42  
43  	@Target(FIELD)
44  	@Retention(RUNTIME)
45  	public static @interface TestAnnotation1 {
46  		String value() default "";
47  	}
48  
49  	@Target(FIELD)
50  	@Retention(RUNTIME)
51  	public static @interface TestAnnotation2 {
52  		int value() default 0;
53  	}
54  
55  	@Target(FIELD)
56  	@Retention(RUNTIME)
57  	public static @interface TestAnnotation3 {
58  		String value() default "";
59  	}
60  
61  	private static void check(String expected, Object o) {
62  		assertEquals(expected, TO_STRING.apply(o));
63  	}
64  
65  	private static final Function<Object,String> TO_STRING = t -> {
66  		if (t == null)
67  			return null;
68  		if (t instanceof A)
69  			return "@A(" + ((A)t).value() + ")";
70  		if (t instanceof ClassInfo)
71  			return ((ClassInfo)t).getNameSimple();
72  		if (t instanceof FieldInfo)
73  			return ((FieldInfo)t).getName();
74  		if (t instanceof Field)
75  			return ((Field)t).getName();
76  		return t.toString();
77  	};
78  
79  	private static FieldInfo off(Class<?> c, String name) {
80  		try {
81  			return FieldInfo.of(c.getDeclaredField(name));
82  		} catch (SecurityException | NoSuchFieldException e) {
83  			fail(e.getLocalizedMessage());
84  		}
85  		return null;
86  	}
87  
88  	//-----------------------------------------------------------------------------------------------------------------
89  	// Test classes
90  	//-----------------------------------------------------------------------------------------------------------------
91  
92  	static class A1 {
93  		public int f1;
94  	}
95  	FieldInfo a1_f1 = off(A1.class, "f1");
96  
97  	public static class B {
98  		@A("a1") public int a1;
99  		public int a2;
100 	}
101 	FieldInfo
102 		b_a1 = off(B.class, "a1"),
103 		b_a2 = off(B.class, "a2");
104 
105 	abstract static class C {
106 		@Deprecated public int deprecated;
107 		public int notDeprecated;
108 		public int isPublic;
109 		protected int isNotPublic;
110 		public static int isStatic;
111 		public int isNotStatic;
112 		public transient int isTransient;
113 		public int isNotTransient;
114 	}
115 	static ClassInfo c = ClassInfo.of(C.class);
116 	static FieldInfo
117 		c_deprecated = c.getPublicField(x -> x.hasName("deprecated")).get(),
118 		c_notDeprecated = c.getPublicField(x -> x.hasName("notDeprecated")).get(),
119 		c_isPublic = c.getPublicField(x -> x.hasName("isPublic")).get(),
120 		c_isNotPublic = c.getDeclaredField(x -> x.hasName("isNotPublic")).get(),
121 		c_isStatic = c.getPublicField(x -> x.hasName("isStatic")).get(),
122 		c_isNotStatic = c.getPublicField(x -> x.hasName("isNotStatic")).get(),
123 		c_isTransient = c.getPublicField(x -> x.hasName("isTransient")).get(),
124 		c_isNotTransient = c.getPublicField(x -> x.hasName("isNotTransient")).get()
125 	;
126 
127 	abstract static class D {
128 		public int isPublic;
129 		protected int isProtected;
130 		@SuppressWarnings("unused")
131 		private int isPrivate;
132 		int isDefault;
133 	}
134 	static ClassInfo d = ClassInfo.of(D.class);
135 	static FieldInfo
136 		d_isPublic = d.getPublicField(x -> x.hasName("isPublic")).get(),
137 		d_isProtected = d.getDeclaredField(x -> x.hasName("isProtected")).get(),
138 		d_isPrivate = d.getDeclaredField(x -> x.hasName("isPrivate")).get(),
139 		d_isDefault = d.getDeclaredField(x -> x.hasName("isDefault")).get();
140 
141 	static class E {
142 		public int a1;
143 		int a2;
144 	}
145 	static ClassInfo e = ClassInfo.of(E.class);
146 	static FieldInfo
147 		e_a1 = e.getPublicField(x -> x.hasName("a1")).get(),
148 		e_a2 = e.getDeclaredField(x -> x.hasName("a2")).get();
149 
150 	public static class F {
151 		@TestAnnotation1("test1")
152 		@TestAnnotation2(42)
153 		public String field1;
154 
155 		@TestAnnotation1("test2")
156 		public String field2;
157 
158 		public String field3;
159 	}
160 	static ClassInfo f = ClassInfo.of(F.class);
161 	static FieldInfo
162 		f_field1 = f.getPublicField(x -> x.hasName("field1")).get(),
163 		f_field2 = f.getPublicField(x -> x.hasName("field2")).get(),
164 		f_field3 = f.getPublicField(x -> x.hasName("field3")).get();
165 
166 	public static class G {
167 		public String field1;
168 		public int field2;
169 	}
170 	static ClassInfo g = ClassInfo.of(G.class);
171 	static FieldInfo
172 		g_field1 = g.getPublicField(x -> x.hasName("field1")).get(),
173 		g_field2 = g.getPublicField(x -> x.hasName("field2")).get();
174 
175 	public static class InnerClass {
176 		public String innerField;
177 	}
178 	static ClassInfo inner = ClassInfo.of(InnerClass.class);
179 	static FieldInfo inner_field = inner.getPublicField(x -> x.hasName("innerField")).get();
180 
181 	public static class GetSetTest {
182 		public String value;
183 		public Integer number;
184 	}
185 	static ClassInfo getSetTest = ClassInfo.of(GetSetTest.class);
186 	static FieldInfo
187 		getSetTest_value = getSetTest.getPublicField(x -> x.hasName("value")).get(),
188 		getSetTest_number = getSetTest.getPublicField(x -> x.hasName("number")).get();
189 
190 	public enum TestEnum {
191 		VALUE1, VALUE2
192 	}
193 	static ClassInfo testEnum = ClassInfo.of(TestEnum.class);
194 	static FieldInfo
195 		testEnum_value1 = testEnum.getPublicField(x -> x.hasName("VALUE1")).get(),
196 		testEnum_value2 = testEnum.getPublicField(x -> x.hasName("VALUE2")).get();
197 
198 	public static class EqualsTestClass {
199 		public String field1;
200 		public int field2;
201 	}
202 
203 	//====================================================================================================
204 	// accessible()
205 	//====================================================================================================
206 	@Test
207 	void a001_accessible() {
208 		assertDoesNotThrow(()->d_isPublic.accessible());
209 		assertDoesNotThrow(()->d_isProtected.accessible());
210 		assertDoesNotThrow(()->d_isPrivate.accessible());
211 		assertDoesNotThrow(()->d_isDefault.accessible());
212 		
213 		// Verify it returns this for chaining
214 		var result = d_isPublic.accessible();
215 		assertSame(d_isPublic, result);
216 	}
217 
218 	//====================================================================================================
219 	// compareTo(FieldInfo)
220 	//====================================================================================================
221 	@Test
222 	void a002_compareTo() {
223 		// Fields should be sorted by field name only
224 		// "a2" comes before "f1" alphabetically, so b_a2 should come before a1_f1
225 		var b_a2 = off(B.class, "a2");
226 		
227 		// "a2" < "f1" alphabetically, so b_a2 should come before a1_f1
228 		assertTrue(b_a2.compareTo(a1_f1) < 0);
229 		assertTrue(a1_f1.compareTo(b_a2) > 0);
230 		assertEquals(0, a1_f1.compareTo(a1_f1));
231 		
232 		// Test fields from same class - should be sorted by field name
233 		assertTrue(b_a1.compareTo(b_a2) < 0);
234 		assertTrue(b_a2.compareTo(b_a1) > 0);
235 	}
236 
237 	//====================================================================================================
238 	// get(Object)
239 	//====================================================================================================
240 	@Test
241 	void a003_get() {
242 		var obj = new GetSetTest();
243 		obj.value = "test";
244 		obj.number = 42;
245 		
246 		assertEquals("test", getSetTest_value.get(obj));
247 		assertEquals(Integer.valueOf(42), getSetTest_number.get(obj));
248 		
249 		// Null value
250 		obj.value = null;
251 		assertNull(getSetTest_value.get(obj));
252 	}
253 
254 	//====================================================================================================
255 	// getAnnotatableType()
256 	//====================================================================================================
257 	@Test
258 	void a004_getAnnotatableType() {
259 		assertEquals(AnnotatableType.FIELD_TYPE, a1_f1.getAnnotatableType());
260 	}
261 
262 	//====================================================================================================
263 	// getAnnotatedType()
264 	//====================================================================================================
265 	@Test
266 	void a005_getAnnotatedType() {
267 		var annotatedType = e_a1.getAnnotatedType();
268 		assertNotNull(annotatedType);
269 		assertEquals(int.class, annotatedType.getType());
270 	}
271 
272 	//====================================================================================================
273 	// getAnnotations()
274 	//====================================================================================================
275 	@Test
276 	void a006_getAnnotations() {
277 		var annotations1 = f_field1.getAnnotations();
278 		assertEquals(2, annotations1.size());
279 		assertTrue(annotations1.stream().anyMatch(a -> a.hasSimpleName("TestAnnotation1")));
280 		assertTrue(annotations1.stream().anyMatch(a -> a.hasSimpleName("TestAnnotation2")));
281 
282 		var annotations2 = f_field2.getAnnotations();
283 		assertEquals(1, annotations2.size());
284 		assertTrue(annotations2.stream().anyMatch(a -> a.hasSimpleName("TestAnnotation1")));
285 
286 		var annotations3 = f_field3.getAnnotations();
287 		assertEquals(0, annotations3.size());
288 		
289 		// Test memoization - should return same instance
290 		var annotations1_2 = f_field1.getAnnotations();
291 		assertSame(annotations1, annotations1_2);
292 	}
293 
294 	//====================================================================================================
295 	// getAnnotations(Class<A>)
296 	//====================================================================================================
297 	@Test
298 	void a007_getAnnotations_typed() {
299 		var ann1_type1 = f_field1.getAnnotations(TestAnnotation1.class).toList();
300 		assertEquals(1, ann1_type1.size());
301 		assertEquals("test1", ann1_type1.get(0).getValue().get());
302 
303 		var ann1_type2 = f_field1.getAnnotations(TestAnnotation2.class).toList();
304 		assertEquals(1, ann1_type2.size());
305 		assertEquals(42, ann1_type2.get(0).getInt("value").get());
306 
307 		var ann1_type3 = f_field1.getAnnotations(TestAnnotation3.class).toList();
308 		assertEquals(0, ann1_type3.size());
309 
310 		var ann2_type1 = f_field2.getAnnotations(TestAnnotation1.class).toList();
311 		assertEquals(1, ann2_type1.size());
312 		assertEquals("test2", ann2_type1.get(0).getValue().get());
313 
314 		var ann2_type2 = f_field2.getAnnotations(TestAnnotation2.class).toList();
315 		assertEquals(0, ann2_type2.size());
316 	}
317 
318 	//====================================================================================================
319 	// getDeclaringClass()
320 	//====================================================================================================
321 	@Test
322 	void a008_getDeclaringClass() {
323 		check("A1", a1_f1.getDeclaringClass());
324 		check("B", b_a1.getDeclaringClass());
325 	}
326 
327 	//====================================================================================================
328 	// getFieldType()
329 	//====================================================================================================
330 	@Test
331 	void a009_getFieldType() {
332 		check("int", e_a1.getFieldType());
333 		check("int", e_a2.getFieldType());
334 		
335 		// Test memoization - should return same instance
336 		var type1 = e_a1.getFieldType();
337 		var type2 = e_a1.getFieldType();
338 		assertSame(type1, type2);
339 	}
340 
341 	//====================================================================================================
342 	// getFullName()
343 	//====================================================================================================
344 	@Test
345 	void a010_getFullName() throws Exception {
346 		String fullName1 = g_field1.getFullName();
347 		String fullName2 = g_field2.getFullName();
348 		
349 		// Test line 449: getPackage() returns null (default package class)
350 		// A field can have a null package if its declaring class is in the default package
351 		// According to Java API, Class.getPackage() returns null for classes in the default package
352 		try {
353 			Class<?> defaultPkgClassType = Class.forName("DefaultPackageTestClass");
354 			ClassInfo defaultPkgClass = ClassInfo.of(defaultPkgClassType);
355 			PackageInfo pkg = defaultPkgClass.getPackage();
356 			if (pkg == null) {
357 				// Test the false branch of line 449: when package is null, don't append package name
358 				FieldInfo defaultPkgField = defaultPkgClass.getPublicField(x -> x.hasName("testField")).get();
359 				String fullName = defaultPkgField.getFullName();
360 				// When package is null, getFullName() should not include package prefix
361 				assertTrue(fullName.startsWith("DefaultPackageTestClass"), "Full name should start with class name when package is null: " + fullName);
362 				assertTrue(fullName.endsWith(".testField"), "Full name should end with field name: " + fullName);
363 				// Verify no package prefix (no dots before the class name, except for inner classes)
364 				assertFalse(fullName.matches("^[a-z][a-z0-9]*(\\.[a-z][a-z0-9]*)+\\."), "Full name should not have package prefix when package is null: " + fullName);
365 			}
366 		} catch (ClassNotFoundException e) {
367 			// If class not found, skip this part of the test
368 		}
369 
370 		assertTrue(fullName1.endsWith("FieldInfo_Test$G.field1"));
371 		assertTrue(fullName2.endsWith("FieldInfo_Test$G.field2"));
372 		
373 		assertTrue(fullName1.startsWith("org.apache.juneau.commons.reflect."));
374 		assertTrue(fullName2.startsWith("org.apache.juneau.commons.reflect."));
375 		
376 		// Test memoization - should return same instance
377 		String name1 = g_field1.getFullName();
378 		String name2 = g_field1.getFullName();
379 		assertSame(name1, name2);
380 		
381 		// Test with inner class
382 		String innerFullName = inner_field.getFullName();
383 		assertTrue(innerFullName.contains("FieldInfo_Test$InnerClass"));
384 		assertTrue(innerFullName.endsWith(".innerField"));
385 	}
386 
387 	//====================================================================================================
388 	// getLabel()
389 	//====================================================================================================
390 	@Test
391 	void a011_getLabel() {
392 		var label = a1_f1.getLabel();
393 		assertNotNull(label);
394 		assertTrue(label.contains("A1"));
395 		assertTrue(label.contains("f1"));
396 	}
397 
398 	//====================================================================================================
399 	// getName()
400 	//====================================================================================================
401 	@Test
402 	void a012_getName() {
403 		assertEquals("f1", a1_f1.getName());
404 		assertEquals("a1", b_a1.getName());
405 		assertEquals("a2", b_a2.getName());
406 	}
407 
408 	//====================================================================================================
409 	// hasAnnotation(Class<A>)
410 	//====================================================================================================
411 	@Test
412 	void a013_hasAnnotation() {
413 		assertTrue(b_a1.hasAnnotation(A.class));
414 		assertFalse(b_a2.hasAnnotation(A.class));
415 	}
416 
417 	//====================================================================================================
418 	// hasName(String)
419 	//====================================================================================================
420 	@Test
421 	void a014_hasName() {
422 		assertTrue(b_a1.hasName("a1"));
423 		assertFalse(b_a1.hasName("a2"));
424 		assertFalse(b_a1.hasName(null));
425 	}
426 
427 	//====================================================================================================
428 	// inner()
429 	//====================================================================================================
430 	@Test
431 	void a015_inner() {
432 		check("f1", a1_f1.inner());
433 		var field = a1_f1.inner();
434 		assertNotNull(field);
435 		assertEquals("f1", field.getName());
436 	}
437 
438 	//====================================================================================================
439 	// is(ElementFlag)
440 	//====================================================================================================
441 	@Test
442 	void a016_is() {
443 		assertTrue(c_deprecated.is(DEPRECATED));
444 		assertTrue(c_notDeprecated.is(NOT_DEPRECATED));
445 		assertTrue(c_isPublic.is(PUBLIC));
446 		assertTrue(c_isNotPublic.is(NOT_PUBLIC));
447 		assertTrue(c_isStatic.is(STATIC));
448 		assertTrue(c_isNotStatic.is(NOT_STATIC));
449 		assertTrue(c_isTransient.is(TRANSIENT));
450 		assertTrue(c_isNotTransient.is(NOT_TRANSIENT));
451 
452 		assertFalse(c_deprecated.is(NOT_DEPRECATED));
453 		assertFalse(c_notDeprecated.is(DEPRECATED));
454 		assertFalse(c_isPublic.is(NOT_PUBLIC));
455 		assertFalse(c_isNotPublic.is(PUBLIC));
456 		assertFalse(c_isStatic.is(NOT_STATIC));
457 		assertFalse(c_isNotStatic.is(STATIC));
458 		assertFalse(c_isTransient.is(NOT_TRANSIENT));
459 		assertFalse(c_isNotTransient.is(TRANSIENT));
460 		
461 		// Enum constant
462 		assertTrue(testEnum_value1.is(ENUM_CONSTANT));
463 		assertFalse(a1_f1.is(ENUM_CONSTANT));
464 		assertTrue(a1_f1.is(NOT_ENUM_CONSTANT));  // Line 313: true branch - field is NOT an enum constant
465 		assertFalse(testEnum_value1.is(NOT_ENUM_CONSTANT));  // Line 313: false branch - field IS an enum constant
466 		
467 		// Synthetic (lines 314-315)
468 		assertFalse(a1_f1.is(SYNTHETIC));
469 		assertTrue(a1_f1.is(NOT_SYNTHETIC));
470 		assertFalse(b_a1.is(SYNTHETIC));
471 		assertTrue(b_a1.is(NOT_SYNTHETIC));
472 		
473 		// HAS_PARAMS doesn't apply to fields, should throw exception
474 		assertThrowsWithMessage(RuntimeException.class, "Invalid flag for element: HAS_PARAMS", () -> c_deprecated.is(HAS_PARAMS));
475 	}
476 
477 	//====================================================================================================
478 	// isAccessible()
479 	//====================================================================================================
480 	@Test
481 	void a017_isAccessible() {
482 		// Test isAccessible() before and after setAccessible()
483 		var privateBefore = d_isPrivate.isAccessible();
484 		var protectedBefore = d_isProtected.isAccessible();
485 		var defaultBefore = d_isDefault.isAccessible();
486 		
487 		// Make them accessible
488 		d_isPrivate.setAccessible();
489 		d_isProtected.setAccessible();
490 		d_isDefault.setAccessible();
491 		
492 		// After setAccessible(), they should be accessible (if Java 9+)
493 		var privateAfter = d_isPrivate.isAccessible();
494 		var protectedAfter = d_isProtected.isAccessible();
495 		var defaultAfter = d_isDefault.isAccessible();
496 		
497 		// Verify the method doesn't throw and returns a boolean
498 		assertTrue(privateAfter || !privateBefore, "After setAccessible(), isAccessible() should return true (Java 9+) or false (Java 8)");
499 		assertTrue(protectedAfter || !protectedBefore, "After setAccessible(), isAccessible() should return true (Java 9+) or false (Java 8)");
500 		assertTrue(defaultAfter || !defaultBefore, "After setAccessible(), isAccessible() should return true (Java 9+) or false (Java 8)");
501 		
502 		// Public fields might already be accessible
503 		var publicAccessible = d_isPublic.isAccessible();
504 		assertNotNull(publicAccessible);
505 	}
506 
507 	//====================================================================================================
508 	// isAll(ElementFlag...)
509 	//====================================================================================================
510 	@Test
511 	void a018_isAll() {
512 		assertTrue(c_deprecated.isAll(DEPRECATED));
513 		assertTrue(c_isPublic.isAll(PUBLIC, NOT_PRIVATE));
514 		assertFalse(c_deprecated.isAll(DEPRECATED, NOT_DEPRECATED));
515 	}
516 
517 	//====================================================================================================
518 	// isAny(ElementFlag...)
519 	//====================================================================================================
520 	@Test
521 	void a019_isAny() {
522 		assertTrue(c_deprecated.isAny(DEPRECATED, NOT_DEPRECATED));
523 		assertTrue(c_isPublic.isAny(PUBLIC, PRIVATE));
524 		assertFalse(c_deprecated.isAny(NOT_DEPRECATED));
525 	}
526 
527 	//====================================================================================================
528 	// isDeprecated()
529 	//====================================================================================================
530 	@Test
531 	void a020_isDeprecated() {
532 		assertTrue(c_deprecated.isDeprecated());
533 		assertFalse(c_notDeprecated.isDeprecated());
534 	}
535 
536 	//====================================================================================================
537 	// isEnumConstant()
538 	//====================================================================================================
539 	@Test
540 	void a021_isEnumConstant() {
541 		assertTrue(testEnum_value1.isEnumConstant());
542 		assertTrue(testEnum_value2.isEnumConstant());
543 		assertFalse(a1_f1.isEnumConstant());
544 	}
545 
546 	//====================================================================================================
547 	// isNotDeprecated()
548 	//====================================================================================================
549 	@Test
550 	void a022_isNotDeprecated() {
551 		assertFalse(c_deprecated.isNotDeprecated());
552 		assertTrue(c_notDeprecated.isNotDeprecated());
553 	}
554 
555 	//====================================================================================================
556 	// isSynthetic()
557 	//====================================================================================================
558 	@Test
559 	void a023_isSynthetic() {
560 		// Regular fields are not synthetic
561 		assertFalse(a1_f1.isSynthetic());
562 	}
563 
564 	//====================================================================================================
565 	// isVisible(Visibility)
566 	//====================================================================================================
567 	@Test
568 	void a024_isVisible() {
569 		assertTrue(d_isPublic.isVisible(Visibility.PUBLIC));
570 		assertTrue(d_isPublic.isVisible(Visibility.PROTECTED));
571 		assertTrue(d_isPublic.isVisible(Visibility.PRIVATE));
572 		assertTrue(d_isPublic.isVisible(Visibility.DEFAULT));
573 
574 		assertFalse(d_isProtected.isVisible(Visibility.PUBLIC));
575 		assertTrue(d_isProtected.isVisible(Visibility.PROTECTED));
576 		assertTrue(d_isProtected.isVisible(Visibility.PRIVATE));
577 		assertTrue(d_isProtected.isVisible(Visibility.DEFAULT));
578 
579 		assertFalse(d_isPrivate.isVisible(Visibility.PUBLIC));
580 		assertFalse(d_isPrivate.isVisible(Visibility.PROTECTED));
581 		assertTrue(d_isPrivate.isVisible(Visibility.PRIVATE));
582 		assertFalse(d_isPrivate.isVisible(Visibility.DEFAULT));
583 
584 		assertFalse(d_isDefault.isVisible(Visibility.PUBLIC));
585 		assertFalse(d_isDefault.isVisible(Visibility.PROTECTED));
586 		assertTrue(d_isDefault.isVisible(Visibility.PRIVATE));
587 		assertTrue(d_isDefault.isVisible(Visibility.DEFAULT));
588 	}
589 
590 	//====================================================================================================
591 	// of(ClassInfo, Field)
592 	//====================================================================================================
593 	@Test
594 	void a025_of_withClass() {
595 		check("f1", FieldInfo.of(ClassInfo.of(A1.class), a1_f1.inner()));
596 	}
597 
598 	//====================================================================================================
599 	// of(Field)
600 	//====================================================================================================
601 	@Test
602 	void a026_of_withoutClass() {
603 		check("f1", FieldInfo.of(a1_f1.inner()));
604 		
605 		// Null should throw
606 		assertThrows(IllegalArgumentException.class, () -> FieldInfo.of((Field)null));
607 		assertThrows(IllegalArgumentException.class, () -> FieldInfo.of((ClassInfo)null, null));
608 	}
609 
610 	//====================================================================================================
611 	// set(Object, Object)
612 	//====================================================================================================
613 	@Test
614 	void a027_set() {
615 		var obj = new GetSetTest();
616 		
617 		getSetTest_value.set(obj, "newValue");
618 		assertEquals("newValue", obj.value);
619 		
620 		getSetTest_number.set(obj, 100);
621 		assertEquals(100, obj.number);
622 		
623 		// Set to null
624 		getSetTest_value.set(obj, null);
625 		assertNull(obj.value);
626 	}
627 
628 	//====================================================================================================
629 	// setAccessible()
630 	//====================================================================================================
631 	@Test
632 	void a028_setAccessible() {
633 		assertDoesNotThrow(()->d_isPublic.setAccessible());
634 		assertDoesNotThrow(()->d_isProtected.setAccessible());
635 		assertDoesNotThrow(()->d_isPrivate.setAccessible());
636 		assertDoesNotThrow(()->d_isDefault.setAccessible());
637 	}
638 
639 	//====================================================================================================
640 	// setIfNull(Object, Object)
641 	//====================================================================================================
642 	@Test
643 	void a029_setIfNull() {
644 		var obj = new GetSetTest();
645 		
646 		// Set when null
647 		obj.value = null;
648 		getSetTest_value.setIfNull(obj, "defaultValue");
649 		assertEquals("defaultValue", obj.value);
650 		
651 		// Don't set when not null
652 		obj.value = "existing";
653 		getSetTest_value.setIfNull(obj, "shouldNotSet");
654 		assertEquals("existing", obj.value);
655 	}
656 
657 	//====================================================================================================
658 	// toGenericString()
659 	//====================================================================================================
660 	@Test
661 	void a030_toGenericString() {
662 		var str = e_a1.toGenericString();
663 		assertNotNull(str);
664 		assertTrue(str.contains("int"));
665 		assertTrue(str.contains("a1"));
666 	}
667 
668 	//====================================================================================================
669 	// toString()
670 	//====================================================================================================
671 	@Test
672 	void a031_toString() {
673 		assertEquals("org.apache.juneau.commons.reflect.FieldInfo_Test$E.a1", e_a1.toString());
674 	}
675 
676 	//====================================================================================================
677 	// equals(Object) and hashCode()
678 	//====================================================================================================
679 	@Test
680 	void a032_equals_hashCode() throws Exception {
681 		// Get FieldInfo instances from the same Field
682 		Field f1 = EqualsTestClass.class.getField("field1");
683 		FieldInfo fi1a = FieldInfo.of(f1);
684 		FieldInfo fi1b = FieldInfo.of(f1);
685 		
686 		Field f2 = EqualsTestClass.class.getField("field2");
687 		FieldInfo fi2 = FieldInfo.of(f2);
688 
689 		// Same field should be equal
690 		assertEquals(fi1a, fi1b);
691 		assertEquals(fi1a.hashCode(), fi1b.hashCode());
692 		
693 		// Different fields should not be equal
694 		assertNotEquals(fi1a, fi2);
695 		assertNotEquals(fi1a, null);
696 		assertNotEquals(fi1a, "not a FieldInfo");
697 		
698 		// Reflexive
699 		assertEquals(fi1a, fi1a);
700 		
701 		// Symmetric
702 		assertEquals(fi1a, fi1b);
703 		assertEquals(fi1b, fi1a);
704 		
705 		// Transitive
706 		FieldInfo fi1c = FieldInfo.of(f1);
707 		assertEquals(fi1a, fi1b);
708 		assertEquals(fi1b, fi1c);
709 		assertEquals(fi1a, fi1c);
710 		
711 		// HashMap usage - same field should map to same value
712 		Map<FieldInfo, String> map = new HashMap<>();
713 		map.put(fi1a, "value1");
714 		assertEquals("value1", map.get(fi1b));
715 		assertEquals("value1", map.get(fi1c));
716 		
717 		// HashMap usage - different fields should map to different values
718 		map.put(fi2, "value2");
719 		assertEquals("value2", map.get(fi2));
720 		assertNotEquals("value2", map.get(fi1a));
721 		
722 		// HashSet usage
723 		Set<FieldInfo> set = new HashSet<>();
724 		set.add(fi1a);
725 		assertTrue(set.contains(fi1b));
726 		assertTrue(set.contains(fi1c));
727 		assertFalse(set.contains(fi2));
728 	}
729 }
730