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 org.apache.juneau.commons.reflect.ClassArrayFormat.*;
20 import static org.apache.juneau.commons.reflect.ClassNameFormat.*;
21 import static org.apache.juneau.commons.utils.AssertionUtils.*;
22 import static org.apache.juneau.commons.utils.CollectionUtils.*;
23 import static org.apache.juneau.commons.utils.ThrowableUtils.*;
24 import static org.apache.juneau.commons.utils.Utils.*;
25
26 import java.lang.annotation.*;
27 import java.lang.reflect.*;
28 import java.util.*;
29 import java.util.function.*;
30 import java.util.stream.*;
31
32 import org.apache.juneau.commons.function.*;
33 import org.apache.juneau.commons.utils.*;
34
35 /**
36 * Lightweight utility class for introspecting information about a Java field.
37 *
38 * <p>
39 * This class provides a convenient wrapper around {@link Field} that extends the standard Java reflection
40 * API with additional functionality for field introspection, annotation handling, and value access.
41 * It extends {@link AccessibleInfo} to provide {@link AccessibleObject} functionality for accessing
42 * private fields.
43 *
44 * <h5 class='section'>Features:</h5>
45 * <ul class='spaced-list'>
46 * <li>Field introspection - access field metadata, type, modifiers
47 * <li>Annotation support - get annotations declared on the field
48 * <li>Value access - get and set field values with type safety
49 * <li>Accessibility control - make private fields accessible
50 * <li>Thread-safe - instances are immutable and safe for concurrent access
51 * </ul>
52 *
53 * <h5 class='section'>Use Cases:</h5>
54 * <ul class='spaced-list'>
55 * <li>Introspecting field metadata for code generation or analysis
56 * <li>Accessing field values in beans or data objects
57 * <li>Finding annotations on fields
58 * <li>Working with field types and modifiers
59 * <li>Building frameworks that need to analyze or manipulate field values
60 * </ul>
61 *
62 * <h5 class='section'>Usage:</h5>
63 * <p class='bjava'>
64 * <jc>// Get FieldInfo from a class</jc>
65 * ClassInfo <jv>ci</jv> = ClassInfo.<jsm>of</jsm>(MyClass.<jk>class</jk>);
66 * FieldInfo <jv>field</jv> = <jv>ci</jv>.getField(<js>"myField"</js>);
67 *
68 * <jc>// Get field type</jc>
69 * ClassInfo <jv>type</jv> = <jv>field</jv>.getType();
70 *
71 * <jc>// Get annotations</jc>
72 * List<AnnotationInfo<MyAnnotation>> <jv>annotations</jv> =
73 * <jv>field</jv>.getAnnotations(MyAnnotation.<jk>class</jk>).toList();
74 *
75 * <jc>// Access field value</jc>
76 * MyClass <jv>obj</jv> = <jk>new</jk> MyClass();
77 * <jv>field</jv>.accessible(); <jc>// Make accessible if private</jc>
78 * Object <jv>value</jv> = <jv>field</jv>.get(<jv>obj</jv>);
79 * </p>
80 *
81 * <h5 class='section'>See Also:</h5><ul>
82 * <li class='jc'>{@link ClassInfo} - Class introspection
83 * <li class='jc'>{@link MethodInfo} - Method introspection
84 * <li class='jc'>{@link ConstructorInfo} - Constructor introspection
85 * <li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/JuneauCommonsReflection">Reflection Package</a>
86 * </ul>
87 */
88 public class FieldInfo extends AccessibleInfo implements Comparable<FieldInfo>, Annotatable {
89 /**
90 * Creates a FieldInfo wrapper for the specified field.
91 *
92 * <h5 class='section'>Example:</h5>
93 * <p class='bjava'>
94 * ClassInfo <jv>ci</jv> = ClassInfo.<jsm>of</jsm>(MyClass.<jk>class</jk>);
95 * Field <jv>f</jv> = MyClass.<jk>class</jk>.getField(<js>"myField"</js>);
96 * FieldInfo <jv>fi</jv> = FieldInfo.<jsm>of</jsm>(<jv>ci</jv>, <jv>f</jv>);
97 * </p>
98 *
99 * @param declaringClass The ClassInfo for the class that declares this field. Must not be <jk>null</jk>.
100 * @param inner The field being wrapped. Must not be <jk>null</jk>.
101 * @return A new FieldInfo object wrapping the field.
102 */
103 public static FieldInfo of(ClassInfo declaringClass, Field inner) {
104 assertArgNotNull("declaringClass", declaringClass);
105 return declaringClass.getField(inner);
106 }
107
108 /**
109 * Creates a FieldInfo wrapper for the specified field.
110 *
111 * <p>
112 * This convenience method automatically determines the declaring class from the field.
113 *
114 * <h5 class='section'>Example:</h5>
115 * <p class='bjava'>
116 * Field <jv>f</jv> = MyClass.<jk>class</jk>.getField(<js>"myField"</js>);
117 * FieldInfo <jv>fi</jv> = FieldInfo.<jsm>of</jsm>(<jv>f</jv>);
118 * </p>
119 *
120 * @param inner The field being wrapped. Must not be <jk>null</jk>.
121 * @return A new FieldInfo object wrapping the field.
122 */
123 public static FieldInfo of(Field inner) {
124 assertArgNotNull("inner", inner);
125 return ClassInfo.of(inner.getDeclaringClass()).getField(inner);
126 }
127
128 private final Field inner;
129 private final ClassInfo declaringClass;
130 private final Supplier<ClassInfo> type;
131 private final Supplier<List<AnnotationInfo<Annotation>>> annotations; // All annotations declared directly on this field.
132 private final Supplier<String> fullName; // Fully qualified field name (declaring-class.field-name).
133
134 /**
135 * Constructor.
136 *
137 * <p>
138 * Creates a new FieldInfo wrapper for the specified field. This constructor is protected
139 * and should not be called directly. Use the static factory methods {@link #of(Field)} or
140 * obtain FieldInfo instances from {@link ClassInfo#getField(Field)}.
141 *
142 * @param declaringClass The ClassInfo for the class that declares this field.
143 * @param inner The field being wrapped.
144 */
145 protected FieldInfo(ClassInfo declaringClass, Field inner) {
146 super(inner, inner.getModifiers());
147 assertArgNotNull("inner", inner);
148 this.declaringClass = declaringClass;
149 this.inner = inner;
150 this.type = mem(() -> ClassInfo.of(inner.getType(), inner.getGenericType()));
151 this.annotations = mem(() -> stream(inner.getAnnotations()).flatMap(a -> AnnotationUtils.streamRepeated(a)).map(a -> ai(this, a)).toList());
152 this.fullName = mem(this::findFullName);
153 }
154
155 /**
156 * Attempts to call <code>x.setAccessible(<jk>true</jk>)</code> and quietly ignores security exceptions.
157 *
158 * @return This object.
159 */
160 public FieldInfo accessible() {
161 setAccessible();
162 return this;
163 }
164
165 @Override
166 public int compareTo(FieldInfo o) {
167 return cmp(getName(), o.getName());
168 }
169
170 /**
171 * Returns the field value on the specified object.
172 *
173 * @param o The object containing the field.
174 * @param <T> The object type to retrieve.
175 * @return The field value.
176 * @throws BeanRuntimeException Field was not accessible or field does not belong to object.
177 */
178 @SuppressWarnings("unchecked")
179 public <T> T get(Object o) throws BeanRuntimeException {
180 return safe(() -> {
181 inner.setAccessible(true);
182 return (T)inner.get(o);
183 }, e -> bex(e));
184 }
185
186 @Override /* Annotatable */
187 public AnnotatableType getAnnotatableType() { return AnnotatableType.FIELD_TYPE; }
188
189 /**
190 * Returns an {@link AnnotatedType} object that represents the use of a type to specify the declared type of the field.
191 *
192 * <p>
193 * Same as calling {@link Field#getAnnotatedType()}.
194 *
195 * <h5 class='section'>Example:</h5>
196 * <p class='bjava'>
197 * <jc>// Get annotated type: @NotNull String name</jc>
198 * FieldInfo <jv>fi</jv> = ClassInfo.<jsm>of</jsm>(MyClass.<jk>class</jk>).getField(<js>"name"</js>);
199 * AnnotatedType <jv>aType</jv> = <jv>fi</jv>.getAnnotatedType();
200 * <jc>// Check for @NotNull on the type</jc>
201 * </p>
202 *
203 * @return An {@link AnnotatedType} object representing the declared type.
204 * @see Field#getAnnotatedType()
205 */
206 public AnnotatedType getAnnotatedType() { return inner.getAnnotatedType(); }
207
208 /**
209 * Returns all annotations declared on this field.
210 *
211 * <p>
212 * <b>Note on Repeatable Annotations:</b>
213 * Repeatable annotations (those marked with {@link java.lang.annotation.Repeatable @Repeatable}) are automatically
214 * expanded into their individual annotation instances. For example, if a field has multiple {@code @Bean} annotations,
215 * this method returns each {@code @Bean} annotation separately, rather than the container annotation.
216 *
217 * @return
218 * An unmodifiable list of all annotations declared on this field.
219 * <br>Repeatable annotations are expanded into individual instances.
220 */
221 public List<AnnotationInfo<Annotation>> getAnnotations() { return annotations.get(); }
222
223 /**
224 * Returns all annotations of the specified type declared on this field.
225 *
226 * @param <A> The annotation type.
227 * @param type The annotation type.
228 * @return A stream of all matching annotations.
229 */
230 @SuppressWarnings("unchecked")
231 public <A extends Annotation> Stream<AnnotationInfo<A>> getAnnotations(Class<A> type) {
232 return annotations.get().stream().filter(x -> type.isInstance(x.inner())).map(x -> (AnnotationInfo<A>)x);
233 }
234
235 /**
236 * Returns metadata about the declaring class.
237 *
238 * @return Metadata about the declaring class.
239 */
240 public ClassInfo getDeclaringClass() { return declaringClass; }
241
242 /**
243 * Returns the type of this field.
244 *
245 * @return The type of this field.
246 */
247 public ClassInfo getFieldType() { return type.get(); }
248
249 /**
250 * Returns the full name of this field.
251 *
252 * <h5 class='section'>Examples:</h5>
253 * <ul>
254 * <li><js>"com.foo.MyClass.myField"</js> - Method.
255 * </ul>
256 *
257 * @return The underlying executable name.
258 */
259 public String getFullName() { return fullName.get(); }
260
261 @Override /* Annotatable */
262 public String getLabel() { return getDeclaringClass().getNameSimple() + "." + getName(); }
263
264 /**
265 * Returns the name of this field.
266 *
267 * @return The name of this field.
268 */
269 public String getName() { return inner.getName(); }
270
271 /**
272 * Returns <jk>true</jk> if the specified annotation is present.
273 *
274 * @param <A> The annotation type to look for.
275 * @param type The annotation to look for.
276 * @return <jk>true</jk> if the specified annotation is present.
277 */
278 public <A extends Annotation> boolean hasAnnotation(Class<A> type) {
279 return getAnnotations(type).findAny().isPresent();
280 }
281
282 /**
283 * Returns <jk>true</jk> if the field has the specified name.
284 *
285 * @param name The name to compare against.
286 * @return <jk>true</jk> if the field has the specified name.
287 */
288 public boolean hasName(String name) {
289 return inner.getName().equals(name);
290 }
291
292 /**
293 * Returns the wrapped field.
294 *
295 * @return The wrapped field.
296 */
297 public Field inner() {
298 return inner;
299 }
300
301 /**
302 * Compares this FieldInfo with the specified object for equality.
303 *
304 * <p>
305 * Two FieldInfo objects are considered equal if they wrap the same underlying {@link Field} object.
306 * This delegates to the underlying {@link Field#equals(Object)} method.
307 *
308 * <p>
309 * This method makes FieldInfo suitable for use as keys in hash-based collections such as {@link HashMap}
310 * and {@link HashSet}.
311 *
312 * @param obj The object to compare with.
313 * @return <jk>true</jk> if the objects are equal, <jk>false</jk> otherwise.
314 */
315 @Override
316 public boolean equals(Object obj) {
317 return obj instanceof FieldInfo other && eq(this, other, (x, y) -> eq(x.inner, y.inner));
318 }
319
320 /**
321 * Returns a hash code value for this FieldInfo.
322 *
323 * <p>
324 * This delegates to the underlying {@link Field#hashCode()} method.
325 *
326 * <p>
327 * This method makes FieldInfo suitable for use as keys in hash-based collections such as {@link HashMap}
328 * and {@link HashSet}.
329 *
330 * @return A hash code value for this FieldInfo.
331 */
332 @Override
333 public int hashCode() {
334 return inner.hashCode();
335 }
336
337 /**
338 * Returns <jk>true</jk> if all specified flags are applicable to this field.
339 *
340 * @param flag The flag to test for.
341 * @return <jk>true</jk> if all specified flags are applicable to this field.
342 */
343 @Override
344 public boolean is(ElementFlag flag) {
345 return switch (flag) {
346 case DEPRECATED -> isDeprecated();
347 case NOT_DEPRECATED -> isNotDeprecated();
348 case ENUM_CONSTANT -> isEnumConstant();
349 case NOT_ENUM_CONSTANT -> ! isEnumConstant();
350 case SYNTHETIC -> isSynthetic();
351 case NOT_SYNTHETIC -> ! isSynthetic(); // HTT
352 default -> super.is(flag);
353 };
354 }
355
356 /**
357 * Returns <jk>true</jk> if this field has the {@link Deprecated @Deprecated} annotation on it.
358 *
359 * @return <jk>true</jk> if this field has the {@link Deprecated @Deprecated} annotation on it.
360 */
361 public boolean isDeprecated() { return inner.isAnnotationPresent(Deprecated.class); }
362
363 /**
364 * Returns <jk>true</jk> if this field represents an element of an enumerated type.
365 *
366 * <p>
367 * Same as calling {@link Field#isEnumConstant()}.
368 *
369 * <h5 class='section'>Example:</h5>
370 * <p class='bjava'>
371 * <jc>// Check if field is an enum constant</jc>
372 * FieldInfo <jv>fi</jv> = ClassInfo.<jsm>of</jsm>(MyEnum.<jk>class</jk>).getField(<js>"VALUE1"</js>);
373 * <jk>if</jk> (<jv>fi</jv>.isEnumConstant()) {
374 * <jc>// Handle enum constant</jc>
375 * }
376 * </p>
377 *
378 * @return <jk>true</jk> if this field represents an enum constant.
379 * @see Field#isEnumConstant()
380 */
381 public boolean isEnumConstant() { return inner.isEnumConstant(); }
382
383 /**
384 * Returns <jk>true</jk> if this field doesn't have the {@link Deprecated @Deprecated} annotation on it.
385 *
386 * @return <jk>true</jk> if this field doesn't have the {@link Deprecated @Deprecated} annotation on it.
387 */
388 public boolean isNotDeprecated() { return ! inner.isAnnotationPresent(Deprecated.class); }
389
390 /**
391 * Returns <jk>true</jk> if this field is a synthetic field as defined by the Java Language Specification.
392 *
393 * <p>
394 * Same as calling {@link Field#isSynthetic()}.
395 *
396 * <h5 class='section'>Example:</h5>
397 * <p class='bjava'>
398 * <jc>// Filter out compiler-generated fields</jc>
399 * FieldInfo <jv>fi</jv> = ...;
400 * <jk>if</jk> (! <jv>fi</jv>.isSynthetic()) {
401 * <jc>// Process real field</jc>
402 * }
403 * </p>
404 *
405 * @return <jk>true</jk> if this field is a synthetic field.
406 * @see Field#isSynthetic()
407 */
408 public boolean isSynthetic() { return inner.isSynthetic(); }
409
410 /**
411 * Identifies if the specified visibility matches this field.
412 *
413 * @param v The visibility to validate against.
414 * @return <jk>true</jk> if this visibility matches the modifier attribute of this field.
415 */
416 public boolean isVisible(Visibility v) {
417 return v.isVisible(inner);
418 }
419
420 /**
421 * Sets the field value on the specified object.
422 *
423 * @param o The object containing the field.
424 * @param value The new field value.
425 * @throws BeanRuntimeException Field was not accessible or field does not belong to object.
426 */
427 public void set(Object o, Object value) throws BeanRuntimeException {
428 safe((Snippet)() -> {
429 inner.setAccessible(true);
430 inner.set(o, value);
431 }, e -> bex(e));
432 }
433
434 //-----------------------------------------------------------------------------------------------------------------
435 // Field-Specific Methods
436 //-----------------------------------------------------------------------------------------------------------------
437
438 /**
439 * Sets the field value on the specified object if the value is <jk>null</jk>.
440 *
441 * @param o The object containing the field.
442 * @param value The new field value.
443 * @throws BeanRuntimeException Field was not accessible or field does not belong to object.
444 */
445 public void setIfNull(Object o, Object value) {
446 Object v = get(o);
447 if (v == null)
448 set(o, value);
449 }
450
451 /**
452 * Returns a string describing this field, including its generic type.
453 *
454 * <p>
455 * Same as calling {@link Field#toGenericString()}.
456 *
457 * <h5 class='section'>Example:</h5>
458 * <p class='bjava'>
459 * <jc>// Get generic string for: public static final List<String> VALUES</jc>
460 * FieldInfo <jv>fi</jv> = ClassInfo.<jsm>of</jsm>(MyClass.<jk>class</jk>).getField(<js>"VALUES"</js>);
461 * String <jv>str</jv> = <jv>fi</jv>.toGenericString();
462 * <jc>// Returns: "public static final java.util.List<java.lang.String> com.example.MyClass.VALUES"</jc>
463 * </p>
464 *
465 * @return A string describing this field.
466 * @see Field#toGenericString()
467 */
468 public String toGenericString() {
469 return inner.toGenericString();
470 }
471
472 //-----------------------------------------------------------------------------------------------------------------
473 // Annotatable interface methods
474 //-----------------------------------------------------------------------------------------------------------------
475
476 @Override
477 public String toString() {
478 return cn(inner.getDeclaringClass()) + "." + inner.getName();
479 }
480
481 private String findFullName() {
482 var sb = new StringBuilder(128);
483 var dc = declaringClass;
484 var pi = dc.getPackage();
485 if (nn(pi))
486 sb.append(pi.getName()).append('.');
487 // HTT - false branch (pi == null) is hard to test: some classloaders return Package objects
488 // even for default package classes, though Java API spec says it should return null
489 dc.appendNameFormatted(sb, SHORT, true, '$', BRACKETS);
490 sb.append('.').append(getName());
491 return sb.toString();
492 }
493 }