This is an automated email from the ASF dual-hosted git repository.

jamesbognar pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/juneau.git


The following commit(s) were added to refs/heads/master by this push:
     new e06fc7c0dd New org.apache.juneau.commons.inject package
e06fc7c0dd is described below

commit e06fc7c0ddb8487be68a441568d9331af79f1d24
Author: James Bognar <[email protected]>
AuthorDate: Thu Jan 8 16:05:01 2026 -0500

    New org.apache.juneau.commons.inject package
---
 .../apache/juneau/commons/inject/BeanCreator2.java | 414 ++++++++++-----------
 .../apache/juneau/commons/inject/InjectUtils.java  | 397 +++++++++++++++-----
 .../juneau/commons/reflect/ExecutableInfo.java     |   2 +
 .../apache/juneau/commons/reflect/MethodInfo.java  |   4 +
 .../juneau/commons/inject/InjectUtils_Test.java    | 334 ++++++++++++++---
 5 files changed, 775 insertions(+), 376 deletions(-)

diff --git 
a/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/inject/BeanCreator2.java
 
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/inject/BeanCreator2.java
index daf8806d9a..ceebd6b52c 100644
--- 
a/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/inject/BeanCreator2.java
+++ 
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/inject/BeanCreator2.java
@@ -20,11 +20,17 @@ import static java.util.stream.Collectors.*;
 import static org.apache.juneau.commons.reflect.ReflectionUtils.*;
 import static org.apache.juneau.commons.reflect.Visibility.*;
 import static org.apache.juneau.commons.utils.AssertionUtils.*;
+import static org.apache.juneau.commons.utils.ThrowableUtils.*;
 import static org.apache.juneau.commons.utils.Utils.*;
+import static org.apache.juneau.commons.inject.InjectUtils.*;
+import static java.util.Comparator.*;
+import static org.apache.juneau.commons.reflect.ElementFlag.*;
+
 
 import java.util.*;
 import java.util.function.*;
 
+import org.apache.juneau.commons.function.*;
 import org.apache.juneau.commons.lang.*;
 import org.apache.juneau.commons.reflect.*;
 import org.apache.juneau.commons.utils.*;
@@ -116,149 +122,148 @@ import org.apache.juneau.commons.utils.*;
  *     <li class='jc'>{@link BasicBeanStore}
  * </ul>
  *
- * @param <T> The bean type being created.
+ * @param <T> The bean beanSubType being created.
  */
 public class BeanCreator2<T> {
 
-       static class Match<T extends ExecutableInfo> {
-               T executable = null;
-               int numMatches = -1;
-
-               @SuppressWarnings("unchecked")
-               void add(T ei) {
-                       if (ei.getParameterCount() > numMatches) {
-                               numMatches = ei.getParameterCount();
-                               executable = (T)ei.accessible();
-                       }
-               }
-
-               T get() {
-                       return executable;
-               }
-
-               boolean isPresent() { return nn(executable); }
-       }
-
        /**
-        * Shortcut for calling <c>BeanCreator.of(beanType, 
BasicBeanStore.INSTANCE)</c>.
+        * Shortcut for calling <c>BeanCreator.of(beanSubType, 
BasicBeanStore.INSTANCE)</c>.
         *
-        * @param <T> The bean type to create.
-        * @param beanType The bean type to create.
+        * @param <T> The bean beanSubType to create.
+        * @param beanSubType The bean beanSubType to create.
         * @return A new creator.
         */
-       public static <T> BeanCreator2<T> of(ClassInfoTyped<T> beanType) {
-               return new BeanCreator2<>(beanType, null);
+       public static <T> BeanCreator2<T> of(Class<T> beanType) {
+               return new BeanCreator2<>(beanType);
        }
 
-       /**
-        * Creates a new bean creator.
-        *
-        * @param <T> The bean type to create.
-        * @param beanType The bean type to create.
-        * @param beanStore The bean store to use for parameter resolution.
-        * @return A new creator.
-        */
-       public static <T> BeanCreator2<T> of(ClassInfoTyped<T> beanType, 
BeanStore beanStore) {
-               assertArgNotNull("beanType", beanType);
-               return new BeanCreator2<>(beanType, beanStore);
-       }
-
-       private final BasicBeanStore2 store;
-       private ClassInfoTyped<T> type;
-       private Object builder;
-       private T impl;
-       private Object outer;
-
+       private BeanStore parentStore;
+       private Supplier<BasicBeanStore2> store = mem(()-> new 
BasicBeanStore2(parentStore));
+       private ClassInfoTyped<T> beanType;
+       private ClassInfo beanSubType;
+       private ResettableSupplier<ClassInfo> builderType = memr(() -> 
findBuilderType());;
+       private ResettableSupplier<Object> builder = memr(() -> findBuilder());
+       private Supplier<ExecutableInfo> builderAcceptor = memr(() -> 
findBuilderAcceptor());
+       private T beanImpl;
+       private Object enclosingInstance;
        private boolean silent;
 
        /**
         * Constructor.
         *
-        * @param type The bean type being created.
-        * @param store The bean store creating this creator.
+        * @param beanSubType The bean type being created.
         */
-       protected BeanCreator2(ClassInfoTyped<T> type, BeanStore store) {
-               this.type = type;
-               this.store = new BasicBeanStore2(store);
+       protected BeanCreator2(Class<T> beanType) {
+               this.beanType = info(assertArgNotNull("beanType", beanType));
+               this.beanSubType = this.beanType;
        }
 
+       public BeanCreator2 beanStore(BeanStore value) {
+               parentStore = value;
+               return this;
+       }
        /**
-        * Specifies the outer object to use when instantiating inner classes.
+        * Specifies the enclosingInstance object to use if/when instantiating 
inner classes.
         *
-        * @param outer The outer object.  Can be <jk>null</jk>.
+        * @param enclosingInstance The enclosingInstance object.  Can be 
<jk>null</jk>.
         * @return This object.
         */
-       public BeanCreator2<T> outer(Object outer) {
-               this.outer = outer;
+       public BeanCreator2<T> enclosingInstance(Object outer) {
+               this.enclosingInstance = outer;
                return this;
        }
 
-       /**
-        * Adds an argument to this creator.
-        *
-        * @param <T2> The parameter type.
-        * @param beanType The parameter type.
-        * @param bean The parameter value.
-        * @return This object.
-        */
-       public <T2> BeanCreator2<T> addBean(Class<T2> beanType, T2 bean) {
-               store.add(beanType, bean);
+       public <T2> BeanCreator2<T> addBean(Class<T2> type, T2 bean) {
+               store.get().add(type, bean);
                return this;
        }
 
+       public <T2> T2 add(Class<T2> type, T2 bean) {
+               store.get().add(type, bean);
+               return bean;
+       }
+
        /**
-        * Specifies a builder object for the bean type.
+        * Specifies a builder object for the bean beanSubType.
         *
         * <h5 class='section'>Notes:</h5><ul>
         *      <li class='note'>When specified, we don't look for a static 
creator method.
         * </ul>
         *
-        * @param <B> The class type of the builder.
-        * @param type The class type of the builder.
+        * @param beanSubType The class beanSubType of the builder.
         * @param value The value for this setting.
         * @return This object.
         */
-       @SuppressWarnings("unchecked")
+       @SuppressWarnings({ "unchecked", "rawtypes" })
        public BeanCreator2<T> builder(Object value) {
-               builder = value;
-               return this;
-       }
-
-       public <B> BeanCreator2<T> builder(ClassInfoTyped<B> type) {
-               // Find and invoke builder from the following in this order:
-               // public static Builder create(...) method on outer class with 
matching args in bean store.
-               // public Builder(...) constructor with matching args in bean 
store.
+               builder.set(assertArgNotNull("value", value));
                return this;
        }
 
        /**
-        * Same as {@link #run()} but returns the value wrapped in an {@link 
Optional}.
+        * Creates a builder instance of the specified beanSubType.
         *
-        * @return A new bean wrapped in an {@link Optional}.
-        */
-       public Optional<T> execute() {
-               return opt(silent().run());
-       }
-
-       /**
-        * Allows you to specify a specific instance for the build method to 
return.
+        * <p>
+        * Looks for a builder instance in the following order:
+        * <ol>
+        *      <li>A public static method on the enclosingInstance class that 
returns the builder beanSubType.
+        *              The method must be named <c>create</c> or 
<c>builder</c>, be static, not deprecated,
+        *              and have parameters that can be resolved from the bean 
store.
+        *      <li>A public constructor on the builder beanSubType with 
parameters that can be resolved from the bean store.
+        * </ol>
         *
-        * @param value The value for this setting.
-        * @return This object.
+        * @param <B> The builder beanSubType.
+        * @param builderType The builder beanSubType to create.
+        * @return A builder instance.
+        * @throws ExecutableException If the builder could not be created.
         */
-       public BeanCreator2<T> impl(T value) {
-               impl = value;
+       @SuppressWarnings("unchecked")
+       public BeanCreator2<T> builder(Class<?> value) {
+               builderType.set(info(assertArgNotNull("value", value)));
                return this;
        }
 
-       /**
-        * Same as {@link #run()} but returns the alternate value if a method 
of creation could not be found.
-        *
-        * @param other The other bean to use.
-        * @return Either the created or other bean.
-        */
-       public T orElse(T other) {
-               return execute().orElse(other);
+       private Object findBuilder() {
+
+//             var bs = store.get();
+//
+//             // First, look for a static create/builder method on the 
enclosingInstance class that returns the builder beanSubType
+//             var m1 = beanSubType.getPublicMethods().stream()
+//                     .filter(x -> x.isStatic() && x.isNotDeprecated() && 
x.hasReturnType(builderType) && (x.hasName("create") || x.hasName("builder")))
+//                     .filter(x -> hasAllParameters(x, bs, enclosingInstance))
+//                     
.sorted(comparing(MethodInfo::getParameterCount).reversed())
+//                     .findFirst();
+//
+//             if (m1.isPresent()) {
+//                     builder = add(builderType, injectBeans(invoke(m1.get(), 
bs, enclosingInstance), bs));
+//                     return (B)builder;
+//             }
+//
+//             // Second, look for a public constructor on the builder 
beanSubType
+//             var m2 = bt.getPublicConstructors().stream()
+//                     .filter(x -> hasAllParameters(x, bs, enclosingInstance))
+//                     
.sorted(comparing(ConstructorInfo::getParameterCount).reversed())
+//                     .findFirst();
+//
+//             if (m2.isPresent()) {
+//                     builder = add(builderType, injectBeans(invoke(m2.get(), 
bs, enclosingInstance), bs));
+//                     return (B)builder;
+//             }
+//
+//             // If silent mode, return null; otherwise throw exception
+//             if (silent)
+//                     return null;
+//
+//             throw exex("Could not create builder {0} for enclosingInstance 
class {1}: No suitable static create/builder method or public constructor found 
with resolvable parameters.", cn(builderType), cn(beanSubType));
+               return null;
+       }
+
+       private ExecutableInfo findBuilderAcceptor() {
+               return null;
+       }
+
+       private ClassInfo findBuilderType() {
+               return null;
        }
 
        /**
@@ -267,126 +272,90 @@ public class BeanCreator2<T> {
         * @return A new bean.
         * @throws ExecutableException if bean could not be created and {@link 
#silent()} was not enabled.
         */
