All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.cookingfox.chefling.impl.command.CreateInstanceCommandImpl Maven / Gradle / Ivy

Go to download

Chefling is a very minimal dependency injection container written in pure Java.

There is a newer version: 7.1.1
Show newest version
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