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.function.Suppliers.*;
020import static org.apache.juneau.commons.reflect.ClassArrayFormat.*;
021import static org.apache.juneau.commons.reflect.ClassNameFormat.*;
022import static org.apache.juneau.commons.utils.CollectionUtils.*;
023import static org.apache.juneau.commons.utils.Shorts.*;
024import static org.apache.juneau.commons.utils.ThrowableUtils.*;
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.inject.*;
034import org.apache.juneau.commons.utils.*;
035
036/**
037 * Lightweight utility class for introspecting information about a Java field.
038 *
039 * <p>
040 * This class provides a convenient wrapper around {@link Field} that extends the standard Java reflection
041 * API with additional functionality for field introspection, annotation handling, and value access.
042 * It extends {@link AccessibleInfo} to provide {@link AccessibleObject} functionality for accessing
043 * private fields.
044 *
045 * <h5 class='section'>Features:</h5>
046 * <ul class='spaced-list'>
047 *    <li>Field introspection - access field metadata, type, modifiers
048 *    <li>Annotation support - get annotations declared on the field
049 *    <li>Value access - get and set field values with type safety
050 *    <li>Accessibility control - make private fields accessible
051 *    <li>Thread-safe - instances are immutable and safe for concurrent access
052 * </ul>
053 *
054 * <h5 class='section'>Use Cases:</h5>
055 * <ul class='spaced-list'>
056 *    <li>Introspecting field metadata for code generation or analysis
057 *    <li>Accessing field values in beans or data objects
058 *    <li>Finding annotations on fields
059 *    <li>Working with field types and modifiers
060 *    <li>Building frameworks that need to analyze or manipulate field values
061 * </ul>
062 *
063 * <h5 class='section'>Usage:</h5>
064 * <p class='bjava'>
065 *    <jc>// Get FieldInfo from a class</jc>
066 *    ClassInfo <jv>ci</jv> = ClassInfo.<jsm>of</jsm>(MyClass.<jk>class</jk>);
067 *    FieldInfo <jv>field</jv> = <jv>ci</jv>.getField(<js>"myField"</js>);
068 *
069 *    <jc>// Get field type</jc>
070 *    ClassInfo <jv>type</jv> = <jv>field</jv>.getType();
071 *
072 *    <jc>// Get annotations</jc>
073 *    List&lt;AnnotationInfo&lt;MyAnnotation&gt;&gt; <jv>annotations</jv> =
074 *       <jv>field</jv>.getAnnotations(MyAnnotation.<jk>class</jk>).toList();
075 *
076 *    <jc>// Access field value</jc>
077 *    MyClass <jv>obj</jv> = <jk>new</jk> MyClass();
078 *    <jv>field</jv>.accessible();  <jc>// Make accessible if private</jc>
079 *    Object <jv>value</jv> = <jv>field</jv>.get(<jv>obj</jv>);
080 * </p>
081 *
082 * <h5 class='section'>See Also:</h5><ul>
083 *    <li class='jc'>{@link ClassInfo} - Class introspection
084 *    <li class='jc'>{@link MethodInfo} - Method introspection
085 *    <li class='jc'>{@link ConstructorInfo} - Constructor introspection
086 *    <li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/JuneauCommonsReflection">Reflection Package</a>
087 * </ul>
088 */
089@SuppressWarnings({
090   "java:S1192", // Duplicated literals (argument/property names) read more clearly inline than as constants
091   "java:S3011", // Reflection access needed for field introspection
092   "unchecked" // (T)inner.get(o) and the (AnnotationInfo<A>) cast are guarded by the caller-supplied type / type.isInstance filter
093})
094public final class FieldInfo extends AccessibleInfo implements Comparable<FieldInfo>, Annotatable {
095
096   /**
097    * Creates a FieldInfo wrapper for the specified field.
098    *
099    * <h5 class='section'>Example:</h5>
100    * <p class='bjava'>
101    *    ClassInfo <jv>ci</jv> = ClassInfo.<jsm>of</jsm>(MyClass.<jk>class</jk>);
102    *    Field <jv>f</jv> = MyClass.<jk>class</jk>.getField(<js>"myField"</js>);
103    *    FieldInfo <jv>fi</jv> = FieldInfo.<jsm>of</jsm>(<jv>ci</jv>, <jv>f</jv>);
104    * </p>
105    *
106    * @param declaringClass The ClassInfo for the class that declares this field. Must not be <jk>null</jk>.
107    * @param inner The field being wrapped. Must not be <jk>null</jk>.
108    * @return A new FieldInfo object wrapping the field.
109    */
110   public static FieldInfo of(ClassInfo declaringClass, Field inner) {
111      reqnn("declaringClass", declaringClass);
112      return declaringClass.getField(inner);
113   }
114
115   /**
116    * Creates a FieldInfo wrapper for the specified field.
117    *
118    * <p>
119    * This convenience method automatically determines the declaring class from the field.
120    *
121    * <h5 class='section'>Example:</h5>
122    * <p class='bjava'>
123    *    Field <jv>f</jv> = MyClass.<jk>class</jk>.getField(<js>"myField"</js>);
124    *    FieldInfo <jv>fi</jv> = FieldInfo.<jsm>of</jsm>(<jv>f</jv>);
125    * </p>
126    *
127    * @param inner The field being wrapped. Must not be <jk>null</jk>.
128    * @return A new FieldInfo object wrapping the field.
129    */
130   public static FieldInfo of(Field inner) {
131      reqnn("inner", inner);
132      return ClassInfo.of(inner.getDeclaringClass()).getField(inner);
133   }
134
135   private final Field inner;
136   private final ClassInfo declaringClass;
137   private final Supplier<ClassInfo> type;
138   private final Supplier<List<AnnotationInfo<Annotation>>> annotations;  // All annotations declared directly on this field.
139   private final Supplier<String> nameFull;  // Fully qualified field name (declaring-class.field-name).
140   private final Supplier<String> toString;  // String representation with modifiers, type, and full name.
141
142   /**
143    * Constructor.
144    *
145    * <p>
146    * Creates a new FieldInfo wrapper for the specified field. This constructor is protected
147    * and should not be called directly. Use the static factory methods {@link #of(Field)} or
148    * obtain FieldInfo instances from {@link ClassInfo#getField(Field)}.
149    *
150    * @param declaringClass The ClassInfo for the class that declares this field. Must not be <jk>null</jk>.
151    * @param inner The field being wrapped. Must not be <jk>null</jk>.
152    */
153   protected FieldInfo(ClassInfo declaringClass, Field inner) {
154      super(inner, inner.getModifiers());
155      reqnn("inner", inner);
156      this.declaringClass = declaringClass;
157      this.inner = inner;
158      this.type = memoize(() -> ClassInfo.of(inner.getType(), inner.getGenericType()));
159      this.annotations = memoize(() -> stream(inner.getAnnotations()).flatMap(AnnotationUtils::streamRepeated).map(a -> ai(this, a)).toList());
160      this.nameFull = memoize(this::findNameFull);
161      this.toString = memoize(this::findToString);
162   }
163
164   /**
165    * Attempts to call <code>x.setAccessible(<jk>true</jk>)</code> and quietly ignores security exceptions.
166    *
167    * @return This object.
168    */
169   public FieldInfo accessible() {
170      setAccessible();
171      return this;
172   }
173
174   @Override
175   public int compareTo(FieldInfo o) {
176      return cmp(getName(), o.getName());
177   }
178
179   /**
180    * Returns the field value on the specified object.
181    *
182    * <p>
183    * If the underlying field is static, then the specified <c>o</c> argument is ignored. It may be <jk>null</jk>.
184    *
185    * @param <T> The object type to retrieve.
186    * @param o The object containing the field.  Can be <jk>null</jk> for static fields.
187    * @return The field value.
188    * @throws BeanRuntimeException Field was not accessible or field does not belong to object.
189    */
190   public <T> T get(Object o) throws BeanRuntimeException {
191      return safe(() -> {
192         inner.setAccessible(true);
193         return (T)inner.get(o);
194      }, Shorts::brex);
195   }
196
197   @Override /* Annotatable */
198   public AnnotatableType getAnnotatableType() { return AnnotatableType.FIELD_TYPE; }
199
200   /**
201    * Returns an {@link AnnotatedType} object that represents the use of a type to specify the declared type of the field.
202    *
203    * <p>
204    * Same as calling {@link Field#getAnnotatedType()}.
205    *
206    * <h5 class='section'>Example:</h5>
207    * <p class='bjava'>
208    *    <jc>// Get annotated type: @NotNull String name</jc>
209    *    FieldInfo <jv>fi</jv> = ClassInfo.<jsm>of</jsm>(MyClass.<jk>class</jk>).getField(<js>"name"</js>);
210    *    AnnotatedType <jv>aType</jv> = <jv>fi</jv>.getAnnotatedType();
211    *    <jc>// Check for @NotNull on the type</jc>
212    * </p>
213    *
214    * @return An {@link AnnotatedType} object representing the declared type.
215    * @see Field#getAnnotatedType()
216    */
217   public AnnotatedType getAnnotatedType() { return inner.getAnnotatedType(); }
218
219   /**
220    * Returns all annotations declared on this field.
221    *
222    * <p>
223    * <b>Note on Repeatable Annotations:</b>
224    * Repeatable annotations (those marked with {@link Repeatable @Repeatable}) are automatically
225    * expanded into their individual annotation instances. For example, if a field has multiple {@code @Marshalled} annotations,
226    * this method returns each {@code @Marshalled} annotation separately, rather than the container annotation.
227    *
228    * @return
229    *    An unmodifiable list of all annotations declared on this field.
230    *    <br>Repeatable annotations are expanded into individual instances.
231    */
232   public List<AnnotationInfo<Annotation>> getAnnotations() { return annotations.get(); }
233
234   /**
235    * Returns all annotations of the specified type declared on this field.
236    *
237    * @param <A> The annotation type.
238    * @param type The annotation type.
239    * @return A stream of all matching annotations.
240    */
241   public <A extends Annotation> Stream<AnnotationInfo<A>> getAnnotations(Class<A> type) {
242      return annotations.get().stream().filter(x -> type.isInstance(x.inner())).map(x -> (AnnotationInfo<A>)x);
243   }
244
245   /**
246    * Returns metadata about the declaring class.
247    *
248    * @return Metadata about the declaring class.
249    */
250   public ClassInfo getDeclaringClass() { return declaringClass; }
251
252   /**
253    * Returns the type of this field.
254    *
255    * @return The type of this field.
256    */
257   public ClassInfo getFieldType() { return type.get(); }
258
259   /**
260    * Returns the full name of this field.
261    *
262    * <h5 class='section'>Examples:</h5>
263    * <ul>
264    *    <li><js>"com.foo.MyClass.myField"</js> - Method.
265    * </ul>
266    *
267    * @return The underlying executable name.
268    */
269   public String getNameFull() { return nameFull.get(); }
270
271   @Override /* Annotatable */
272   public String getLabel() { return getDeclaringClass().getNameSimple() + "." + getName(); }
273
274   /**
275    * Returns the name of this field.
276    *
277    * @return The name of this field.
278    */
279   public String getName() { return inner.getName(); }
280
281   /**
282    * Returns <jk>true</jk> if the specified annotation is present.
283    *
284    * @param <A> The annotation type to look for.
285    * @param type The annotation to look for.
286    * @return <jk>true</jk> if the specified annotation is present.
287    */
288   public <A extends Annotation> boolean hasAnnotation(Class<A> type) {
289      return getAnnotations(type).findAny().isPresent();
290   }
291
292   /**
293    * Returns <jk>true</jk> if the field has the specified name.
294    *
295    * @param name The name to compare against.
296    * @return <jk>true</jk> if the field has the specified name.
297    */
298   public boolean hasName(String name) {
299      return inner.getName().equals(name);
300   }
301
302   /**
303    * Returns the wrapped field.
304    *
305    * @return The wrapped field.
306    */
307   public Field inner() {
308      return inner;
309   }
310
311   /**
312    * Compares this FieldInfo with the specified object for equality.
313    *
314    * <p>
315    * Two FieldInfo objects are considered equal if they wrap the same underlying {@link Field} object.
316    * This delegates to the underlying {@link Field#equals(Object)} method.
317    *
318    * <p>
319    * This method makes FieldInfo suitable for use as keys in hash-based collections such as {@link HashMap}
320    * and {@link HashSet}.
321    *
322    * @param obj The object to compare with.
323    * @return <jk>true</jk> if the objects are equal, <jk>false</jk> otherwise.
324    */
325   @Override
326   public boolean equals(Object obj) {
327      return obj instanceof FieldInfo obj2 && eq(this, obj2, (x, y) -> eq(x.inner, y.inner));
328   }
329
330   /**
331    * Returns a hash code value for this FieldInfo.
332    *
333    * <p>
334    * This delegates to the underlying {@link Field#hashCode()} method.
335    *
336    * <p>
337    * This method makes FieldInfo suitable for use as keys in hash-based collections such as {@link HashMap}
338    * and {@link HashSet}.
339    *
340    * @return A hash code value for this FieldInfo.
341    */
342   @Override
343   public int hashCode() {
344      return inner.hashCode();
345   }
346
347   /**
348    * Returns <jk>true</jk> if all specified flags are applicable to this field.
349    *
350    * @param flag The flag to test for.
351    * @return <jk>true</jk> if all specified flags are applicable to this field.
352    */
353   @Override
354   public boolean is(ElementFlag flag) {
355      return switch (flag) {
356         case DEPRECATED -> isDeprecated();
357         case NOT_DEPRECATED -> isNotDeprecated();
358         case ENUM_CONSTANT -> isEnumConstant();
359         case NOT_ENUM_CONSTANT -> ! isEnumConstant();
360         case SYNTHETIC -> isSynthetic();
361         case NOT_SYNTHETIC -> ! isSynthetic();  // HTT
362         default -> super.is(flag);
363      };
364   }
365
366   /**
367    * Returns <jk>true</jk> if this field has the {@link Deprecated @Deprecated} annotation on it.
368    *
369    * @return <jk>true</jk> if this field has the {@link Deprecated @Deprecated} annotation on it.
370    */
371   public boolean isDeprecated() { return inner.isAnnotationPresent(Deprecated.class); }
372
373   /**
374    * Returns <jk>true</jk> if this field represents an element of an enumerated type.
375    *
376    * <p>
377    * Same as calling {@link Field#isEnumConstant()}.
378    *
379    * <h5 class='section'>Example:</h5>
380    * <p class='bjava'>
381    *    <jc>// Check if field is an enum constant</jc>
382    *    FieldInfo <jv>fi</jv> = ClassInfo.<jsm>of</jsm>(MyEnum.<jk>class</jk>).getField(<js>"VALUE1"</js>);
383    *    <jk>if</jk> (<jv>fi</jv>.isEnumConstant()) {
384    *       <jc>// Handle enum constant</jc>
385    *    }
386    * </p>
387    *
388    * @return <jk>true</jk> if this field represents an enum constant.
389    * @see Field#isEnumConstant()
390    */
391   public boolean isEnumConstant() { return inner.isEnumConstant(); }
392
393   /**
394    * Returns <jk>true</jk> if this field doesn't have the {@link Deprecated @Deprecated} annotation on it.
395    *
396    * @return <jk>true</jk> if this field doesn't have the {@link Deprecated @Deprecated} annotation on it.
397    */
398   public boolean isNotDeprecated() { return ! inner.isAnnotationPresent(Deprecated.class); }
399
400   /**
401    * Returns <jk>true</jk> if this field is a synthetic field as defined by the Java Language Specification.
402    *
403    * <p>
404    * Same as calling {@link Field#isSynthetic()}.
405    *
406    * <h5 class='section'>Example:</h5>
407    * <p class='bjava'>
408    *    <jc>// Filter out compiler-generated fields</jc>
409    *    FieldInfo <jv>fi</jv> = ...;
410    *    <jk>if</jk> (! <jv>fi</jv>.isSynthetic()) {
411    *       <jc>// Process real field</jc>
412    *    }
413    * </p>
414    *
415    * @return <jk>true</jk> if this field is a synthetic field.
416    * @see Field#isSynthetic()
417    */
418   public boolean isSynthetic() { return inner.isSynthetic(); }
419
420   /**
421    * Identifies if the specified visibility matches this field.
422    *
423    * @param v The visibility to validate against.
424    * @return <jk>true</jk> if this visibility matches the modifier attribute of this field.
425    */
426   public boolean isVisible(Visibility v) {
427      return v.isVisible(inner);
428   }
429
430   /**
431    * Sets the field value on the specified object.
432    *
433    * <p>
434    * If the underlying field is static, then the specified <c>o</c> argument is ignored. It may be <jk>null</jk>.
435    *
436    * @param o The object containing the field.  Can be <jk>null</jk> for static fields.
437    * @param value The new field value.
438    * @throws BeanRuntimeException Field was not accessible or field does not belong to object.
439    */
440   public void set(Object o, Object value) throws BeanRuntimeException {
441      safe((Snippet)() -> {
442         inner.setAccessible(true);
443         inner.set(o, value);
444      }, Shorts::brex);
445   }
446
447   //-----------------------------------------------------------------------------------------------------------------
448   // Field-Specific Methods
449   //-----------------------------------------------------------------------------------------------------------------
450
451   /**
452    * Sets the field value on the specified object if the value is <jk>null</jk>.
453    *
454    * @param o The object containing the field.  Can be <jk>null</jk> for static fields.
455    * @param value The new field value.
456    * @throws BeanRuntimeException Field was not accessible or field does not belong to object.
457    */
458   public void setIfNull(Object o, Object value) {
459      Object v = get(o);
460      if (v == null)
461         set(o, value);
462   }
463
464   /**
465    * Returns a string describing this field, including its generic type.
466    *
467    * <p>
468    * Same as calling {@link Field#toGenericString()}.
469    *
470    * <h5 class='section'>Example:</h5>
471    * <p class='bjava'>
472    *    <jc>// Get generic string for: public static final List&lt;String&gt; VALUES</jc>
473    *    FieldInfo <jv>fi</jv> = ClassInfo.<jsm>of</jsm>(MyClass.<jk>class</jk>).getField(<js>"VALUES"</js>);
474    *    String <jv>str</jv> = <jv>fi</jv>.toGenericString();
475    *    <jc>// Returns: "public static final java.util.List&lt;java.lang.String&gt; com.example.MyClass.VALUES"</jc>
476    * </p>
477    *
478    * @return A string describing this field.
479    * @see Field#toGenericString()
480    */
481   public String toGenericString() {
482      return inner.toGenericString();
483   }
484
485   //-----------------------------------------------------------------------------------------------------------------
486   // Annotatable interface methods
487   //-----------------------------------------------------------------------------------------------------------------
488
489   /**
490    * Returns a detailed string representation of this field.
491    *
492    * <p>
493    * The returned string includes:
494    * <ul>
495    *    <li>Modifiers (public, private, protected, static, final, volatile, transient)
496    *    <li>Field type with generics (e.g., "List&lt;String&gt;")
497    *    <li>Fully qualified field name (declaring-class.field-name)
498    * </ul>
499    *
500    * <h5 class='section'>Examples:</h5>
501    * <p class='bjava'>
502    *    <jc>// Simple field</jc>
503    *    FieldInfo <jv>fi</jv> = ...;
504    *    <jv>fi</jv>.toString();
505    *    <jc>// Returns: "public java.lang.String org.example.MyClass.name"</jc>
506    *
507    *    <jc>// Static final field</jc>
508    *    <jc>// Returns: "public static final int org.example.MyClass.MAX_VALUE"</jc>
509    *
510    *    <jc>// Generic field</jc>
511    *    <jc>// Returns: "private java.util.List&lt;java.lang.String&gt; org.example.MyClass.items"</jc>
512    *
513    *    <jc>// Volatile field</jc>
514    *    <jc>// Returns: "volatile boolean org.example.MyClass.flag"</jc>
515    * </p>
516    *
517    * @return A detailed string representation including modifiers, type, and full name.
518    */
519   @Override
520   public String toString() {
521      return toString.get();
522   }
523
524   private String findToString() {
525      var sb = new StringBuilder(256);
526
527      // Modifiers
528      var mods = Modifier.toString(getModifiers());
529      if (nn(mods) && ! mods.isEmpty()) {
530         sb.append(mods).append(" ");
531      }
532
533      // Field type (use generic type to show generics)
534      var genericType = inner.getGenericType();
535      ClassInfo.of(genericType).appendNameFormatted(sb, FULL, true, '$', BRACKETS);
536
537      // Fully qualified field name
538      sb.append(" ").append(getNameFull());
539
540      return sb.toString();
541   }
542
543   private String findNameFull() {
544      var sb = new StringBuilder(128);
545      var dc = declaringClass;
546      var pi = dc.getPackage();
547      if (nn(pi))
548         sb.append(pi.getName()).append('.');
549      // HTT - false branch (pi == null) is hard to test: some classloaders return Package objects
550      // even for default package classes, though Java API spec says it should return null
551      dc.appendNameFormatted(sb, SHORT, true, '$', BRACKETS);
552      sb.append('.').append(getName());
553      return sb.toString();
554   }
555
556   /**
557    * Resolves field value from the bean store and sets it on the specified bean instance.
558    *
559    * <p>
560    * This method resolves the field value using the same logic as parameter resolution,
561    * supporting single beans, {@code Optional}, arrays, {@code List}, {@code Set}, and {@code Map}.
562    * The field is made accessible before setting its value.
563    *
564    * <h5 class='section'>Field Resolution:</h5>
565    * <ul class='spaced-list'>
566    *    <li><b>Single beans</b> - Resolved using {@link BeanStore#getBean(Class)} or {@link BeanStore#getBean(Class, String)}.
567    *       Throws {@link ExecutableException} if not found.
568    *    <li><b>Optional beans</b> - Wrapped in <c>Optional</c>, or <c>Optional.empty()</c> if not found.
569    *       Never throws an exception.
570    *    <li><b>Arrays</b> - All beans of the element type are collected into an array (may be empty).
571    *       Never throws an exception.
572    *    <li><b>Lists</b> - All beans of the element type are collected into a <c>List</c> (may be empty).
573    *       Never throws an exception.
574    *    <li><b>Sets</b> - All beans of the element type are collected into a <c>LinkedHashSet</c> (may be empty).
575    *       Never throws an exception.
576    *    <li><b>Maps</b> - All beans of the value type are collected into a <c>LinkedHashMap</c> keyed by bean name (may be empty).
577    *       Unnamed beans use an empty string as the key.  Never throws an exception.
578    * </ul>
579    *
580    * @param <T> The bean type.
581    * @param beanStore The bean store to resolve the field value from.
582    * @param bean The object instance containing the field.
583    * @return The same bean instance (for method chaining).
584    * @throws ExecutableException If a required field (non-Optional, non-collection) cannot be resolved from the bean store.
585    */
586   @SuppressWarnings({
587      "java:S3776" // Cognitive complexity acceptable for dependency injection
588   })
589   public <T> T inject(BeanStore beanStore, T bean) {
590      accessible();
591      var fieldType = getFieldType();
592
593      // @Value sites resolve from Settings/VarResolver, not the bean store.
594      var annos = getAnnotations();
595      var valueExpr = ValueResolver.findValueExpression(annos);
596      if (valueExpr != null) {
597         ValueResolver.checkInjectConflict(annos, this.toString());
598         var unwrapped = fieldType.unwrap(Optional.class).inner();
599         // Pass the declared (generic) field type so @Value Supplier<String> autodetects
600         // Field type IS the opt-in signal for re-evaluating reads (Supplier<String> autodetect).
601         // BeanStore is forwarded so caller-scoped PropertySource beans (e.g. per-RestContext
602         // @Rest(config=...) Configs) participate in expression resolution alongside Settings.
603         var resolved = ValueResolver.resolve(valueExpr, unwrapped, inner.getGenericType(), this.toString(), beanStore);
604         if (fieldType.is(Optional.class)) {
605            // VarResolver substitutes "" for a missing key with no default. Collapse both to
606            // Optional.empty() so @Value("${maybe}") Optional<T> behaves the same as Spring's.
607            set(bean, (resolved == null || (resolved instanceof String resolved2 && resolved2.isEmpty()))
608               ? oe()
609               : o(resolved));
610         } else {
611            set(bean, resolved);
612         }
613         return bean;
614      }
615
616      // Find qualifier from @Named or @Qualifier annotation (same logic as ParameterInfo.getResolvedQualifier)
617      var beanQualifier = annos.stream()
618         .map(JsrSupport::qualifierValue)
619         .filter(Objects::nonNull)
620         .findFirst()
621         .orElse(null);
622
623      var ptUnwrapped = fieldType.unwrap(Optional.class);
624
625      // Handle collections and arrays
626      Object collectionValue = null;
627      if (ptUnwrapped.isInjectCollectionType()) {
628         // Extract element type from field type
629         Class<?> elementType = null;
630
631         if (ptUnwrapped.isArray()) {
632            elementType = ptUnwrapped.getComponentType().inner();
633         } else {
634            Type parameterizedType = fieldType.innerType();
635            var inner2 = o(ptUnwrapped.inner()).orElse(Object.class);
636
637            if (eqa(inner2, List.class, Set.class)) {
638               if (parameterizedType instanceof ParameterizedType parameterizedType2) {
639                  var typeArgs = parameterizedType2.getActualTypeArguments();
640                  if (typeArgs.length > 0 && typeArgs[0] instanceof Class<?> elementClass) {
641                     elementType = elementClass;
642                  }
643               }
644            } else if (eq(inner2, Map.class) && parameterizedType instanceof ParameterizedType parameterizedType2) {
645               var typeArgs = parameterizedType2.getActualTypeArguments();
646               if (typeArgs.length >= 2 && typeArgs[0] == String.class && typeArgs[1] instanceof Class<?> valueClass) {
647                  elementType = valueClass;
648               }
649            }
650         }
651
652         collectionValue = ReflectionUtils.resolveCollectionValue(elementType, beanStore, ptUnwrapped);
653      }
654
655      Object value;
656      if (nn(collectionValue)) {
657         value = fieldType.is(Optional.class) ? o(collectionValue) : collectionValue;
658      } else {
659         // Handle single bean
660         var ptc = ptUnwrapped.inner();
661         var o2 = beanQualifier == null ? beanStore.getBean(ptc) : beanStore.getBean(ptc, beanQualifier);
662
663         if (fieldType.is(Optional.class)) {
664            value = o2;
665         } else if (o2.isPresent()) {
666            value = o2.get();
667         } else {
668            throw exex("Could not resolve value for field %s", this);
669         }
670      }
671
672      set(bean, value);
673      return bean;
674   }
675}