Module.java

package fi.eis.libraries.di;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import fi.eis.libraries.di.SimpleLogger.LogLevel;

/**
 * Creation Date: 30.11.2014
 * Creation Time: 22:49
 *
 * @author eis
 */
public class Module {

    protected final SimpleLogger logger = new SimpleLogger(this.getClass());
    
    private static final Object NO_INSTANCE = new Object();
    private Map<Class, Object> providers = new HashMap<>();
    public Module(Class... classes) {
        for (Class clazz: classes) {
            this.providers.put(clazz, NO_INSTANCE );
        }
    }
    public Module(List<Class> classes) {
        for (Class clazz: classes) {
            this.providers.put(clazz, NO_INSTANCE );
        }
    }
    public Module(Map<Class,Object> classesWithInstances) {
        for (Map.Entry<Class,Object> entry: classesWithInstances.entrySet()) {
            logger.debug("Storing %s with key %s", entry.getValue(), entry.getKey());
            this.providers.put(entry.getKey(), entry.getValue());
        }
    }
    public void setLogLevel(LogLevel level) {
        this.logger.setLogLevel(level);
    }

    public void add(Module module) {
        this.providers.putAll(module.providers);
    }

    public boolean has(Class type) {
        logger.debug("Has %s? (in %s)%n", type, providers.keySet());
        for (Class clazz: providers.keySet()) {
            logger.debug("Comparing %s with %s%n", clazz, type);
            if (type.isAssignableFrom(clazz)) {
                return true;
            }
        }
        return false;
    }
    private Class getImplClassFor(Class type) {
        for (Class clazz: providers.keySet()) {
            if (type.isAssignableFrom(clazz)) {
                return clazz;
            }
        }
        throw new IllegalArgumentException("not allowed: " + type);
    }
    private <T> T getInstance(Class <T> type) {
        for (Map.Entry<Class,Object> classObjEntry: providers.entrySet()) {
            if (type.isAssignableFrom(classObjEntry.getKey())) {
                return (T)classObjEntry.getValue();
            }
        }
        throw new IllegalArgumentException("not allowed: " + type);

    }
    public <T>T get(Class<T> type) {
        if (!has(type)) {
            throw new IllegalArgumentException("not supported: " + type);
        }
        Object storedValue = getInstance(type);
        if (storedValue != NO_INSTANCE) {
            return (T)storedValue;
        } else {
            try {
                Class implClass = getImplClassFor(type);
                storedValue = newInstance(implClass);
                providers.put(implClass, storedValue);
                return (T)storedValue;
            } catch (InstantiationException | IllegalAccessException | NoSuchMethodException | InvocationTargetException e) {
                throw new IllegalStateException(e);
            }
        }
    }

    protected Object newInstance(Class implClass) throws InstantiationException,
            IllegalAccessException, NoSuchMethodException, InvocationTargetException {
        for (Constructor constructor: implClass.getConstructors()) {
            if (constructor.isAnnotationPresent(Inject.class)) {

                Class[] parameterTypes = constructor.getParameterTypes();
                Object[] objArr = new Object[parameterTypes.length];

                int i = 0;

                for(Class c : parameterTypes) {
                    objArr[i++] = get(c);
                }

                return constructor.newInstance(objArr);
            }
        }
        return implClass.newInstance();
    }
    @Override
    public String toString() {
        return this.getClass() + " [providers=" + providers.toString() + "]";
    }
}