
com.cookingfox.chefling.impl.command.CreateInstanceCommandImpl Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of chefling-di-java Show documentation
Show all versions of chefling-di-java Show documentation
Chefling is a very minimal dependency injection container written in pure Java.
package com.cookingfox.chefling.impl.command;
import com.cookingfox.chefling.api.CheflingFactory;
import com.cookingfox.chefling.api.CheflingLifecycle;
import com.cookingfox.chefling.api.command.CreateInstanceCommand;
import com.cookingfox.chefling.api.exception.*;
import com.cookingfox.chefling.impl.helper.ConstructorParameters;
import java.lang.reflect.Constructor;
import java.lang.reflect.Modifier;
import java.util.*;
/**
* @see CreateInstanceCommand
*/
public class CreateInstanceCommandImpl extends AbstractCommand implements CreateInstanceCommand {
//----------------------------------------------------------------------------------------------
// STATIC PROPERTIES
//----------------------------------------------------------------------------------------------
/**
* Cache for selected constructor + parameter types, since this is an expensive operation.
*/
protected final static LinkedHashMap TYPE_CACHE = new LinkedHashMap<>();
//----------------------------------------------------------------------------------------------
// CONSTRUCTORS
//----------------------------------------------------------------------------------------------
public CreateInstanceCommandImpl(CommandContainer container) {
super(container);
}
//----------------------------------------------------------------------------------------------
// PUBLIC METHODS
//----------------------------------------------------------------------------------------------
@Override
@SuppressWarnings("unchecked")
public T createInstance(Class type) {
assertNonNull(type, "type");
isAllowed(type);
Object mapping = findMapping(_container, type);
T instance;
if (mapping instanceof Class) {
// create instance using mapped type
instance = createInstance((Class) mapping);
} else if (type.isInstance(mapping)) {
// mapping is instance
instance = (T) mapping;
} else if (mapping instanceof CheflingFactory) {
// use factory to create instance
instance = resolveUsingFactory((CheflingFactory) mapping, type);
} else {
// no mapping: create instance using provided type
instance = constructInstance(type);
}
// call life cycle initialize
if (instance instanceof CheflingLifecycle) {
((CheflingLifecycle) instance).initialize();
}
return instance;
}
//----------------------------------------------------------------------------------------------
// PROTECTED METHODS
//----------------------------------------------------------------------------------------------
/**
* Creates a new instance of `type`, attempting to resolve its full dependency tree.
*
* @param type The type to instantiate.
* @param Ensures the returned object is cast to the expected type.
* @return New instance of the type.
* @throws ContainerException
*/
protected T constructInstance(Class type) {
// cached constructor + parameters for this type
ConstructorParameters cached = TYPE_CACHE.get(type);
// resolve using cache
if (cached != null) {
return newInstance(type, cached.constructor, cached.parameterTypes);
}
// determine default constructor + parameters
Constructor constructor = getDefaultConstructor(type);
Class[] parameterTypes = constructor.getParameterTypes();
// create instance
T instance = newInstance(type, constructor, parameterTypes);
// success: cache constructor + parameters for this type
TYPE_CACHE.put(type, new ConstructorParameters(constructor, parameterTypes));
return instance;
}
/**
* Creates a new instance by providing the correct parameters to the selected constructor.
*
* @param type The type to instantiate.
* @param constructor The selected constructor for this type.
* @param parameterTypes The parameter types of the constructor.
* @param Ensures the returned object is cast to the expected type.
* @return New instance of the type.
* @throws ContainerException
*/
@SuppressWarnings("unchecked")
protected T newInstance(Class type, Constructor constructor, Class[] parameterTypes)
throws ContainerException {
Object[] parameters = new Object[parameterTypes.length];
// gather constructor parameters based on their types
for (int i = 0; i < parameterTypes.length; i++) {
parameters[i] = _container.getInstance(parameterTypes[i]);
}
try {
// create a new instance, passing the constructor parameters
return (T) constructor.newInstance(parameters);
} catch (Exception e) {
throw new TypeInstantiationException(type, e);
}
}
/**
* Get the default constructor for this type.
*
* @param type The type to get the constructor for.
* @return Constructor, if a resolvable one can be found.
* @throws TypeNotAllowedException
*/
protected Constructor getDefaultConstructor(Class type) throws TypeNotAllowedException {
isInstantiable(type);
Constructor[] constructors = type.getDeclaredConstructors();
ResolvabilityResult firstResult = getResolvabilityResult(constructors[0]);
// if first constructor is resolvable, return it immediately
if (firstResult.isResolvable()) {
return firstResult.constructor;
}
// map of resolvable results, by number of parameters: we favor a constructor with a small
// number of parameters, because the chances are higher that it is resolvable.
Map> resultMap = buildResultMap(constructors);
// select resolvable constructor
for (Map.Entry> entry : resultMap.entrySet()) {
for (ResolvabilityResult result : entry.getValue()) {
if (result.isResolvable()) {
// constructor is resolvable: return it
return result.constructor;
}
}
}
throw new TypeNotInstantiableException(type, buildErrorMessage(type, resultMap));
}
/**
* Create a "resolvability" result: check all constructor parameters to see whether they are
* resolvable by the container.
*
* @param constructor The constructor to check.
* @return The result.
*/
protected ResolvabilityResult getResolvabilityResult(Constructor constructor) {
Class[] parameterTypes = constructor.getParameterTypes();
int numParameters = parameterTypes.length;
ResolvabilityResult result = new ResolvabilityResult(constructor, numParameters);
// check whether the constructor parameters are resolvable
for (int i = 0; i < numParameters; i++) {
Class parameterType = parameterTypes[i];
// container has a mapping for this parameter type: ok!
if (_container.hasInstanceOrMapping(parameterType)) {
continue;
}
try {
// is this type instantiable?
isInstantiable(parameterType);
} catch (TypeNotAllowedException e) {
// not instantiable: store in result
result.unresolvable.add(new UnresolvableParameter(i, e));
}
}
return result;
}
/**
* Build a resolvability result map, sorted by number of parameters.
*
* @param constructors Declared constructors for the type that needs to be created.
* @return A map of resolvability results.
*/
protected Map> buildResultMap(Constructor[] constructors) {
Map> resultMap = new TreeMap<>();
// inspect constructor resolvability
for (Constructor constructor : constructors) {
// create a resolvability result for this constructor
ResolvabilityResult result = getResolvabilityResult(constructor);
List resultList = resultMap.get(result.numParameters);
if (resultList == null) {
resultList = new LinkedList<>();
}
resultList.add(result);
resultMap.put(result.numParameters, resultList);
}
return resultMap;
}
/**
* Builds an error message that is thrown when the type is not instantiable. It uses the
* information from the class resolvability results.
*
* @param type The type to get the constructor for.
* @param resultMap The results from the resolvability checks.
* @return The error message.
*/
protected String buildErrorMessage(Class type, Map> resultMap) {
// build error message
StringBuilder errorBuilder = new StringBuilder();
errorBuilder.append("it does not have constructors that are resolvable by the container:\n\n");
Iterator iterator = resultMap.entrySet().iterator();
while (iterator.hasNext()) {
@SuppressWarnings("unchecked")
Map.Entry> entry = (Map.Entry) iterator.next();
List resultList = entry.getValue();
// add error report entry for every resolvability result
for (int i = 0; i < resultList.size(); i++) {
addErrorReportEntry(errorBuilder, resultList.get(i), type);
if (i < resultList.size() - 1) {
errorBuilder.append("\n");
}
}
if (iterator.hasNext()) {
errorBuilder.append("\n");
}
iterator.remove();
}
return errorBuilder.toString();
}
/**
* Add an error report for an unresolvable constructor.
*
* @param errorBuilder The string builder for the error message.
* @param result The resolvability result.
* @param type The type we are attempting to instantiate.
*/
protected void addErrorReportEntry(StringBuilder errorBuilder, ResolvabilityResult result, Class type) {
// add name of this constructor
String modifierName = Modifier.toString(result.getModifiers());
if (modifierName.isEmpty()) {
modifierName = "non-public";
}
errorBuilder.append(String.format("[%s] %s ( ", modifierName, type.getSimpleName()));
Class[] parameterTypes = result.constructor.getParameterTypes();
// add parameter types to constructor signature
for (int i = 0; i < parameterTypes.length; i++) {
Class parameterType = parameterTypes[i];
errorBuilder.append(parameterType.getName());
if (i < parameterTypes.length - 1) {
errorBuilder.append(", ");
}
}
errorBuilder.append(" )\n");
if (!result.isPublic()) {
errorBuilder.append(String.format("The constructor is %s\n", modifierName));
} else if (!result.unresolvable.isEmpty()) {
// loop through unresolvable parameters and print their exception messages
for (UnresolvableParameter notResolvable : result.unresolvable) {
errorBuilder.append(String.format("Parameter #%d: %s\n",
notResolvable.parameterIndex + 1, notResolvable.exception.getMessage()));
}
}
}
/**
* Resolves a type using a factory instance. Throws if the returned value is null or invalid.
*
* @param factory The factory object.
* @param type The expected type that the factory should return.
* @param Ensures the return value is cast to expected type.
* @return The created instance.
* @throws ContainerException
*/
protected T resolveUsingFactory(CheflingFactory factory, Class type) {
T instance = factory.createInstance(_container);
if (instance == null) {
throw new FactoryReturnedNullException(type);
} else if (!type.isInstance(instance)) {
throw new FactoryReturnedUnexpectedValueException(type, instance);
}
return instance;
}
//----------------------------------------------------------------------------------------------
// INNER CLASSES
//----------------------------------------------------------------------------------------------
/**
* Represents information for an unresolvable constructor parameter.
*/
protected static class UnresolvableParameter {
protected final int parameterIndex;
protected final Exception exception;
protected UnresolvableParameter(int parameterIndex, Exception exception) {
this.parameterIndex = parameterIndex;
this.exception = exception;
}
}
/**
* Represents information for a constructor's resolvability.
*/
protected static class ResolvabilityResult {
protected final Constructor constructor;
protected final int numParameters;
protected final ArrayList unresolvable = new ArrayList<>();
protected ResolvabilityResult(Constructor constructor, int numParameters) {
this.constructor = constructor;
this.numParameters = numParameters;
}
protected int getModifiers() {
return constructor.getModifiers();
}
protected boolean isPublic() {
return Modifier.isPublic(getModifiers());
}
protected boolean isResolvable() {
return isPublic() && unresolvable.isEmpty();
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy