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 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&lt;AnnotationInfo&lt;MyAnnotation&gt;&gt; <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&lt;String&gt; 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&lt;java.lang.String&gt; 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 }