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<T></c>
* <li>Parameters of type <c>T[]</c>, <c>List<T></c>,
<c>Set<T></c>, or <c>Map<String,T></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<T></c>
+ * <li>Parameters of type <c>T[]</c>, <c>List<T></c>,
<c>Set<T></c>, or <c>Map<String,T></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<T></c>
* <li>Parameters of type <c>T[]</c>, <c>List<T></c>,
<c>Set<T></c>, or <c>Map<String,T></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<T></c>
+ * <li>Parameters of type <c>T[]</c>, <c>List<T></c>,
<c>Set<T></c>, or <c>Map<String,T></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));
+ }
}