-       public T run() {
-
-               if (nn(impl))
-                       return impl;
-
-               if (type == null)
-                       return null;
-
-               var found = Value.<String>empty();
-
-               // Look for getInstance(Builder).
-               if (nn(builder)) {
-                       // @formatter:off
-                       var result = type.getPublicMethod(
-                               x -> x.isStatic()
-                               && x.isNotDeprecated()
-                               && x.hasNumParameters(1)
-                               && x.getParameter(0).canAccept(builder)
-                               && x.hasReturnType(type)
-                               && x.hasName("getInstance")
-                       ).map(m -> m.<T>invoke(null, builder));
-                       // @formatter:on
-                       if (result.isPresent())
-                               return result.get();
-               }
-
-               // Look for getInstance().
-               if (builder == null) {
-                       // @formatter:off
-                       var result = type.getPublicMethod(
-                               x -> x.isStatic()
-                               && x.isNotDeprecated()
-                               && x.getParameterCount() == 0
-                               && x.hasReturnType(type)
-                               && x.hasName("getInstance")
-                       ).map(m -> m.<T>invoke(null));
-                       // @formatter:on
-                       if (result.isPresent())
-                               return result.get();
-               }
-
-               if (builder == null) {
-                       // Look for static creator methods.
-
-                       var match = new Match<MethodInfo>();
-
-                       // Look for static creator method.
-                       type.getPublicMethods().stream().filter(x -> 
isStaticCreateMethod(x)).forEach(x -> {
-                               found.set("STATIC_CREATOR");
-                               if (InjectUtils.hasAllParameters(x, store, 
outer))
-                                       match.add(x);
-                       });
-
-                       if (match.isPresent())
-                               return match.get().invoke(null, 
InjectUtils.getParameters(match.get(), store, outer));
-               }
-
-               if (type.isInterface()) {
-                       if (silent)
-                               return null;
-                       throw new ExecutableException("Could not instantiate 
class {0}: {1}.", type.getName(), "Class is an interface");
-               }
-
-               if (type.isAbstract()) {
-                       if (silent)
-                               return null;
-                       throw new ExecutableException("Could not instantiate 
class {0}: {1}.", type.getName(), "Class is abstract");
-               }
-
-               // Look for public constructor.
-               var constructorMatch = new Match<ConstructorInfo>();
-               type.getPublicConstructors().stream().forEach(x -> {
-                       found.setIfEmpty("PUBLIC_CONSTRUCTOR");
-                       if (InjectUtils.hasAllParameters(x, store, outer))
-                               constructorMatch.add(x);
-               });
-
-               // Look for protected constructor.
-               if (! constructorMatch.isPresent()) {
-                       
type.getDeclaredConstructors().stream().filter(ConstructorInfo::isProtected).forEach(x
 -> {
-                               found.setIfEmpty("PROTECTED_CONSTRUCTOR");
-                               if (InjectUtils.hasAllParameters(x, store, 
outer))
-                                       constructorMatch.add(x);
-                       });
-               }
-
-               // Execute.
-               if (constructorMatch.isPresent())
-                       return 
constructorMatch.get().newInstance(InjectUtils.getParameters(constructorMatch.get(),
 store, outer));
-
-               if (builder == null) {
-                       // Look for static-builder/protected-constructor pair.
-                       var value = Value.<T>empty();
-                       type.getDeclaredConstructors().stream().filter(x -> 
x.hasNumParameters(1) && x.isVisible(PROTECTED)).forEach(x -> {
-                               var pt = 
x.getParameter(0).getParameterType().inner();
-                               type.getPublicMethod(y -> 
isStaticCreateMethod(y, pt)).ifPresent(m -> {
-                                       Object b = m.invoke(null);
-                                       
value.set(x.accessible().newInstance(b));
-                               });
-                       });
-                       if (value.isPresent())
-                               return value.get();
-               }
+       public Optional<T> run() {
+               // @formatter:off
 
-               if (silent)
-                       return null;
+               if (nn(beanImpl))
+                       return opt(beanImpl);
+
+               var store = this.store.get();
+               var builder = findBuilder();
+               var builderType = findBuilderType();
+               Optional<T> r;
+               var methodComparator = 
comparing(MethodInfo::getParameterCount).reversed();
+               var constructorComparator = 
comparing(ConstructorInfo::getParameterCount).reversed();
+//
+//             if (builder != null) {
+//
+//                     // Look for Builder.build().
+//                     r = builderType.getPublicMethods().stream()
+//                             .filter(x -> x.isAll(NOT_STATIC, 
NOT_DEPRECATED) && hasName(x, "build", "create", "get") && 
x.hasReturnType(beanSubType) && hasAllParameters(x, store))
+//                             .sorted(methodComparator)
+//                             .findFirst()
+//                             .map(x -> beanType.cast(invoke(x, store, 
builder)));
+//
+//                     // Look for Bean.getInstance(Builder).
+//                     if (r.isEmpty())
+//                             r = beanType.getPublicMethods().stream()
+//                                     .filter(x -> x.isAll(STATIC, 
NOT_DEPRECATED) && hasName(x, "getInstance") && x.hasReturnType(beanSubType) && 
hasAllParameters(x, store, builder) && x.hasParameter(builder))
+//                                     .sorted(methodComparator)
+//                                     .findFirst()
+//                                     .map(x -> beanType.cast(invoke(x, 
store, builder)));
+//
+//                     // Look for Bean(Builder).
+//                     if (r.isEmpty())
+//                             r = beanSubType.getPublicConstructors().stream()
+//                                     .filter(x -> x.is(NOT_DEPRECATED) && 
x.isDeclaringClass(beanSubType) && hasAllParameters(x, store, 
enclosingInstance, builder))
+//                                     .sorted(constructorComparator)
+//                                     .findFirst()
+//                                     .map(x -> beanType.cast(invoke(x, 
store, enclosingInstance, builder)));
+//
+//                     // Look for Builder.anything().
+//                     if (r.isEmpty())
+//                             r = builderType.getPublicMethods().stream()
+//                                     .filter(x -> x.isAll(NOT_STATIC, 
NOT_DEPRECATED) && x.hasReturnType(beanSubType) && hasAllParameters(x, store))
+//                                     .sorted(methodComparator)
+//                                     .findFirst()
+//                                     .map(x -> beanType.cast(invoke(x, 
store, builder)));
+//
+//                     if (r.isPresent())
+//                             return r;
+//
+//                     if (silent)
+//                             return null;
+//
+//                     throw new ExecutableException("Could not instantiate 
class {0} using builder type {1}.", beanSubType.getName(), builderType);
+//             }
+//
+//             // Look for Bean.getInstance().
+//             r = beanSubType.getPublicMethods().stream()
+//                     .filter(x -> x.isAll(STATIC, NOT_DEPRECATED) && 
hasName(x, "getInstance") && x.hasReturnType(beanSubType))
+//                     .sorted(methodComparator)
+//                     .findFirst()
+//                     .map(x -> beanType.cast(invoke(x, store)));
+//
+//             // Look for Bean().
+//             beanSubType.getPublicConstructors().stream()
+//                     .filter(x -> x.isAll(NOT_DEPRECATED) && 
x.isDeclaringClass(beanSubType) && hasAllParams(x, store, enclosingInstance))
+//                     .sorted(constructorComparator)
+//                     .findFirst()
+//                     .map(x -> beanType.cast(invoke(x, store)));
+//
+//
+//             if (beanSubType.isInterface()) {
+//                     if (silent)
+//                             return null;
+//                     throw new ExecutableException("Could not instantiate 
class {0}: {1}.", beanSubType.getName(), "Class is an interface");
+//             }
+//
+//             if (beanSubType.isAbstract()) {
+//                     if (silent)
+//                             return null;
+//                     throw new ExecutableException("Could not instantiate 
class {0}: {1}.", beanSubType.getName(), "Class is abstract");
+//             }
+
+               return null;
 
