001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.juneau.commons.reflect;
018
019import static org.apache.juneau.commons.reflect.ClassArrayFormat.*;
020import static org.apache.juneau.commons.reflect.ClassNameFormat.*;
021import static org.apache.juneau.commons.utils.AssertionUtils.*;
022import static org.apache.juneau.commons.utils.CollectionUtils.*;
023import static org.apache.juneau.commons.utils.ThrowableUtils.*;
024import static org.apache.juneau.commons.utils.Utils.*;
025
026import java.lang.annotation.*;
027import java.lang.reflect.*;
028import java.util.*;
029import java.util.function.*;
030import java.util.stream.*;
031
032import org.apache.juneau.commons.function.*;
033import org.apache.juneau.commons.utils.*;
034
035/**
036 * Lightweight utility class for introspecting information about a Java field.
037 *
038 * <p>
039 * This class provides a convenient wrapper around {@link Field} that extends the standard Java reflection
040 * API with additional functionality for field introspection, annotation handling, and value access.
041 * It extends {@link AccessibleInfo} to provide {@link AccessibleObject} functionality for accessing
042 * private fields.
043 *
044 * <h5 class='section'>Features:</h5>
045 * <ul class='spaced-list'>
046 *    <li>Field introspection - access field metadata, type, modifiers
047 *    <li>Annotation support - get annotations declared on the field
048 *    <li>Value access - get and set field values with type safety
049 *    <li>Accessibility control - make private fields accessible
050 *    <li>Thread-safe - instances are immutable and safe for concurrent access
051 * </ul>
052 *
053 * <h5 class='section'>Use Cases:</h5>
054 * <ul class='spaced-list'>
055 *    <li>Introspecting field metadata for code generation or analysis
056 *    <li>Accessing field values in beans or data objects
057 *    <li>Finding annotations on fields
058 *    <li>Working with field types and modifiers
059 *    <li>Building frameworks that need to analyze or manipulate field values
060 * </ul>
061 *
062 * <h5 class='section'>Usage:</h5>
063 * <p class='bjava'>
064 *    <jc>// Get FieldInfo from a class</jc>
065 *    ClassInfo <jv>ci</jv> = ClassInfo.<jsm>of</jsm>(MyClass.<jk>class</jk>);
066 *    FieldInfo <jv>field</jv> = <jv>ci</jv>.getField(<js>"myField"</js>);
067 *
068 *    <jc>// Get field type</jc>
069 *    ClassInfo <jv>type</jv> = <jv>field</jv>.getType();
070 *
071 *    <jc>// Get annotations</jc>
072 *    List&lt;AnnotationInfo&lt;MyAnnotation&gt;&gt; <jv>annotations</jv> =
073 *       <jv>field</jv>.getAnnotations(MyAnnotation.<jk>class</jk>).toList();
074 *
075 *    <jc>// Access field value</jc>
076 *    MyClass <jv>obj</jv> = <jk>new</jk> MyClass();
077 *    <jv>field</jv>.accessible();  <jc>// Make accessible if private</jc>
078 *    Object <jv>value</jv> = <jv>field</jv>.get(<jv>obj</jv>);
079 * </p>
080 *
081 * <h5 class='section'>See Also:</h5><ul>
082 *    <li class='jc'>{@link ClassInfo} - Class introspection
083 *    <li class='jc'>{@link MethodInfo} - Method introspection
084 *    <li class='jc'>{@link ConstructorInfo} - Constructor introspection
085 *    <li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/JuneauCommonsReflection">Reflection Package</a>
086 * </ul>
087 */
088public class FieldInfo extends AccessibleInfo implements Comparable<FieldInfo>, Annotatable {
089   /**
090    * Creates a FieldInfo wrapper for the specified field.
091    *
092    * <h5 class='section'>Example:</h5>
093    * <p class='bjava'>
094    *    ClassInfo <jv>ci</jv> = ClassInfo.<jsm>of</jsm>(MyClass.<jk>class</jk>);
095    *    Field <jv>f</jv> = MyClass.<jk>class</jk>.getField(<js>"myField"</js>);
096    *    FieldInfo <jv>fi</jv> = FieldInfo.<jsm>of</jsm>(<jv>ci</jv>, <jv>f</jv>);
097    * </p>
098    *
099    * @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}