001package com.gigya.android.sdk.containers;
002
003import com.gigya.android.sdk.GigyaLogger;
004
005import java.lang.reflect.Constructor;
006import java.lang.reflect.InvocationTargetException;
007import java.lang.reflect.Modifier;
008import java.util.ArrayList;
009import java.util.HashMap;
010import java.util.List;
011import java.util.Map;
012import java.util.MissingResourceException;
013
014@SuppressWarnings("unchecked")
015public class IoCContainer {
016
017    private static final String LOG_TAG = "IoCContainer";
018
019    public static class BindInfo<T> {
020        Class<T> concrete;
021        boolean asSingleton;
022        public T instance;
023
024        BindInfo(Class<T> c, boolean s) {
025            concrete = c;
026            asSingleton = s;
027        }
028
029        BindInfo(T i) {
030            concrete = (Class<T>) i.getClass();
031            asSingleton = true;
032            instance = i;
033        }
034    }
035
036    private Map<Class<?>, BindInfo> _bindings = new HashMap<>();
037
038    public <I, C extends I> IoCContainer bind(Class<I> contractClazz, final Class<C> concreteClazz, final boolean asSingleton) {
039        GigyaLogger.ioc(LOG_TAG, "Binding " + contractClazz.getCanonicalName() + " to " + concreteClazz.getCanonicalName() + " as " + (asSingleton ? "singleton" : "factory"));
040        _bindings.put(contractClazz, new BindInfo<>(concreteClazz, asSingleton));
041        return this;
042    }
043
044    public <I, C extends I> IoCContainer bind(Class<I> contractClazz, C concreteInstance) {
045        GigyaLogger.ioc(LOG_TAG, "binding " + contractClazz.getCanonicalName() + " to instance (of type " + concreteInstance.getClass().getCanonicalName() + ")");
046        _bindings.put(contractClazz, new BindInfo<>(concreteInstance));
047        return this;
048    }
049
050    public <T> T get(Class<T> contractClazz) throws InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException {
051        GigyaLogger.ioc(LOG_TAG, "Trying to get: " + contractClazz.getCanonicalName());
052
053        if (!isBound(contractClazz)) {
054            GigyaLogger.ioc(LOG_TAG, "Contract was not registered");
055            return null;
056        }
057
058        BindInfo<T> bindingInfo = _bindings.get(contractClazz);
059        if (bindingInfo == null) {
060            GigyaLogger.ioc(LOG_TAG, "Contract was not registered = null");
061            return null;
062        }
063
064        if (bindingInfo.instance != null) {
065            return bindingInfo.instance;
066        }
067        T instance = createInstance(bindingInfo.concrete);
068        if (bindingInfo.asSingleton) {
069            bindingInfo.instance = instance;
070        }
071        return instance;
072    }
073
074    public <T> T createInstance(Class<T> concreteClazz) throws InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException {
075        return createInstance(concreteClazz, false);
076    }
077
078    public <T> T createInstance(Class<T> concreteClazz, boolean forceCreation) throws InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException {
079        GigyaLogger.ioc(LOG_TAG, "Trying to create new instance for: " + concreteClazz.getCanonicalName());
080        Constructor<?>[] constructors = concreteClazz.getDeclaredConstructors();
081        if (constructors.length == 0) {
082            GigyaLogger.ioc(LOG_TAG, "Default constructor - creating instance");
083            return concreteClazz.newInstance();
084        }
085
086        for (Constructor<?> ctor : constructors) {
087            Class<?>[] ctorParams = ctor.getParameterTypes();
088            GigyaLogger.ioc(LOG_TAG, "For constructor with params #: " + ctorParams.length);
089
090            List<Object> params = new ArrayList<>();
091            for (Class paramContract : ctorParams) {
092                GigyaLogger.ioc(LOG_TAG, "Getting required param: " + paramContract.getCanonicalName());
093
094                Object paramInstance = get(paramContract);
095
096                if (paramInstance == null && forceCreation) {
097                    paramInstance = createInstance(paramContract);
098                }
099
100                if (paramInstance != null) {
101                    params.add(paramInstance);
102                } else {
103                    // missing param, so keep searching
104                    break;
105                }
106            }
107
108            if (params.size() == ctorParams.length) {
109                GigyaLogger.ioc(LOG_TAG, "Creating new instance for " + concreteClazz.getCanonicalName());
110                if (Modifier.isProtected(ctor.getModifiers())) {
111                    GigyaLogger.ioc(LOG_TAG, "Constructor is protected");
112                    ctor.setAccessible(true);
113                }
114                return (T) ctor.newInstance(params.toArray());
115            } else {
116                GigyaLogger.ioc(LOG_TAG, "Constructor wasn't suitable");
117            }
118        }
119
120        throw new MissingResourceException("Concrete class missing dependencies", concreteClazz.getName(), "iocContainer");
121    }
122
123    public boolean isBound(Class contractClazz) {
124        return _bindings.containsKey(contractClazz);
125    }
126
127    public IoCContainer clone() {
128        IoCContainer clone = new IoCContainer();
129        clone._bindings = new HashMap<>(this._bindings);
130        return clone;
131    }
132
133    public void dispose() {
134        _bindings.clear();
135        _bindings = null;
136    }
137}