-               var msg = (String)null;
-               if (found.isEmpty()) {
-                       msg = "No public/protected constructors found";
-               } else if (found.get().equals("STATIC_CREATOR")) {
-                       msg = "Static creator found but could not find 
prerequisites: "
-                               + type.getPublicMethods().stream().filter(x -> 
isStaticCreateMethod(x)).map(x -> InjectUtils.getMissingParameters(x, store, 
outer)).sorted().collect(joining(" or "));
-               } else if (found.get().equals("PUBLIC_CONSTRUCTOR")) {
-                       msg = "Public constructor found but could not find 
prerequisites: " + type.getPublicConstructors().stream().map(x -> 
InjectUtils.getMissingParameters(x, store, outer)).sorted().collect(joining(" 
or "));
-               } else {
-                       msg = "Protected constructor found but could not find 
prerequisites: "
-                               + 
type.getDeclaredConstructors().stream().filter(ConstructorInfo::isProtected).map(x
 -> InjectUtils.getMissingParameters(x, store, 
outer)).sorted().collect(joining(" or "));
-               }
-               throw new ExecutableException("Could not instantiate class {0}: 
{1}.", type.getName(), msg);
        }
 
        /**
@@ -406,22 +375,22 @@ public class BeanCreator2<T> {
         * @return A supplier that returns the results of the {@link #run()} 
method.
         */
        public Supplier<T> supplier() {
-               return () -> run();
+               return () -> run().get();
        }
 
        /**
-        * Allows you to specify a subclass of the specified bean type to 
create.
+        * Allows you to specify a subclass of the specified bean beanSubType 
to create.
         *
         * @param value The value for this setting.
         * @return This object.
         */
        public BeanCreator2<T> type(Class<T> value) {
-               type = opt(value).map(x -> info(x)).orElse(null);
+               beanSubType = opt(value).map(x -> info(x)).orElse(null);
                return this;
        }
 
        /**
-        * Allows you to specify a subclass of the specified bean type to 
create.
+        * Allows you to specify a subclass of the specified bean beanSubType 
to create.
         *
         * @param value The value for this setting.
         * @return This object.
@@ -431,7 +400,7 @@ public class BeanCreator2<T> {
        }
 
        private boolean isStaticCreateMethod(MethodInfo m) {
-               return isStaticCreateMethod(m, type.inner());
+               return isStaticCreateMethod(m, beanSubType.inner());
        }
 
        private static boolean isStaticCreateMethod(MethodInfo m, Class<?> 
type) {
@@ -442,4 +411,11 @@ public class BeanCreator2<T> {
                        && (m.hasName("create") || m.hasName("builder"));
                // @formatter:on
        }
+
+       private static boolean hasName(MethodInfo ei, String...names) {
+               for (var s : names)
+                       if (ei.hasName(s))
+                               return true;
+               return false;
+       }
 }
\ No newline at end of file
diff --git 
a/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/inject/InjectUtils.java
 
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/inject/InjectUtils.java
index fa6ed8194c..a95015bedc 100644
--- 
a/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/inject/InjectUtils.java
+++ 
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/inject/InjectUtils.java
@@ -17,7 +17,7 @@
 package org.apache.juneau.commons.inject;
 
 import static java.util.stream.Collectors.*;
-import static org.apache.juneau.commons.utils.CollectionUtils.*;
+import static org.apache.juneau.commons.utils.ThrowableUtils.*;
 import static org.apache.juneau.commons.utils.Utils.*;
 
 import java.lang.reflect.*;
@@ -59,6 +59,12 @@ import org.apache.juneau.commons.reflect.*;
  * <c>@Inject</c> or <c>@Autowired</c> (matched by simple class name).  This 
provides Spring-like dependency injection
  * functionality.
  *
+ * <h5 class='section'>Additional Bean Sources:</h5>
+ * <p>
+ * All parameter resolution methods support an optional <c>otherBeans</c> 
varargs parameter that provides
+ * additional bean instances to use if not found in the bean store.  This is 
useful for passing in
+ * context-specific beans or temporary instances that aren't stored in the 
main bean store.
+ *
  * <h5 class='section'>Example:</h5>
  * <p class='bjava'>
  *     <jc>// Bean store with multiple services</jc>
@@ -81,9 +87,16 @@ import org.apache.juneau.commons.reflect.*;
  *     Object[] <jv>params</jv> = 
InjectUtils.<jsm>getParameters</jsm>(<jv>constructor</jv>, <jv>beanStore</jv>, 
<jk>null</jk>);
  *     MyClass <jv>instance</jv> = 
<jv>constructor</jv>.newInstance(<jv>params</jv>);
  *
+ *     <jc>// Or use convenience method</jc>
+ *     MyClass <jv>instance</jv> = 
InjectUtils.<jsm>invoke</jsm>(<jv>constructor</jv>, <jv>beanStore</jv>, 
<jk>null</jk>);
+ *
  *     <jc>// Or use automatic injection</jc>
  *     MyClass <jv>instance</jv> = <jk>new</jk> MyClass();
  *     InjectUtils.<jsm>injectBeans</jsm>(<jv>instance</jv>, 
<jv>beanStore</jv>);
+ *
+ *     <jc>// Or provide additional beans not in store</jc>
+ *     MyService <jv>tempService</jv> = <jk>new</jk> MyService();
+ *     MyClass <jv>instance2</jv> = 
InjectUtils.<jsm>invoke</jsm>(<jv>constructor</jv>, <jv>beanStore</jv>, 
<jk>null</jk>, <jv>tempService</jv>);
  * </p>
  *
  * <h5 class='section'>See Also:</h5><ul>
@@ -97,112 +110,174 @@ public class InjectUtils {
         * Returns a comma-delimited list of parameter types that are missing 
from the bean store.
         *
         * <p>
-        * Analyzes the parameters of the specified executable and checks if 
all required beans are available in the bean store.
+        * Analyzes the parameters of the specified constructor and checks if 
all required beans are available
+        * in the bean store or in the <c>otherBeans</c> parameter.
         *
         * <p>
         * The following parameter types are considered optional and are not 
checked:
         * <ul>
         *      <li>Parameters of type <c>Optional&lt;T&gt;</c>
         *      <li>Parameters of type <c>T[]</c>, <c>List&lt;T&gt;</c>, 
<c>Set&lt;T&gt;</c>, or <c>Map&lt;String,T&gt;</c>
-        *      <li>The first parameter if it matches the <c>bean</c> object 
type (for non-static inner classes)
+        *      <li>The first parameter if it matches the 
<c>enclosingInstance</c> object type (for non-static inner classes)
         * </ul>
         *
         * <p>
         * If a parameter has a {@link org.apache.juneau.annotation.Named 
@Named} or {@code @Qualifier} annotation,
-        * the method checks for a bean with that specific name.  Otherwise, it 
checks for an unnamed bean of the parameter type.
+        * the method checks for a bean with that specific name in the bean 
store.  Otherwise, it checks for an unnamed bean
+        * of the parameter type in the bean store, and if not found, checks 
the <c>otherBeans</c> parameter.
         *
-        * @param executable The constructor or method to analyze.
+        * @param ci The constructor to analyze.
         * @param beanStore The bean store to check for beans.
-        * @param bean The outer class instance for non-static inner class 
constructors/methods.
+        * @param enclosingInstance The outer class instance for non-static 
inner class constructors.
         *      If the first parameter type matches this object's type, it is 
used as the first parameter and not checked in the bean store.
         *      Can be <jk>null</jk> for regular classes or static inner 
classes.
+        * @param otherBeans Optional additional bean instances to check if not 
found in the bean store.
+        *      These are checked after the bean store but before marking the 
parameter as missing.
         * @return A comma-delimited, sorted list of missing parameter types 
(e.g., <js>"String,Integer"</js>),
         *      or <jk>null</jk> if all required parameters are available.
         */
-       public static String getMissingParameters(ExecutableInfo executable, 
BeanStore beanStore, Object bean) {
-               var params = executable.getParameters();
-               List<String> l = list();
-               loop: for (int i = 0; i < params.size(); i++) {
-                       var pi = params.get(i);
-                       var pt = pi.getParameterType();
-                       if (i == 0 && nn(bean) && pt.isInstance(bean))
-                               continue loop;
-                       if (pt.is(Optional.class))
-                               continue loop;
-                       if (isCollectionType(pt))
-                               continue loop; // Collections and arrays are 
always satisfied (even if empty), so skip them
-                       var beanName = pi.getResolvedQualifier();  // Use 
@Named/@Qualified for bean injection
-                       var ptc = pt.unwrap(Optional.class).inner();
-                       if (beanName == null && ! beanStore.hasBean(ptc))
-                               l.add(pt.getNameSimple());
-                       if (nn(beanName) && ! beanStore.hasBean(ptc, beanName))
-                               l.add(pt.getNameSimple() + '@' + beanName);
-               }
-               return l.isEmpty() ? null : 
l.stream().sorted().collect(joining(","));
+       public static String getMissingParameters(ConstructorInfo ci, BeanStore 
beanStore, Object enclosingInstance, Object...otherBeans) {
+               // @formatter:off
+               return nullIfEmpty(
+                       ci.getParameters()
+                               .stream()
+                               .map(x -> getMissingConstructorParameter(x, 
beanStore, enclosingInstance, otherBeans))
+                               .filter(Objects::nonNull)
+                               .sorted()
+                               .collect(joining(","))
+               );
+               // @formatter:on
        }
 
        /**
-        * Resolves and returns parameter values from the bean store for the 
specified executable.
+        * Returns a comma-delimited list of parameter types that are missing 
from the bean store.
+        *
+        * <p>
+        * Analyzes the parameters of the specified method and checks if all 
required beans are available
+        * in the bean store or in the <c>otherBeans</c> parameter.
         *
         * <p>
-        * For each parameter in the executable, this method:
+        * The following parameter types are considered optional and are not 
checked:
         * <ul>
-        *      <li>If the first parameter type matches the <c>bean</c> object 
type, uses the <c>bean</c> object
-        *              (for non-static inner class constructors/methods).
+        *      <li>Parameters of type <c>Optional&lt;T&gt;</c>
+        *      <li>Parameters of type <c>T[]</c>, <c>List&lt;T&gt;</c>, 
<c>Set&lt;T&gt;</c>, or <c>Map&lt;String,T&gt;</c>
+        * </ul>
+        *
+        * <p>
+        * If a parameter has a {@link org.apache.juneau.annotation.Named 
@Named} or {@code @Qualifier} annotation,
+        * the method checks for a bean with that specific name in the bean 
store.  Otherwise, it checks for an unnamed bean
+        * of the parameter type in the bean store, and if not found, checks 
the <c>otherBeans</c> parameter.
+        *
+        * @param mi The method to analyze.
+        * @param beanStore The bean store to check for beans.
+        * @param otherBeans Optional additional bean instances to check if not 
found in the bean store.
+        *      These are checked after the bean store but before marking the 
parameter as missing.
+        * @return A comma-delimited, sorted list of missing parameter types 
(e.g., <js>"String,Integer"</js>),
+        *      or <jk>null</jk> if all required parameters are available.
+        */
+       public static String getMissingParameters(MethodInfo mi, BeanStore 
beanStore, Object...otherBeans) {
+               // @formatter:off
+               return nullIfEmpty(
+                       mi.getParameters()
+                               .stream()
+                               .map(x -> getMissingParameter(x, beanStore, 
otherBeans))
+                               .filter(Objects::nonNull)
+                               .sorted()
+                               .collect(joining(","))
+               );
+               // @formatter:on
+       }
+
+       /**
+        * Resolves and returns parameter values from the bean store for the 
specified constructor.
+        *
+        * <p>
+        * For each parameter in the constructor, this method:
+        * <ul>
+        *      <li>If the first parameter type matches the 
<c>enclosingInstance</c> object type, uses the <c>enclosingInstance</c> object
+        *              (for non-static inner class constructors).
         *      <li>If the parameter is a collection/array/map type, collects 
all beans of the element type.
-        *      <li>Otherwise, looks up a single bean by type and optional 
qualifier name.
+        *      <li>Otherwise, looks up a single bean by type and optional 
qualifier name in the bean store.
+        *      <li>If not found in the bean store, checks the 
<c>otherBeans</c> parameter.
         * </ul>
         *
         * <h5 class='section'>Parameter Resolution:</h5>
         * <ul class='spaced-list'>
         *      <li><b>Single beans</b> - Resolved using {@link 
BeanStore#getBean(Class)} or {@link BeanStore#getBean(Class, String)}.
+        *              If not found, checks <c>otherBeans</c> for a compatible 
instance.
+        *              Throws {@link ExecutableException} if not found in 
either location.
         *      <li><b>Optional beans</b> - Wrapped in <c>Optional</c>, or 
<c>Optional.empty()</c> if not found.
-        *      <li><b>Arrays</b> - All beans of the element type are collected 
into an array.
-        *      <li><b>Lists</b> - All beans of the element type are collected 
into a <c>List</c>.
-        *      <li><b>Sets</b> - All beans of the element type are collected 
into a <c>LinkedHashSet</c>.
-        *      <li><b>Maps</b> - All beans of the value type are collected 
into a <c>LinkedHashMap</c> keyed by bean name.
+        *              Never throws an exception.
+        *      <li><b>Arrays</b> - All beans of the element type are collected 
into an array (may be empty).
+        *              Never throws an exception.
+        *      <li><b>Lists</b> - All beans of the element type are collected 
into a <c>List</c> (may be empty).
+        *              Never throws an exception.
+        *      <li><b>Sets</b> - All beans of the element type are collected 
into a <c>LinkedHashSet</c> (may be empty).
+        *              Never throws an exception.
+        *      <li><b>Maps</b> - All beans of the value type are collected 
into a <c>LinkedHashMap</c> keyed by bean name (may be empty).
+        *              Never throws an exception.
         * </ul>
         *
-        * @param executable The constructor or method to get parameters for.
+        * @param ci The constructor to get parameters for.
         * @param beanStore The bean store to resolve beans from.
-        * @param bean The outer class instance for non-static inner class 
constructors/methods.
+        * @param enclosingInstance The outer class instance for non-static 
inner class constructors.
         *      If the first parameter type matches this object's type, it is 
used as the first parameter value.
         *      Can be <jk>null</jk> for regular classes or static inner 
classes.
-        * @return An array of parameter values in the same order as the 
executable parameters.
-        *      May contain <jk>null</jk> values if a required bean is not 
found (for non-Optional parameters).
+        * @param otherBeans Optional additional bean instances to use if not 
found in the bean store.
+        *      These are checked after the bean store but before throwing an 
exception.
+        * @return An array of parameter values in the same order as the 
constructor parameters.
+        * @throws ExecutableException If a required parameter (non-Optional, 
non-collection) cannot be resolved
+        *      from the bean store or <c>otherBeans</c>.
         */
-       public static Object[] getParameters(ExecutableInfo executable, 
BeanStore beanStore, Object bean) {
-               var o = new Object[executable.getParameterCount()];
-               for (var i = 0; i < executable.getParameterCount(); i++) {
-                       var pi = executable.getParameter(i);
-                       var pt = pi.getParameterType();
-                       if (i == 0 && nn(bean) && pt.isInstance(bean)) {
-                               o[i] = bean;
-                       } else {
-                               var beanQualifier = pi.getResolvedQualifier();
-                               var ptUnwrapped = pt.unwrap(Optional.class);
-
-                               // Handle collections and arrays
-                               var collectionValue = getCollectionValue(pi, 
ptUnwrapped, beanStore, beanQualifier);
-                               if (nn(collectionValue)) {
-                                       o[i] = pt.is(Optional.class) ? 
Optional.of(collectionValue) : collectionValue;
-                                       continue;
-                               }
+       public static Object[] getParameters(ConstructorInfo ci, BeanStore 
beanStore, Object enclosingInstance, Object...otherBeans) {
+               return ci.getParameters().stream().map(x -> 
getConstructorParameter(x, beanStore, enclosingInstance, otherBeans)).toArray();
+       }
 
-                               // Handle single bean
-                               var ptc = ptUnwrapped.inner();
-                               var o2 = beanQualifier == null ? 
beanStore.getBean(ptc) : beanStore.getBean(ptc, beanQualifier);
-                               o[i] = pt.is(Optional.class) ? o2 : 
o2.orElse(null);
-                       }
-               }
-               return o;
+       /**
+        * Resolves and returns parameter values from the bean store for the 
specified method.
+        *
+        * <p>
+        * For each parameter in the method, this method:
+        * <ul>
+        *      <li>If the parameter is a collection/array/map type, collects 
all beans of the element type.
+        *      <li>Otherwise, looks up a single bean by type and optional 
qualifier name in the bean store.
+        *      <li>If not found in the bean store, checks the 
<c>otherBeans</c> parameter.
+        * </ul>
+        *
+        * <h5 class='section'>Parameter Resolution:</h5>
+        * <ul class='spaced-list'>
+        *      <li><b>Single beans</b> - Resolved using {@link 
BeanStore#getBean(Class)} or {@link BeanStore#getBean(Class, String)}.
+        *              If not found, checks <c>otherBeans</c> for a compatible 
instance.
+        *              Never throws an exception.
+        *      <li><b>Optional beans</b> - Wrapped in <c>Optional</c>, or 
<c>Optional.empty()</c> if not found.
+        *              Never throws an exception.
+        *      <li><b>Arrays</b> - All beans of the element type are collected 
into an array (may be empty).
+        *              Never throws an exception.
+        *      <li><b>Lists</b> - All beans of the element type are collected 
into a <c>List</c> (may be empty).
+        *              Never throws an exception.
+        *      <li><b>Sets</b> - All beans of the element type are collected 
into a <c>LinkedHashSet</c> (may be empty).
+        *              Never throws an exception.
+        *      <li><b>Maps</b> - All beans of the value type are collected 
into a <c>LinkedHashMap</c> keyed by bean name (may be empty).
+        *              Never throws an exception.
+        * </ul>
+        *
+        * @param mi The method to get parameters for.
+        * @param beanStore The bean store to resolve beans from.
+        * @param otherBeans Optional additional bean instances to use if not 
found in the bean store.
+        *      These are checked after the bean store but before throwing an 
exception.
+        * @return An array of parameter values in the same order as the method 
parameters.
+        * @throws ExecutableException If a required parameter (non-Optional, 
non-collection) cannot be resolved
+        *      from the bean store or <c>otherBeans</c>.
+        */
+       public static Object[] getParameters(MethodInfo mi, BeanStore 
beanStore, Object...otherBeans) {
+               return mi.getParameters().stream().map(x -> getParameter(x, 
beanStore, otherBeans)).toArray();
        }
 
        /**
-        * Returns <jk>true</jk> if the bean store has all required parameters 
for the specified executable.
+        * Returns <jk>true</jk> if the bean store has all required parameters 
for the specified constructor.
         *
         * <p>
-        * This method performs the same checks as {@link 
#getMissingParameters(ExecutableInfo, BeanStore, Object)} but
+        * This method performs the same checks as {@link 
#getMissingParameters(ConstructorInfo, BeanStore, Object, Object...)} but
         * returns a boolean instead of a list of missing types.
         *
         * <p>
@@ -210,32 +285,54 @@ public class InjectUtils {
         * <ul>
         *      <li>Parameters of type <c>Optional&lt;T&gt;</c>
         *      <li>Parameters of type <c>T[]</c>, <c>List&lt;T&gt;</c>, 
<c>Set&lt;T&gt;</c>, or <c>Map&lt;String,T&gt;</c>
-        *      <li>The first parameter if it matches the <c>bean</c> object 
type (for non-static inner classes)
+        *      <li>The first parameter if it matches the 
<c>enclosingInstance</c> object type (for non-static inner classes)
         * </ul>
         *
-        * @param executable The constructor or method to check.
+        * <p>
+        * If a parameter has a {@link org.apache.juneau.annotation.Named 
@Named} or {@code @Qualifier} annotation,
+        * the method checks for a bean with that specific name in the bean 
store.  Otherwise, it checks for an unnamed bean
+        * of the parameter type in the bean store, and if not found, checks 
the <c>otherBeans</c> parameter.
+        *
+        * @param ci The constructor to check.
         * @param beanStore The bean store to check for beans.
-        * @param bean The outer class instance for non-static inner class 
constructors/methods.
+        * @param enclosingInstance The outer class instance for non-static 
inner class constructors.
         *      If the first parameter type matches this object's type, it is 
used as the first parameter and not checked in the bean store.
         *      Can be <jk>null</jk> for regular classes or static inner 
classes.
-        * @return <jk>true</jk> if all required parameters are available in 
the bean store, <jk>false</jk> otherwise.
+        * @param otherBeans Optional additional bean instances to check if not 
found in the bean store.
+        *      These are checked after the bean store but before marking the 
parameter as missing.
+        * @return <jk>true</jk> if all required parameters are available in 
the bean store or <c>otherBeans</c>, <jk>false</jk> otherwise.
         */
-       public static boolean hasAllParameters(ExecutableInfo executable, 
BeanStore beanStore, Object bean) {
-               loop: for (int i = 0; i < executable.getParameterCount(); i++) {
-                       var pi = executable.getParameter(i);
-                       var pt = pi.getParameterType();
-                       if (i == 0 && nn(bean) && pt.isInstance(bean))
-                               continue loop;
-                       if (pt.is(Optional.class))
-                               continue loop;
-                       if (isCollectionType(pt))
-                               continue loop; // Collections and arrays are 
always satisfied (even if empty)
-                       var beanQualifier = pi.getResolvedQualifier();
-                       var ptc = pt.unwrap(Optional.class).inner();
-                       if ((beanQualifier == null && ! beanStore.hasBean(ptc)) 
|| (nn(beanQualifier) && ! beanStore.hasBean(ptc, beanQualifier)))
-                               return false;
-               }
-               return true;
+       public static boolean hasAllParameters(ConstructorInfo ci, BeanStore 
beanStore, Object enclosingInstance, Object...otherBeans) {
+               return ci.getParameters().stream().map(x -> 
hasConstructorParameter(x, beanStore, enclosingInstance, 
otherBeans)).allMatch(x -> x);
+       }
+
+       /**
+        * Returns <jk>true</jk> if the bean store has all required parameters 
for the specified method.
+        *
+        * <p>
+        * This method performs the same checks as {@link 
#getMissingParameters(MethodInfo, BeanStore, Object...)} but
+        * returns a boolean instead of a list of missing types.
+        *
+        * <p>
+        * The following parameter types are considered optional and are not 
checked:
+        * <ul>
+        *      <li>Parameters of type <c>Optional&lt;T&gt;</c>
+        *      <li>Parameters of type <c>T[]</c>, <c>List&lt;T&gt;</c>, 
<c>Set&lt;T&gt;</c>, or <c>Map&lt;String,T&gt;</c>
+        * </ul>
+        *
+        * <p>
+        * If a parameter has a {@link org.apache.juneau.annotation.Named 
@Named} or {@code @Qualifier} annotation,
+        * the method checks for a bean with that specific name in the bean 
store.  Otherwise, it checks for an unnamed bean
+        * of the parameter type in the bean store, and if not found, checks 
the <c>otherBeans</c> parameter.
+        *
+        * @param mi The method to check.
+        * @param beanStore The bean store to check for beans.
+        * @param otherBeans Optional additional bean instances to check if not 
found in the bean store.
+        *      These are checked after the bean store but before marking the 
parameter as missing.
+        * @return <jk>true</jk> if all required parameters are available in 
the bean store or <c>otherBeans</c>, <jk>false</jk> otherwise.
+        */
+       public static boolean hasAllParameters(MethodInfo mi, BeanStore 
beanStore, Object...otherBeans) {
+               return mi.getParameters().stream().map(x -> hasParameter(x, 
beanStore, otherBeans)).allMatch(x -> x);
        }
 
        /**
@@ -288,6 +385,7 @@ public class InjectUtils {
         * @param bean The object to inject beans into.
         * @param beanStore The bean store to resolve beans from.
         * @return The same bean instance (for method chaining).
+        * @throws ExecutableException If any field or method cannot be 
injected (e.g., required bean not found in the bean store).
         */
        public static <T> T injectBeans(T bean, BeanStore beanStore) {
                var type = ClassInfo.of(bean);
@@ -309,23 +407,24 @@ public class InjectUtils {
         * Resolves parameters from the bean store and invokes the specified 
constructor.
         *
         * <p>
-        * This is a convenience method that combines {@link 
#getParameters(ExecutableInfo, BeanStore, Object)} with
+        * This is a convenience method that combines {@link 
#getParameters(ConstructorInfo, BeanStore, Object, Object...)} with
         * {@link ConstructorInfo#newInstance(Object...)}.
         *
         * <p>
-        * The <c>outer</c> parameter is used for non-static inner class 
constructors.  If the first parameter type
+        * The <c>enclosingInstance</c> parameter is used for non-static inner 
class constructors.  If the first parameter type
         * matches this object's type, it is used as the first parameter value. 
 For regular constructors, this parameter
         * is ignored if it doesn't match the first parameter type.
         *
         * @param <T> The return type of the constructor.
         * @param constructor The constructor to invoke.
         * @param beanStore The bean store to resolve parameters from.
-        * @param outer The outer class instance for non-static inner classes 
(can be <jk>null</jk>).
+        * @param enclosingInstance The outer class instance for non-static 
inner classes (can be <jk>null</jk>).
+        * @param otherBeans Optional additional bean instances to use if not 
found in the bean store.
         * @return The result of invoking the constructor.
         * @throws ExecutableException If the constructor cannot be invoked or 
parameter resolution fails.
         */
-       public static <T> T invoke(ConstructorInfo constructor, BeanStore 
beanStore, Object outer) {
-               var params = getParameters(constructor, beanStore, outer);
+       public static <T> T invoke(ConstructorInfo constructor, BeanStore 
beanStore, Object enclosingInstance, Object...otherBeans) {
+               var params = getParameters(constructor, beanStore, 
enclosingInstance, otherBeans);
                return constructor.accessible().newInstance(params);
        }
 
@@ -333,7 +432,7 @@ public class InjectUtils {
         * Resolves the field value from the bean store and sets it on the 
specified object.
         *
         * <p>
-        * This method resolves the field value using the same logic as {@link 
#getParameters(ExecutableInfo, BeanStore, Object)},
+        * This method resolves the field value using the same logic as {@link 
#getParameters(MethodInfo, BeanStore, Object...)},
         * supporting single beans, {@code Optional}, arrays, {@code List}, 
{@code Set}, and {@code Map}.
         *
         * <p>
@@ -355,23 +454,22 @@ public class InjectUtils {
         * Resolves parameters from the bean store and invokes the specified 
method.
         *
         * <p>
-        * This is a convenience method that combines {@link 
#getParameters(ExecutableInfo, BeanStore, Object)} with
+        * This is a convenience method that combines {@link 
#getParameters(MethodInfo, BeanStore, Object...)} with
         * {@link MethodInfo#invoke(Object, Object...)}.
         *
         * <p>
         * The <c>bean</c> parameter is the object instance on which to invoke 
the method (or <jk>null</jk> for static methods).
-        * It is also passed as the <c>outer</c> parameter to {@link 
#getParameters(ExecutableInfo, BeanStore, Object)} for
-        * inner class handling.
         *
         * @param <T> The return type of the method.
         * @param method The method to invoke.
         * @param beanStore The bean store to resolve parameters from.
         * @param bean The object instance on which to invoke the method (or 
<jk>null</jk> for static methods).
+        * @param otherBeans Optional additional bean instances to use if not 
found in the bean store.
         * @return The result of invoking the method.
         * @throws ExecutableException If the method cannot be invoked or 
parameter resolution fails.
         */
-       public static <T> T invoke(MethodInfo method, BeanStore beanStore, 
Object bean) {
-               var params = getParameters(method, beanStore, bean);
+       public static <T> T invoke(MethodInfo method, BeanStore beanStore, 
Object bean, Object...otherBeans) {
+               var params = getParameters(method, beanStore, otherBeans);
                return method.accessible().invoke(bean, params);
        }
 
@@ -472,11 +570,12 @@ public class InjectUtils {
                return null;
        }
 
-
-
-       
//-----------------------------------------------------------------------------------------------------------------
-       // Helper methods
-       
//-----------------------------------------------------------------------------------------------------------------
+       private static Object getConstructorParameter(ParameterInfo pi, 
BeanStore beanStore, Object enclosingInstance, Object...otherBeans) {
+               var pt = pi.getParameterType();
+               if (pi.getIndex() == 0 && nn(enclosingInstance) && 
pt.isInstance(enclosingInstance))
+                       return enclosingInstance;
+               return getParameter(pi, beanStore, otherBeans);
+       }
 
        /**
         * Extracts the element or value type from a collection, array, or map 
type.
@@ -572,7 +671,101 @@ public class InjectUtils {
                // Handle single bean
                var ptc = ptUnwrapped.inner();
                var o2 = beanQualifier == null ? beanStore.getBean(ptc) : 
beanStore.getBean(ptc, beanQualifier);
-               return fieldType.is(Optional.class) ? o2 : o2.orElse(null);
+               
+               if (fieldType.is(Optional.class))
+                       return o2;
+               if (o2.isPresent())
+                       return o2.get();
+               
+               throw exex("Could not resolve value for field {0}", field);
+       }
+
+       private static String getMissingConstructorParameter(ParameterInfo pi, 
BeanStore beanStore, Object enclosingInstance, Object...otherBeans) {
+               var pt = pi.getParameterType();
+               if (pi.getIndex() == 0 && nn(enclosingInstance) && 
pt.isInstance(enclosingInstance))
+                       return null;
+               return getMissingParameter(pi, beanStore, otherBeans);
+       }
+
+       private static String getMissingParameter(ParameterInfo pi, BeanStore 
beanStore, Object...otherBeans) {
+               var pt = pi.getParameterType();
+               if (pt.is(Optional.class) || isCollectionType(pt)) // 
Optional/Collection/array types are always satisfied (even if empty).
+                       return null;
+               var bq = pi.getResolvedQualifier();  // Use @Named/@Qualified 
for bean injection
+               if (nn(bq)) {
+                       if (! beanStore.hasBean(pt.inner(), bq))
+                               return pt.getNameSimple() + '@' + bq;
+                       return null;
+               }
+               if (beanStore.hasBean(pt.inner()))
+                       return null;
+               for (var o : otherBeans)
+                       if (pi.canAccept(o))
+                               return null;
+               return pt.getNameSimple();
+       }
+
+       private static Object getParameter(ParameterInfo pi, BeanStore 
beanStore, Object...otherBeans) {
+               var pt = pi.getParameterType();
+               var bq = pi.getResolvedQualifier();
+               var ptu = pt.unwrap(Optional.class);
+
+               // Handle collections and arrays
+               var collectionValue = getCollectionValue(pi, ptu, beanStore, 
bq);
+               if (nn(collectionValue))
+                       return pt.is(Optional.class) ? 
Optional.of(collectionValue) : collectionValue;
+
+               // Handle single bean
+               var ptc = ptu.inner();
+
+               Optional<Object> r;
+
+               if (nn(bq)) {
+                       r = beanStore.getBean(ptc, bq);
+               } else {
+                       r = beanStore.getBean(ptc);
+                       if (r.isEmpty()) {
+                               for (var r2 : otherBeans)
+                                       if (pi.canAccept(r2)) {
+                                               r = opt(r2);
+                                               break;
+                                       }
+                       }
+               }
+
+               if (pt.is(Optional.class))
+                       return r;
+               if (r.isPresent())
+                       return r.get();
+
+               throw exex("Could not resolve value for parameter {0}", pi);
+       }
+
+       private static boolean hasConstructorParameter(ParameterInfo pi, 
BeanStore beanStore, Object enclosingInstance, Object...otherBeans) {
+               var pt = pi.getParameterType();
+               if (pi.getIndex() == 0 && nn(enclosingInstance) && 
pt.isInstance(enclosingInstance))
+                       return true;
+               return hasParameter(pi, beanStore, otherBeans);
+       }
+
+       private static boolean hasParameter(ParameterInfo pi, BeanStore 
beanStore, Object...otherBeans) {
+               var pt = pi.getParameterType();
+
+               if (pt.is(Optional.class) || isCollectionType(pt)) // 
Optional/Collection/array types are always satisfied (even if empty).
+                       return true;
+
+               var bq = pi.getResolvedQualifier();
+
+               if (nn(bq))
+                       return beanStore.hasBean(pt.inner(), bq);
+
+               if (beanStore.hasBean(pt.inner()))
+                       return true;
+
+               for (var o : otherBeans)
+                       if (pi.canAccept(o))
+                               return true;
+               return false;
        }
 
        /**
diff --git 
a/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/reflect/ExecutableInfo.java
 
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/reflect/ExecutableInfo.java
index 9cdd4ff67e..6cf80ec1cb 100644
--- 
a/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/reflect/ExecutableInfo.java
+++ 
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/reflect/ExecutableInfo.java
@@ -272,6 +272,8 @@ public abstract class ExecutableInfo extends AccessibleInfo 
{
         */
        public final ClassInfo getDeclaringClass() { return declaringClass; }
 
+       public final boolean isDeclaringClass(ClassInfo value) { return 
eq(declaringClass, value); }
+
        /**
         * Returns the exception types on this executable.
         *
diff --git 
a/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/reflect/MethodInfo.java
 
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/reflect/MethodInfo.java
index 455715cf06..456e97ffd6 100644
--- 
a/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/reflect/MethodInfo.java
+++ 
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/reflect/MethodInfo.java
@@ -492,6 +492,10 @@ public class MethodInfo extends ExecutableInfo implements 
Comparable<MethodInfo>
                return hasAllParameters(requiredParam);
        }
 
+       public boolean hasParameter(Object parameter) {
+               return getParameters().stream().anyMatch(x -> 
x.canAccept(parameter));
+       }
+
        /**
         * Returns <jk>true</jk> if this method has this return type.
         *
diff --git 
a/juneau-utest/src/test/java/org/apache/juneau/commons/inject/InjectUtils_Test.java
 
b/juneau-utest/src/test/java/org/apache/juneau/commons/inject/InjectUtils_Test.java
index 98c0ef4e6d..238526d4fa 100644
--- 
a/juneau-utest/src/test/java/org/apache/juneau/commons/inject/InjectUtils_Test.java
+++ 
b/juneau-utest/src/test/java/org/apache/juneau/commons/inject/InjectUtils_Test.java
@@ -25,6 +25,7 @@ import java.util.*;
 import org.apache.juneau.*;
 import org.apache.juneau.annotation.Named;
 import org.apache.juneau.commons.reflect.*;
+import org.apache.juneau.commons.reflect.ExecutableException;
 import org.junit.jupiter.api.*;
 
 class InjectUtils_Test extends TestBase {
@@ -249,9 +250,7 @@ class InjectUtils_Test extends TestBase {
        @Test
        void b02_getParams_singleBeanNotFound() throws Exception {
                var constructor = 
ClassInfo.of(TestClass1.class).getPublicConstructor(x -> 
x.hasParameterTypes(TestService.class)).get();
-               var params = getParameters(constructor, beanStore, null);
-               assertEquals(1, params.length);
-               assertNull(params[0]);
+               assertThrows(ExecutableException.class, () -> 
getParameters(constructor, beanStore, null));
        }
 
        @Test
@@ -571,7 +570,8 @@ class InjectUtils_Test extends TestBase {
                        .findFirst();
                assertTrue(constructorOpt.isPresent(), "Constructor should be 
found. Available: " + constructors);
                var constructor = constructorOpt.get();
-               var params = getParameters(constructor, beanStore, null);
+               // Pass 'this' as enclosingInstance for the inner class outer 
parameter
+               var params = getParameters(constructor, beanStore, this);
                assertEquals(2, params.length); // Outer class + Optional 
parameter
                assertTrue(params[1] instanceof Optional);
                var opt = (Optional<List<TestService>>) params[1];
@@ -645,7 +645,8 @@ class InjectUtils_Test extends TestBase {
                        .findFirst();
                assertTrue(constructorOpt.isPresent(), "Constructor should be 
found. Available: " + constructors);
                var constructor = constructorOpt.get();
-               var params = getParameters(constructor, beanStore, null);
+               // Pass 'this' as enclosingInstance for the inner class outer 
parameter
+               var params = getParameters(constructor, beanStore, this);
                assertEquals(2, params.length); // Outer class + Optional 
parameter
                assertTrue(params[1] instanceof Optional);
                var opt = (Optional<TestService[]>) params[1];
@@ -672,11 +673,10 @@ class InjectUtils_Test extends TestBase {
                        .findFirst();
                assertTrue(constructorOpt.isPresent(), "Constructor should be 
found. Available: " + constructors);
                var constructor = constructorOpt.get();
-               var params = getParameters(constructor, beanStore, null);
-               assertEquals(2, params.length); // Outer class + List parameter
                // Since getElementType returns null for raw List, it won't be 
treated as a collection
-               // and will try to look up a bean of type List, which won't be 
found
-               assertNull(params[1]);
+               // and will try to look up a bean of type List, which won't be 
found - should throw exception
+               // Pass 'this' as enclosingInstance for the inner class outer 
parameter
+               assertThrows(ExecutableException.class, () -> 
getParameters(constructor, beanStore, this));
        }
 
        // Test line 322, 324, 327: getElementType - Map with wrong key type or 
non-Class value type
@@ -695,10 +695,8 @@ class InjectUtils_Test extends TestBase {
                        .findFirst();
                assertTrue(constructorOpt.isPresent(), "Constructor should be 
found. Available: " + constructors);
                var constructor = constructorOpt.get();
-               var params = getParameters(constructor, beanStore, null);
-               assertEquals(2, params.length); // Outer class + Map parameter
-               // Since key type is not String, getElementType returns null
-               assertNull(params[1]);
+               // Since key type is not String, getElementType returns null - 
should throw exception
+               assertThrows(ExecutableException.class, () -> 
getParameters(constructor, beanStore, null));
        }
 
        // Test line 322, 324, 327: getElementType - Map with non-Class value 
type
@@ -717,10 +715,8 @@ class InjectUtils_Test extends TestBase {
                        .findFirst();
                assertTrue(constructorOpt.isPresent(), "Constructor should be 
found. Available: " + constructors);
                var constructor = constructorOpt.get();
-               var params = getParameters(constructor, beanStore, null);
-               assertEquals(2, params.length); // Outer class + Map parameter
-               // Since value type is List<TestService> (ParameterizedType, 
not Class), getElementType returns null
-               assertNull(params[1]);
+               // Since value type is List<TestService> (ParameterizedType, 
not Class), getElementType returns null - should throw exception
+               assertThrows(ExecutableException.class, () -> 
getParameters(constructor, beanStore, null));
        }
 
        // Test line 324: getElementType - Map with raw type (no generic 
parameters)
@@ -739,11 +735,9 @@ class InjectUtils_Test extends TestBase {
                        .findFirst();
                assertTrue(constructorOpt.isPresent(), "Constructor should be 
found. Available: " + constructors);
                var constructor = constructorOpt.get();
-               var params = getParameters(constructor, beanStore, null);
-               assertEquals(2, params.length); // Outer class + Map parameter
                // Since getElementType returns null for raw Map, it won't be 
treated as a collection
-               // and will try to look up a bean of type Map, which won't be 
found
-               assertNull(params[1]);
+               // and will try to look up a bean of type Map, which won't be 
found - should throw exception
+               assertThrows(ExecutableException.class, () -> 
getParameters(constructor, beanStore, null));
        }
 
        // Test line 333: getElementType - return null for unsupported types
@@ -763,11 +757,9 @@ class InjectUtils_Test extends TestBase {
                        .findFirst();
                assertTrue(constructorOpt.isPresent(), "Constructor should be 
found. Available: " + constructors);
                var constructor = constructorOpt.get();
-               var params = getParameters(constructor, beanStore, null);
-               assertEquals(2, params.length); // Outer class + Collection 
parameter
                // Collection is not List/Set/Map, so isCollectionType returns 
false, so getCollectionValue returns null
-               // Then it tries to look up a bean of type Collection, which 
won't be found
-               assertNull(params[1]);
+               // Then it tries to look up a bean of type Collection, which 
won't be found - should throw exception
+               assertThrows(ExecutableException.class, () -> 
getParameters(constructor, beanStore, null));
        }
 
        // Test line 324: getElementType - Optional<SomeClass> where SomeClass 
is not a ParameterizedType
@@ -789,7 +781,8 @@ class InjectUtils_Test extends TestBase {
                        .findFirst();
                assertTrue(constructorOpt.isPresent(), "Constructor should be 
found. Available: " + constructors);
                var constructor = constructorOpt.get();
-               var params = getParameters(constructor, beanStore, null);
+               // Pass 'this' as enclosingInstance for the inner class outer 
parameter
+               var params = getParameters(constructor, beanStore, this);
                assertEquals(2, params.length); // Outer class + Optional<List> 
parameter
                // Optional<List> (raw) - after unwrapping Optional, we get 
List (raw)
                // isCollectionType(List.class) returns true, so 
getCollectionValue is called
@@ -828,7 +821,8 @@ class InjectUtils_Test extends TestBase {
                        .findFirst();
                assertTrue(constructorOpt.isPresent(), "Constructor should be 
found. Available: " + constructors);
                var constructor = constructorOpt.get();
-               var params = getParameters(constructor, beanStore, null);
+               // Pass 'this' as enclosingInstance for the inner class outer 
parameter
+               var params = getParameters(constructor, beanStore, this);
                assertEquals(2, params.length); // Outer class + Optional<Map> 
parameter
                // This should work normally: Optional unwrapping happens, then 
Map is populated
                assertTrue(params[1] instanceof Optional);
@@ -863,11 +857,9 @@ class InjectUtils_Test extends TestBase {
                        .findFirst();
                assertTrue(constructorOpt.isPresent(), "Constructor should be 
found. Available: " + constructors);
                var constructor = constructorOpt.get();
-               var params = getParameters(constructor, beanStore, null);
-               assertEquals(2, params.length); // Outer class + Map parameter
                // Map<Integer, TestService> - getElementType returns null (key 
is not String)
-               // So getCollectionValue returns null early (line 383), never 
reaching line 416
-               assertNull(params[1]);
+               // So getCollectionValue returns null early, then tries to look 
up bean - should throw exception
+               assertThrows(ExecutableException.class, () -> 
getParameters(constructor, beanStore, null));
        }
 
        // Test line 437: getCollectionValue - fallback return null
@@ -898,11 +890,9 @@ class InjectUtils_Test extends TestBase {
                        .findFirst();
                assertTrue(constructorOpt.isPresent(), "Constructor should be 
found. Available: " + constructors);
                var constructor = constructorOpt.get();
-               var params = getParameters(constructor, beanStore, null);
-               assertEquals(2, params.length); // Outer class + Collection 
parameter
                // Collection is not List/Set/Map, so isCollectionType returns 
false, so getCollectionValue returns null early
-               // Then it tries to look up a bean of type Collection, which 
won't be found
-               assertNull(params[1]);
+               // Then it tries to look up a bean of type Collection, which 
won't be found - should throw exception
+               assertThrows(ExecutableException.class, () -> 
getParameters(constructor, beanStore, null));
        }
 
        
//====================================================================================================
@@ -1044,7 +1034,11 @@ class InjectUtils_Test extends TestBase {
        @Test
        void f01_injectBeans_fieldSingleBean() {
                var service = new TestService("test1");
+               var service1 = new TestService("service1");
+               var another = new AnotherService(42);
                beanStore.addBean(TestService.class, service);
+               beanStore.addBean(TestService.class, service1, "service1"); // 
Required for namedService
+               beanStore.addBean(AnotherService.class, another); // Required 
for anotherService
                var bean = new TestFieldInjection();
                injectBeans(bean, beanStore);
                assertSame(service, bean.service);
@@ -1053,7 +1047,11 @@ class InjectUtils_Test extends TestBase {
        @Test
        void f02_injectBeans_fieldWithAutowired() {
                var service = new TestService("test1");
+               var service1 = new TestService("service1");
+               var another = new AnotherService(42);
                beanStore.addBean(TestService.class, service);
+               beanStore.addBean(TestService.class, service1, "service1"); // 
Required for namedService
+               beanStore.addBean(AnotherService.class, another); // Required 
for anotherService
                var bean = new TestFieldInjection();
                injectBeans(bean, beanStore);
                assertSame(service, bean.service2);
@@ -1062,7 +1060,11 @@ class InjectUtils_Test extends TestBase {
        @Test
        void f03_injectBeans_fieldOptionalFound() {
                var service = new TestService("test1");
+               var service1 = new TestService("service1");
+               var another = new AnotherService(42);
                beanStore.addBean(TestService.class, service);
+               beanStore.addBean(TestService.class, service1, "service1"); // 
Required for namedService
+               beanStore.addBean(AnotherService.class, another); // Required 
for anotherService
                var bean = new TestFieldInjection();
                injectBeans(bean, beanStore);
                assertTrue(bean.optionalService.isPresent());
@@ -1071,49 +1073,72 @@ class InjectUtils_Test extends TestBase {
 
        @Test
        void f04_injectBeans_fieldOptionalNotFound() {
+               var service1 = new TestService("service1");
+               var another = new AnotherService(42);
+               beanStore.addBean(TestService.class, service1, "service1"); // 
Required for namedService
+               beanStore.addBean(AnotherService.class, another); // Required 
for anotherService
+               // Note: No unnamed TestService, so service and service2 will 
fail, but optionalService will be empty
                var bean = new TestFieldInjection();
-               injectBeans(bean, beanStore);
-               assertFalse(bean.optionalService.isPresent());
+               // This will throw because service and service2 are required
+               assertThrows(ExecutableException.class, () -> injectBeans(bean, 
beanStore));
        }
 
        @Test
        void f05_injectBeans_fieldList() {
                var service1 = new TestService("test1");
                var service2 = new TestService("test2");
+               var service1Named = new TestService("service1");
+               var another = new AnotherService(42);
                beanStore.addBean(TestService.class, service1);
                beanStore.addBean(TestService.class, service2, "service2");
+               beanStore.addBean(TestService.class, service1Named, 
"service1"); // Required for namedService
+               beanStore.addBean(AnotherService.class, another); // Required 
for anotherService
                var bean = new TestFieldInjection();
                injectBeans(bean, beanStore);
                assertNotNull(bean.serviceList);
-               assertEquals(2, bean.serviceList.size());
+               // getBeansOfType returns all TestService beans (named and 
unnamed), so we get 3: service1, service2, and service1Named
+               assertEquals(3, bean.serviceList.size());
                assertTrue(bean.serviceList.contains(service1));
                assertTrue(bean.serviceList.contains(service2));
+               assertTrue(bean.serviceList.contains(service1Named));
        }
 
        @Test
        void f06_injectBeans_fieldSet() {
                var service1 = new TestService("test1");
                var service2 = new TestService("test2");
+               var service1Named = new TestService("service1");
+               var another = new AnotherService(42);
                beanStore.addBean(TestService.class, service1);
                beanStore.addBean(TestService.class, service2, "service2");
+               beanStore.addBean(TestService.class, service1Named, 
"service1"); // Required for namedService
+               beanStore.addBean(AnotherService.class, another); // Required 
for anotherService
                var bean = new TestFieldInjection();
                injectBeans(bean, beanStore);
                assertNotNull(bean.serviceSet);
-               assertEquals(2, bean.serviceSet.size());
+               // getBeansOfType returns all TestService beans (named and 
unnamed), so we get 3: service1, service2, and service1Named
+               assertEquals(3, bean.serviceSet.size());
                assertTrue(bean.serviceSet.contains(service1));
                assertTrue(bean.serviceSet.contains(service2));
+               assertTrue(bean.serviceSet.contains(service1Named));
        }
 
        @Test
        void f07_injectBeans_fieldMap() {
                var service1 = new TestService("test1");
                var service2 = new TestService("test2");
+               var unnamedService = new TestService("unnamed");
+               var another = new AnotherService(42);
+               beanStore.addBean(TestService.class, unnamedService); // 
Required for service and service2 fields
                beanStore.addBean(TestService.class, service1, "service1");
                beanStore.addBean(TestService.class, service2, "service2");
+               beanStore.addBean(AnotherService.class, another); // Required 
for anotherService
                var bean = new TestFieldInjection();
                injectBeans(bean, beanStore);
                assertNotNull(bean.serviceMap);
-               assertEquals(2, bean.serviceMap.size());
+               // getBeansOfType returns all TestService beans (named and 
unnamed), so we get 3: unnamedService, service1, and service2
+               assertEquals(3, bean.serviceMap.size());
+               assertSame(unnamedService, bean.serviceMap.get("")); // Unnamed 
bean has empty string as key
                assertSame(service1, bean.serviceMap.get("service1"));
                assertSame(service2, bean.serviceMap.get("service2"));
        }
@@ -1122,22 +1147,33 @@ class InjectUtils_Test extends TestBase {
        void f08_injectBeans_fieldArray() {
                var service1 = new TestService("test1");
                var service2 = new TestService("test2");
+               var service1Named = new TestService("service1");
+               var another = new AnotherService(42);
                beanStore.addBean(TestService.class, service1);
                beanStore.addBean(TestService.class, service2, "service2");
+               beanStore.addBean(TestService.class, service1Named, 
"service1"); // Required for namedService
+               beanStore.addBean(AnotherService.class, another); // Required 
for anotherService
                var bean = new TestFieldInjection();
                injectBeans(bean, beanStore);
                assertNotNull(bean.serviceArray);
-               assertEquals(2, bean.serviceArray.length);
-               assertTrue(bean.serviceArray[0].equals(service1) || 
bean.serviceArray[0].equals(service2));
-               assertTrue(bean.serviceArray[1].equals(service1) || 
bean.serviceArray[1].equals(service2));
+               // getBeansOfType returns all TestService beans (named and 
unnamed), so we get 3: service1, service2, and service1Named
+               assertEquals(3, bean.serviceArray.length);
+               assertTrue(bean.serviceArray[0].equals(service1) || 
bean.serviceArray[0].equals(service2) || 
bean.serviceArray[0].equals(service1Named));
+               assertTrue(bean.serviceArray[1].equals(service1) || 
bean.serviceArray[1].equals(service2) || 
bean.serviceArray[1].equals(service1Named));
+               assertTrue(bean.serviceArray[2].equals(service1) || 
bean.serviceArray[2].equals(service2) || 
bean.serviceArray[2].equals(service1Named));
        }
 
        @Test
        void f09_injectBeans_fieldNamedBean() {
                var service1 = new TestService("test1");
                var service2 = new TestService("test2");
+               var another = new AnotherService(42);
                beanStore.addBean(TestService.class, service1, "service1");
                beanStore.addBean(TestService.class, service2, "service2");
+               beanStore.addBean(AnotherService.class, another); // Required 
for anotherService
+               // Also need unnamed TestService for service and service2 fields
+               var unnamedService = new TestService("unnamed");
+               beanStore.addBean(TestService.class, unnamedService);
                var bean = new TestFieldInjection();
                injectBeans(bean, beanStore);
                assertSame(service1, bean.namedService);
@@ -1146,7 +1182,11 @@ class InjectUtils_Test extends TestBase {
        @Test
        void f10_injectBeans_fieldFinalSkipped() {
                var service = new TestService("test1");
+               var service1 = new TestService("service1");
+               var another = new AnotherService(42);
                beanStore.addBean(TestService.class, service);
+               beanStore.addBean(TestService.class, service1, "service1"); // 
Required for namedService
+               beanStore.addBean(AnotherService.class, another); // Required 
for anotherService
                var bean = new TestFieldInjection();
                injectBeans(bean, beanStore);
                // Final field should not be injected (remains null)
@@ -1156,7 +1196,11 @@ class InjectUtils_Test extends TestBase {
        @Test
        void f11_injectBeans_fieldUnannotatedSkipped() {
                var service = new TestService("test1");
+               var service1 = new TestService("service1");
+               var another = new AnotherService(42);
                beanStore.addBean(TestService.class, service);
+               beanStore.addBean(TestService.class, service1, "service1"); // 
Required for namedService
+               beanStore.addBean(AnotherService.class, another); // Required 
for anotherService
                var bean = new TestFieldInjection();
                injectBeans(bean, beanStore);
                // Unannotated field should not be injected
@@ -1166,16 +1210,17 @@ class InjectUtils_Test extends TestBase {
        @Test
        void f12_injectBeans_fieldNotFound() {
                var bean = new TestFieldInjection();
-               injectBeans(bean, beanStore);
-               // Field should remain null if bean not found
-               assertNull(bean.service);
+               // injectBeans now throws exception if required bean is not 
found
+               assertThrows(ExecutableException.class, () -> injectBeans(bean, 
beanStore));
        }
 
        @Test
        void f13_injectBeans_fieldMultipleTypes() {
                var service = new TestService("test1");
+               var service1 = new TestService("service1");
                var another = new AnotherService(42);
                beanStore.addBean(TestService.class, service);
+               beanStore.addBean(TestService.class, service1, "service1"); // 
Required for namedService field
                beanStore.addBean(AnotherService.class, another);
                var bean = new TestFieldInjection();
                injectBeans(bean, beanStore);
@@ -1186,7 +1231,9 @@ class InjectUtils_Test extends TestBase {
        @Test
        void f14_injectBeans_methodSingleBean() {
                var service = new TestService("test1");
+               var service1 = new TestService("service1");
                beanStore.addBean(TestService.class, service);
+               beanStore.addBean(TestService.class, service1, "service1"); // 
Required for method8
                var bean = new TestMethodInjection();
                injectBeans(bean, beanStore);
                assertTrue(bean.method1Called);
@@ -1196,7 +1243,9 @@ class InjectUtils_Test extends TestBase {
        @Test
        void f15_injectBeans_methodWithAutowired() {
                var service = new TestService("test1");
+               var service1 = new TestService("service1");
                beanStore.addBean(TestService.class, service);
+               beanStore.addBean(TestService.class, service1, "service1"); // 
Required for method8
                var bean = new TestMethodInjection();
                injectBeans(bean, beanStore);
                assertTrue(bean.method2Called);
@@ -1206,7 +1255,9 @@ class InjectUtils_Test extends TestBase {
        @Test
        void f16_injectBeans_methodOptional() {
                var service = new TestService("test1");
+               var service1 = new TestService("service1");
                beanStore.addBean(TestService.class, service);
+               beanStore.addBean(TestService.class, service1, "service1"); // 
Required for method8
                var bean = new TestMethodInjection();
                injectBeans(bean, beanStore);
                assertTrue(bean.method3Called);
@@ -1216,8 +1267,10 @@ class InjectUtils_Test extends TestBase {
        void f17_injectBeans_methodList() {
                var service1 = new TestService("test1");
                var service2 = new TestService("test2");
+               var service1Named = new TestService("service1");
                beanStore.addBean(TestService.class, service1);
                beanStore.addBean(TestService.class, service2, "service2");
+               beanStore.addBean(TestService.class, service1Named, 
"service1"); // Required for method8
                var bean = new TestMethodInjection();
                injectBeans(bean, beanStore);
                assertTrue(bean.method4Called);
@@ -1227,8 +1280,10 @@ class InjectUtils_Test extends TestBase {
        void f18_injectBeans_methodSet() {
                var service1 = new TestService("test1");
                var service2 = new TestService("test2");
+               var service1Named = new TestService("service1");
                beanStore.addBean(TestService.class, service1);
                beanStore.addBean(TestService.class, service2, "service2");
+               beanStore.addBean(TestService.class, service1Named, 
"service1"); // Required for method8
                var bean = new TestMethodInjection();
                injectBeans(bean, beanStore);
                assertTrue(bean.method5Called);
@@ -1238,6 +1293,8 @@ class InjectUtils_Test extends TestBase {
        void f19_injectBeans_methodMap() {
                var service1 = new TestService("test1");
                var service2 = new TestService("test2");
+               var unnamedService = new TestService("unnamed");
+               beanStore.addBean(TestService.class, unnamedService); // 
Required for method1
                beanStore.addBean(TestService.class, service1, "service1");
                beanStore.addBean(TestService.class, service2, "service2");
                var bean = new TestMethodInjection();
@@ -1249,8 +1306,10 @@ class InjectUtils_Test extends TestBase {
        void f20_injectBeans_methodArray() {
                var service1 = new TestService("test1");
                var service2 = new TestService("test2");
+               var service1Named = new TestService("service1");
                beanStore.addBean(TestService.class, service1);
                beanStore.addBean(TestService.class, service2, "service2");
+               beanStore.addBean(TestService.class, service1Named, 
"service1"); // Required for method8
                var bean = new TestMethodInjection();
                injectBeans(bean, beanStore);
                assertTrue(bean.method7Called);
@@ -1260,6 +1319,8 @@ class InjectUtils_Test extends TestBase {
        void f21_injectBeans_methodNamedBean() {
                var service1 = new TestService("test1");
                var service2 = new TestService("test2");
+               var unnamedService = new TestService("unnamed");
+               beanStore.addBean(TestService.class, unnamedService); // 
Required for method1
                beanStore.addBean(TestService.class, service1, "service1");
                beanStore.addBean(TestService.class, service2, "service2");
                var bean = new TestMethodInjection();
@@ -1269,6 +1330,10 @@ class InjectUtils_Test extends TestBase {
 
        @Test
        void f22_injectBeans_methodZeroParameters() {
+               var unnamedService = new TestService("unnamed");
+               var service1Named = new TestService("service1");
+               beanStore.addBean(TestService.class, unnamedService); // 
Required for method1
+               beanStore.addBean(TestService.class, service1Named, 
"service1"); // Required for method8
                var bean = new TestMethodInjection();
                injectBeans(bean, beanStore);
                assertTrue(bean.method9Called);
@@ -1277,7 +1342,9 @@ class InjectUtils_Test extends TestBase {
        @Test
        void f23_injectBeans_methodReturnValueIgnored() {
                var service = new TestService("test1");
+               var service1 = new TestService("service1");
                beanStore.addBean(TestService.class, service);
+               beanStore.addBean(TestService.class, service1, "service1"); // 
Required for method8
                var bean = new TestMethodInjection();
                injectBeans(bean, beanStore);
                assertTrue(bean.method10Called);
@@ -1305,7 +1372,9 @@ class InjectUtils_Test extends TestBase {
        @Test
        void f25_injectBeans_methodWithTypeParamsSkipped() {
                var service = new TestService("test1");
+               var service1 = new TestService("service1");
                beanStore.addBean(TestService.class, service);
+               beanStore.addBean(TestService.class, service1, "service1"); // 
Required for method8
                var bean = new TestMethodInjection();
                injectBeans(bean, beanStore);
                // Method with type parameters should be skipped
@@ -1315,7 +1384,9 @@ class InjectUtils_Test extends TestBase {
        @Test
        void f26_injectBeans_methodUnannotatedSkipped() {
                var service = new TestService("test1");
+               var service1 = new TestService("service1");
                beanStore.addBean(TestService.class, service);
+               beanStore.addBean(TestService.class, service1, "service1"); // 
Required for method8
                var bean = new TestMethodInjection();
                injectBeans(bean, beanStore);
                // Unannotated method should be skipped
@@ -1325,7 +1396,9 @@ class InjectUtils_Test extends TestBase {
        @Test
        void f27_injectBeans_methodStatic() {
                var service = new TestService("test1");
+               var service1 = new TestService("service1");
                beanStore.addBean(TestService.class, service);
+               beanStore.addBean(TestService.class, service1, "service1"); // 
Required for method8
                var bean = new TestMethodInjection();
                // Static methods should be called (with null instance)
                // Just verify no exception is thrown
@@ -1336,7 +1409,11 @@ class InjectUtils_Test extends TestBase {
        @Test
        void f28_injectBeans_returnsSameInstance() {
                var service = new TestService("test1");
+               var service1 = new TestService("service1");
+               var another = new AnotherService(42);
                beanStore.addBean(TestService.class, service);
+               beanStore.addBean(TestService.class, service1, "service1"); // 
Required for namedService field
+               beanStore.addBean(AnotherService.class, another); // Required 
for anotherService field
                var bean = new TestFieldInjection();
                var result = injectBeans(bean, beanStore);
                // Should return the same instance for method chaining
@@ -1364,38 +1441,70 @@ class InjectUtils_Test extends TestBase {
 
        @Test
        void f30_injectBeans_fieldListEmpty() {
+               var service = new TestService("test1");
+               var service1 = new TestService("service1");
+               var another = new AnotherService(42);
+               beanStore.addBean(TestService.class, service); // Required for 
service and service2 fields
+               beanStore.addBean(TestService.class, service1, "service1"); // 
Required for namedService field
+               beanStore.addBean(AnotherService.class, another); // Required 
for anotherService field
                var bean = new TestFieldInjection();
                injectBeans(bean, beanStore);
-               // Empty list should be created
+               // List will be populated with all TestService beans (service 
and service1)
                assertNotNull(bean.serviceList);
-               assertTrue(bean.serviceList.isEmpty());
+               assertEquals(2, bean.serviceList.size());
+               assertTrue(bean.serviceList.contains(service));
+               assertTrue(bean.serviceList.contains(service1));
        }
 
        @Test
        void f31_injectBeans_fieldSetEmpty() {
+               var service = new TestService("test1");
+               var service1 = new TestService("service1");
+               var another = new AnotherService(42);
+               beanStore.addBean(TestService.class, service); // Required for 
service and service2 fields
+               beanStore.addBean(TestService.class, service1, "service1"); // 
Required for namedService field
+               beanStore.addBean(AnotherService.class, another); // Required 
for anotherService field
                var bean = new TestFieldInjection();
                injectBeans(bean, beanStore);
-               // Empty set should be created
+               // Set will be populated with all TestService beans (service 
and service1)
                assertNotNull(bean.serviceSet);
-               assertTrue(bean.serviceSet.isEmpty());
+               assertEquals(2, bean.serviceSet.size());
+               assertTrue(bean.serviceSet.contains(service));
+               assertTrue(bean.serviceSet.contains(service1));
        }
 
        @Test
        void f32_injectBeans_fieldMapEmpty() {
+               var service = new TestService("test1");
+               var service1 = new TestService("service1");
+               var another = new AnotherService(42);
+               beanStore.addBean(TestService.class, service); // Required for 
service and service2 fields
+               beanStore.addBean(TestService.class, service1, "service1"); // 
Required for namedService field
+               beanStore.addBean(AnotherService.class, another); // Required 
for anotherService field
                var bean = new TestFieldInjection();
                injectBeans(bean, beanStore);
-               // Empty map should be created
+               // Map will be populated with all TestService beans (service 
with empty string key, service1 with "service1" key)
                assertNotNull(bean.serviceMap);
-               assertTrue(bean.serviceMap.isEmpty());
+               assertEquals(2, bean.serviceMap.size());
+               assertSame(service, bean.serviceMap.get("")); // Unnamed bean 
has empty string as key
+               assertSame(service1, bean.serviceMap.get("service1"));
        }
 
        @Test
        void f33_injectBeans_fieldArrayEmpty() {
+               var service = new TestService("test1");
+               var service1 = new TestService("service1");
+               var another = new AnotherService(42);
+               beanStore.addBean(TestService.class, service); // Required for 
service and service2 fields
+               beanStore.addBean(TestService.class, service1, "service1"); // 
Required for namedService field
+               beanStore.addBean(AnotherService.class, another); // Required 
for anotherService field
                var bean = new TestFieldInjection();
                injectBeans(bean, beanStore);
-               // Empty array should be created
+               // Array will be populated with all TestService beans (service 
and service1)
                assertNotNull(bean.serviceArray);
-               assertEquals(0, bean.serviceArray.length);
+               assertEquals(2, bean.serviceArray.length);
+               assertTrue(bean.serviceArray[0].equals(service) || 
bean.serviceArray[0].equals(service1));
+               assertTrue(bean.serviceArray[1].equals(service) || 
bean.serviceArray[1].equals(service1));
        }
 
        // Test lines 436-442: getCollectionValue - when 
pi.getParameterizedType() is not ParameterizedType
@@ -1421,7 +1530,8 @@ class InjectUtils_Test extends TestBase {
                        .findFirst();
                assertTrue(constructorOpt.isPresent(), "Constructor should be 
found. Available: " + constructors);
                var constructor = constructorOpt.get();
-               var params = getParameters(constructor, beanStore, null);
+               // Pass 'this' as enclosingInstance for the inner class outer 
parameter
+               var params = getParameters(constructor, beanStore, this);
                assertEquals(2, params.length); // Outer class + Map parameter
                // This should work: pt.innerType() provides the 
ParameterizedType even if pi.getParameterizedType() doesn't
                assertTrue(params[1] instanceof Map);
@@ -1430,5 +1540,119 @@ class InjectUtils_Test extends TestBase {
                assertSame(service1, map.get("service1"));
                assertSame(service2, map.get("service2"));
        }
+
+       
//====================================================================================================
+       // otherBeans parameter tests
+       
//====================================================================================================
+
+       @Test
+       void g01_getMissingParams_otherBeans() throws Exception {
+               var service = new TestService("test1");
+               // Service not in bean store, but provided as otherBeans
+               var constructor = 
ClassInfo.of(TestClass1.class).getPublicConstructor(x -> 
x.hasParameterTypes(TestService.class)).get();
+               var result = getMissingParameters(constructor, beanStore, null, 
service);
+               assertNull(result); // Should find service in otherBeans
+       }
+
+       @Test
+       void g02_getMissingParams_otherBeansNotMatching() throws Exception {
+               var wrongService = new AnotherService(42);
+               // Wrong type in otherBeans - should still be missing
+               var constructor = 
ClassInfo.of(TestClass1.class).getPublicConstructor(x -> 
x.hasParameterTypes(TestService.class)).get();
+               var result = getMissingParameters(constructor, beanStore, null, 
wrongService);
+               assertEquals("TestService", result); // Should still be missing
+       }
+
+       @Test
+       void g03_getParameters_otherBeans() throws Exception {
+               var service = new TestService("test1");
+               // Service not in bean store, but provided as otherBeans
+               var constructor = 
ClassInfo.of(TestClass1.class).getPublicConstructor(x -> 
x.hasParameterTypes(TestService.class)).get();
+               var params = getParameters(constructor, beanStore, null, 
service);
+               assertEquals(1, params.length);
+               assertSame(service, params[0]); // Should use service from 
otherBeans
+       }
+
+       @Test
+       void g04_getParameters_otherBeansPreferStore() throws Exception {
+               var storeService = new TestService("store");
+               var otherService = new TestService("other");
+               beanStore.addBean(TestService.class, storeService);
+               // Store has service, otherBeans also has service - should 
prefer store
+               var constructor = 
ClassInfo.of(TestClass1.class).getPublicConstructor(x -> 
x.hasParameterTypes(TestService.class)).get();
+               var params = getParameters(constructor, beanStore, null, 
otherService);
+               assertEquals(1, params.length);
+               assertSame(storeService, params[0]); // Should prefer store 
over otherBeans
+       }
+
+       @Test
+       void g05_hasAllParameters_otherBeans() throws Exception {
+               var service = new TestService("test1");
+               // Service not in bean store, but provided as otherBeans
+               var constructor = 
ClassInfo.of(TestClass1.class).getPublicConstructor(x -> 
x.hasParameterTypes(TestService.class)).get();
+               assertTrue(hasAllParameters(constructor, beanStore, null, 
service)); // Should find service in otherBeans
+       }
+
+       @Test
+       void g06_invoke_constructorWithOtherBeans() throws Exception {
+               var service = new TestService("test1");
+               // Service not in bean store, but provided as otherBeans
+               var constructor = 
ClassInfo.of(TestClass1.class).getPublicConstructor(x -> 
x.hasParameterTypes(TestService.class)).get();
+               var result = invoke(constructor, beanStore, null, service);
+               assertNotNull(result);
+               assertTrue(result instanceof TestClass1);
+       }
+
+       @Test
+       void g07_invoke_methodWithOtherBeans() throws Exception {
+               var service = new TestService("test1");
+               // Service not in bean store, but provided as otherBeans
+               var instance = new TestMethodClass();
+               var method = 
ClassInfo.of(TestMethodClass.class).getPublicMethod(x -> x.hasName("method1") 
&& x.hasParameterTypes(TestService.class)).get();
+               invoke(method, beanStore, instance, service);
+               // Method should execute without exception
+       }
+
+       @Test
+       void g08_getMissingParams_methodWithOtherBeans() throws Exception {
+               var service = new TestService("test1");
+               // Service not in bean store, but provided as otherBeans
+               var method = 
ClassInfo.of(TestMethodClass.class).getPublicMethod(x -> x.hasName("method1") 
&& x.hasParameterTypes(TestService.class)).get();
+               var result = getMissingParameters(method, beanStore, service);
+               assertNull(result); // Should find service in otherBeans
+       }
+
+       @Test
+       void g09_getParameters_methodWithOtherBeans() throws Exception {
+               var service = new TestService("test1");
+               // Service not in bean store, but provided as otherBeans
+               var method = 
ClassInfo.of(TestMethodClass.class).getPublicMethod(x -> x.hasName("method1") 
&& x.hasParameterTypes(TestService.class)).get();
+               var params = getParameters(method, beanStore, service);
+               assertEquals(1, params.length);
+               assertSame(service, params[0]); // Should use service from 
otherBeans
+       }
+
+       @Test
+       void g10_hasAllParameters_methodWithOtherBeans() throws Exception {
+               var service = new TestService("test1");
+               // Service not in bean store, but provided as otherBeans
+               var method = 
ClassInfo.of(TestMethodClass.class).getPublicMethod(x -> x.hasName("method1") 
&& x.hasParameterTypes(TestService.class)).get();
+               assertTrue(hasAllParameters(method, beanStore, service)); // 
Should find service in otherBeans
+       }
+
+       @Test
+       void g11_getParameters_methodNotFound() throws Exception {
+               // Method parameter not in bean store and not in otherBeans - 
should throw exception
+               var method = 
ClassInfo.of(TestMethodClass.class).getPublicMethod(x -> x.hasName("method1") 
&& x.hasParameterTypes(TestService.class)).get();
+               assertThrows(ExecutableException.class, () -> 
getParameters(method, beanStore));
+       }
+
+       @Test
+       void g12_invoke_methodNotFound() throws Exception {
+               // Method parameter not in bean store and not in otherBeans - 
should throw exception
+               var instance = new TestMethodClass();
+               var method = 
ClassInfo.of(TestMethodClass.class).getPublicMethod(x -> x.hasName("method1") 
&& x.hasParameterTypes(TestService.class)).get();
+               assertThrows(ExecutableException.class, () -> invoke(method, 
beanStore, instance));
+       }
 }
 

Reply via email to