![JAR search and dependency download from the Maven repository](/logo.png)
org.apache.juneau.cp.BeanCreator Maven / Gradle / Ivy
// ***************************************************************************************************************************
// * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE file *
// * distributed with this work for additional information regarding copyright ownership. The ASF licenses this file *
// * to you under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance *
// * with the License. You may obtain a copy of the License at *
// * *
// * http://www.apache.org/licenses/LICENSE-2.0 *
// * *
// * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an *
// * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the *
// * specific language governing permissions and limitations under the License. *
// ***************************************************************************************************************************
package org.apache.juneau.cp;
import static java.util.stream.Collectors.*;
import static org.apache.juneau.Visibility.*;
import static org.apache.juneau.internal.CollectionUtils.*;
import java.util.*;
import java.util.function.*;
import org.apache.juneau.*;
import org.apache.juneau.annotation.*;
import org.apache.juneau.reflect.*;
/**
* Utility class for creating beans through constructors, creator methods, and builders.
*
*
* Uses a {@link BeanStore} to find available ways to construct beans via injection of beans from the store.
*
*
* This class is instantiated through the following method:
*
* - {@link BeanStore}
*
* - {@link BeanStore#createBean(Class)}
*
*
*
*
* Example:
*
* // Construct and throw a RuntimeException using a bean store.
* throw BeanStore
* .create ()
* .build()
* .addBean(Throwable.class , cause )
* .addBean(String.class , msg )
* .addBean(Object[].class , args )
* .createBean(RuntimeException.class )
* .run();
*
*
*
* Looks for the following methods for creating a bean:
*
* - Looks for a singleton no-arg method of the form:
*
* public static MyClass getInstance ();
*
*
* - Deprecated and {@link BeanIgnore @BeanIgnore-annotated} methods are ignored.
*
* - Looks for a static creator method of the form:
*
* public static MyClass create (<args> );
*
*
* - All arguments except {@link Optional} and {@link List} parameters must have beans available in the store.
*
- If multiple methods are found, the one with the most matching parameters is used.
*
- Deprecated and {@link BeanIgnore @BeanIgnore-annotated} methods are ignored.
*
* - Looks for a public constructor of the form:
*
* public MyClass(<args> );
*
*
* - All arguments except {@link Optional} and {@link List} parameters must have beans available in the store.
*
- If multiple methods are found, the one with the most matching parameters is used.
*
- Deprecated and {@link BeanIgnore @BeanIgnore-annotated} methods are ignored.
*
* - Looks for a protected constructor of the form:
*
* protected MyClass(<args> );
*
*
* - All arguments except {@link Optional} and {@link List} parameters must have beans available in the store.
*
- If multiple methods are found, the one with the most matching parameters is used.
*
- Deprecated and {@link BeanIgnore @BeanIgnore-annotated} methods are ignored.
*
* - Looks for a static no-arg create method that returns a builder object that can be passed in to a protected constructor.
*
* public static MyClass.Builder create ();
*
* protected MyClass(MyClass.Builder builder );
*
*
* - Deprecated and {@link BeanIgnore @BeanIgnore-annotated} methods are ignored.
*
*
*
* Notes:
* - The {@link #builder(Class,Object)} method can be used to set an existing initialized builder object to pass to a constructor.
*
- An existing initialized builder can be set using the {@link #builder(Class,Object)} method.
*
*
* See Also:
* - {@link BeanStore}
*
*
* @param The bean type being created.
*/
public class BeanCreator {
//-----------------------------------------------------------------------------------------------------------------
// Static
//-----------------------------------------------------------------------------------------------------------------
/**
* Shortcut for calling BeanStore.INSTANCE.createBean(beanType) .
*
* @param The bean type to create.
* @param beanType The bean type to create.
* @return A new creator.
*/
public static BeanCreator of(Class beanType) {
return BeanStore.INSTANCE.createBean(beanType);
}
//-----------------------------------------------------------------------------------------------------------------
// Instance
//-----------------------------------------------------------------------------------------------------------------
private final BeanStore store;
private ClassInfo type;
private Object builder;
private T impl;
private boolean silent;
/**
* Constructor.
*
* @param type The bean type being created.
* @param store The bean store creating this creator.
*/
protected BeanCreator(Class type, BeanStore store) {
this.type = ClassInfo.of(type);
this.store = BeanStore.of(store, store.outer.orElse(null));
}
/**
* Allows you to specify a subclass of the specified bean type to create.
*
* @param value The value for this setting.
* @return This object.
*/
public BeanCreator type(Class> value) {
type = ClassInfo.of(value);
return this;
}
/**
* Allows you to specify a subclass of the specified bean type to create.
*
* @param value The value for this setting.
* @return This object.
*/
public BeanCreator type(ClassInfo value) {
return type(value == null ? null : value.inner());
}
/**
* Allows you to specify a specific instance for the build method to return.
*
* @param value The value for this setting.
* @return This object.
*/
public BeanCreator impl(T value) {
impl = value;
return this;
}
/**
* Adds an argument to this creator.
*
* @param The parameter type.
* @param beanType The parameter type.
* @param bean The parameter value.
* @return This object.
*/
public BeanCreator arg(Class beanType, T2 bean) {
store.add(beanType, bean);
return this;
}
/**
* Suppresses throwing of {@link ExecutableException ExecutableExceptions} from the {@link #run()} method when
* a form of creation cannot be found.
*
* @return This object.
*/
public BeanCreator silent() {
silent = true;
return this;
}
/**
* Specifies a builder object for the bean type.
*
* Notes:
* - When specified, we don't look for a static creator method.
*
*
* @param The class type of the builder.
* @param type The class type of the builder.
* @param value The value for this setting.
* @return This object.
*/
@SuppressWarnings("unchecked")
public BeanCreator builder(Class type, B value) {
builder = value;
Class> t = value.getClass();
do {
store.add((Class)t, (T)value);
t = t.getSuperclass();
} while(t != null && ! t.equals(type));
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);
}
/**
* Same as {@link #run()} but returns the value wrapped in an {@link Optional}.
*
* @return A new bean wrapped in an {@link Optional}.
*/
public Optional execute() {
return optional(silent().run());
}
/**
* Creates the bean.
*
* @return A new bean.
* @throws ExecutableException if bean could not be created and {@link #silent()} was not enabled.
*/
public T run() {
if (impl != null)
return impl;
if (type == null)
return null;
Value found = Value.empty();
// Look for getInstance(Builder).
if (builder != null) {
MethodInfo m = type.getPublicMethod(
x -> x.isStatic()
&& x.isNotDeprecated()
&& x.hasNumParams(1)
&& x.getParam(0).canAccept(builder)
&& x.hasReturnType(type)
&& x.hasNoAnnotation(BeanIgnore.class)
&& x.hasName("getInstance")
);
if (m != null)
return m.invoke(null, builder);
}
// Look for getInstance().
if (builder == null) {
MethodInfo m = type.getPublicMethod(
x -> x.isStatic()
&& x.isNotDeprecated()
&& x.hasNoParams()
&& x.hasReturnType(type)
&& x.hasNoAnnotation(BeanIgnore.class)
&& x.hasName("getInstance")
);
if (m != null)
return m.invoke(null);
}
if (builder == null) {
// Look for static creator methods.
Match match = new Match<>();
// Look for static creator method.
type.forEachPublicMethod(x -> isStaticCreateMethod(x), x -> {
found.set("STATIC_CREATOR");
if (hasAllParams(x))
match.add(x);
});
if (match.isPresent())
return match.get().invoke(null, getParams(match.get()));
}
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.
Match constructorMatch = new Match<>();
type.forEachPublicConstructor(x -> true, x -> {
found.setIfEmpty("PUBLIC_CONSTRUCTOR");
if (hasAllParams(x))
constructorMatch.add(x);
});
// Look for protected constructor.
if (! constructorMatch.isPresent()) {
type.forEachDeclaredConstructor(ConstructorInfo::isProtected, x -> {
found.setIfEmpty("PROTECTED_CONSTRUCTOR");
if (hasAllParams(x))
constructorMatch.add(x);
});
}
// Execute.
if (constructorMatch.isPresent())
return constructorMatch.get().invoke(getParams(constructorMatch.get()));
if (builder == null) {
// Look for static-builder/protected-constructor pair.
Value value = Value.empty();
type.forEachDeclaredConstructor(x -> x.hasNumParams(1) && x.isVisible(PROTECTED), x -> {
Class> pt = x.getParam(0).getParameterType().inner();
MethodInfo m = type.getPublicMethod(y -> isStaticCreateMethod(y, pt));
if (m != null) {
Object builder = m.invoke(null);
value.set(x.accessible().invoke(builder));
}
});
if (value.isPresent())
return value.get();
}
if (silent)
return null;
String msg = 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 -> getMissingParams(x)).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 -> getMissingParams(x)).sorted().collect(joining(" or "));
} else {
msg = "Protected constructor found but could not find prerequisites: " + type.getDeclaredConstructors().stream().filter(ConstructorInfo::isProtected).map(x -> getMissingParams(x)).sorted().collect(joining(" or "));
}
throw new ExecutableException("Could not instantiate class {0}: {1}.", type.getName(), msg);
}
/**
* Converts this creator into a supplier.
*
* @return A supplier that returns the results of the {@link #run()} method.
*/
public Supplier supplier() {
return ()->run();
}
private boolean isStaticCreateMethod(MethodInfo m) {
return isStaticCreateMethod(m, type.inner());
}
private boolean isStaticCreateMethod(MethodInfo m, Class> type) {
return m.isStatic()
&& m.isNotDeprecated()
&& m.hasReturnType(type)
&& m.hasNoAnnotation(BeanIgnore.class)
&& m.hasName("create");
}
//-----------------------------------------------------------------------------------------------------------------
// Helpers
//-----------------------------------------------------------------------------------------------------------------
static class Match {
T executable = null;
int numMatches = -1;
@SuppressWarnings("unchecked")
void add(T ei) {
if (ei.getParamCount() > numMatches) {
numMatches = ei.getParamCount();
executable = (T)ei.accessible();
}
}
boolean isPresent() {
return executable != null;
}
T get() {
return executable;
}
}
private boolean hasAllParams(ExecutableInfo ei) {
return store.hasAllParams(ei);
}
private Object[] getParams(ExecutableInfo ei) {
return store.getParams(ei);
}
private String getMissingParams(ExecutableInfo ei) {
return store.getMissingParams(ei);
}
}