MentaContainer

Rev

Rev 110 | Rev 120 | Go to most recent revision | Blame | Compare with Previous | Last modification | View Log | RSS feed

package org.mentacontainer.impl;

import java.lang.reflect.Method;
import java.util.Collections;
import java.util.HashSet;
import java.util.Hashtable;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.mentacontainer.ConfigurableFactory;
import org.mentacontainer.Container;
import org.mentacontainer.Factory;
import org.mentacontainer.Interceptor;
import org.mentacontainer.Scope;
import org.mentacontainer.util.InjectionUtils;
import org.mentacontainer.util.InjectionUtils.Provider;

/**
 * The implementation of the IoC container.
 *
 * @author sergio.oliveira.jr@gmail.com
 */

public class MentaContainer implements Container {

        private Map<String, Factory> factoriesByName = new Hashtable<String, Factory>();
       
        private Map<String, Scope> scopes = new Hashtable<String, Scope>();
       
        private Map<String, Object> singletonsCache = new Hashtable<String, Object>();
       
        private Map<String, ThreadLocal<Object>> threadLocalsCache = new Hashtable<String, ThreadLocal<Object>>();
       
        private Set<SetterDependency> setterDependencies = Collections.synchronizedSet(new HashSet<SetterDependency>());
       
        private Set<ConstructorDependency> constructorDependencies = Collections.synchronizedSet(new HashSet<ConstructorDependency>());
       
        private Set<ConstructorDependency> forConstructMethod = Collections.synchronizedSet(new HashSet<ConstructorDependency>());
       
        @Override
        public Class<? extends Object> getType(String key) {
               
                Factory factory = factoriesByName.get(key);
               
                if (factory == null) return null;
               
                return factory.getType();
        }
       
        @Override
        public void clear(Scope scope) {
               
                if (scope == Scope.SINGLETON) {
                       
                        List<ClearableHolder> listToClear = new LinkedList<ClearableHolder>();
                       
                        synchronized(this) {
                       
                        for(String key : singletonsCache.keySet()) {
                               
                                Factory factory = factoriesByName.get(key);
                               
                                if (factory instanceof Interceptor) {
                                       
                                        Interceptor c = (Interceptor) factory;
                               
                                        Object value = singletonsCache.get(key);
                                       
                                        listToClear.add(new ClearableHolder(c, value));
                                }
                        }
                       
                        singletonsCache.clear();
                        }
                       
                        // clear everything inside a non-synchronized block...
                       
                        for(ClearableHolder cp : listToClear) cp.clear();
                       
                } else if (scope == Scope.THREAD) {
                       
                        List<ClearableHolder> listToClear = new LinkedList<ClearableHolder>();
                       
                        synchronized(this) {
                       
                        for(String key : threadLocalsCache.keySet()) {
                               
                                Factory factory = factoriesByName.get(key);
                               
                                if (factory instanceof Interceptor) {
                                       
                                        Interceptor c = (Interceptor) factory;
                               
                                        ThreadLocal<Object> t = threadLocalsCache.get(key);
                                       
                                        Object value = t.get();
                                       
                                        if (value != null) listToClear.add(new ClearableHolder(c, value));
                                }
                        }
                       
                        for(ThreadLocal<Object> t : threadLocalsCache.values()) {
                               
                                t.set(null);
                        }
                        }
                       
                        // clear everything inside a non-synchronized block...
                       
                        for(ClearableHolder cp : listToClear) cp.clear();
                }
        }
       
        @Override
        public <T> T clear(String key) {
               
                if (!factoriesByName.containsKey(key)) return null;
               
                Scope scope = scopes.get(key);
               
                if (scope == Scope.SINGLETON) {
                       
                        ClearableHolder cp = null;
                       
                        Object value = null;
                       
                        synchronized(this) {
                       
                        value = singletonsCache.remove(key);
                       
                        if (value != null) {
                               
                                Factory factory = factoriesByName.get(key);
                               
                                if (factory instanceof Interceptor) {
                                       
                                        Interceptor c = (Interceptor) factory;
                                       
                                        cp = new ClearableHolder(c, value);
                                }
                        }
                        }
                       
                        if (cp != null) cp.c.onCleared(cp.value);
                       
                        return (T) value;
                       
                } else if (scope == Scope.THREAD) {
                       
                        ClearableHolder cp = null;
                       
                        Object retVal = null;
                       
                        synchronized(this) {
                       
                        ThreadLocal<Object> t = threadLocalsCache.get(key);
                       
                        if (t != null) {
                               
                                Object o = t.get();
                               
                                if (o != null) {
                                       
                                        Factory factory = factoriesByName.get(key);
                                       
                                        if (factory instanceof Interceptor) {
                                               
                                                Interceptor c = (Interceptor) factory;
                                               
                                                cp = new ClearableHolder(c, o);
                                        }
                                       
                                        t.set(null);
                                       
                                        retVal = o;
                                }
                        }
                        }
                       
                        if (cp != null) cp.c.onCleared(cp.value);
                       
                        return (T) retVal;
               
                } else if (scope == Scope.NONE) {
                       
                        return null; // always...
                       
                } else {
                       
                        throw new UnsupportedOperationException("Scope not supported: " + scope);
                }
        }

        @Override
        public <T> T get(String key) {

                if (!factoriesByName.containsKey(key)) return null;

                Factory c = factoriesByName.get(key);
               
                Scope scope = scopes.get(key);
               
                Object target = null;

                try {

                        if (scope == Scope.SINGLETON) {
                               
                                boolean needsToCreate = false;
                               
                                synchronized(this) {

                                if (singletonsCache.containsKey(key)) {
   
                                        target = singletonsCache.get(key);
   
                                        return (T) target; // no need to wire again...
   
                                } else {
                                       
                                        needsToCreate = true;
                                }
                                }
                               
                                if (needsToCreate) {
                                       
                                        // getInstance needs to be in a non-synchronized block
                                       
                                        target = c.getInstance();
                                       
                                        checkInterceptable(c, target);
                                       
                                        synchronized(this) {

                                                singletonsCache.put(key, target);
                                        }
                                }
                               
                        } else if (scope == Scope.THREAD) {
                               
                                boolean needsToCreate = false;
                               
                                boolean needsToAddToCache = false;
                               
                                ThreadLocal<Object> t = null;
                               
                                synchronized(this) {
                               
                                if (threadLocalsCache.containsKey(key)) {
                                       
                                        t = threadLocalsCache.get(key);
                                       
                                        target = t.get();
                                       
                                        if (target == null) { // different thread...
                                               
                                                needsToCreate = true;
                                               
                                                // don't return... let it be wired...
                                               
                                        } else {
                                       
                                                return (T) target; // no need to wire again...
                                               
                                        }
                                       
                                } else {
                                       
                                        t = new ThreadLocal<Object>();
                                       
                                        needsToCreate = true;
                                       
                                                needsToAddToCache = true;
                                       
                                        // let it be wired...
                                }
                                }
                               
                                if (needsToCreate) {
                                       
                                        // getInstance needs to be in a non-synchronized block
                                       
                                        target = c.getInstance();
                                       
                                        checkInterceptable(c, target);
                                       
                                        t.set(target);
                                }
                               
                                if (needsToAddToCache) {
                                       
                                        synchronized(this) {
                                       
                                                threadLocalsCache.put(key, t);
                                        }
                                }
                               
                        } else if (scope == Scope.NONE) {

                                target = c.getInstance();
                               
                                checkInterceptable(c, target);
                               
                        } else {
                               
                                throw new UnsupportedOperationException("Don't know how to handle scope: " + scope);
                        }

                        if (target != null) {

                                for (SetterDependency d : setterDependencies) {

                                        // has dependency ?
                                        Method m = d.check(target.getClass());

                                        if (m != null) {

                                                String sourceKey = d.getSource();

                                                if (sourceKey.equals(key)) {

                                                        // cannot depend on itself... also avoid recursive StackOverflow...

                                                        continue;

                                                }

                                                Object source = get(sourceKey);

                                                try {

                                                        // inject
                                                        m.invoke(target, source);

                                                } catch (Exception e) {

                                                        throw new RuntimeException("Cannot inject dependency: method = " + (m != null ? m.getName() : "NULL") + " / source = "
                                                                + (source != null ? source : "NULL") + " / target = " + target, e);

                                                }
                                        }
                                }
                        }

                        return (T) target; // return target nicely with all the dependencies

                } catch (Exception e) {

                        throw new RuntimeException(e);
                }
        }
       
        private final void checkInterceptable(Factory f, Object value) {
               
                if (f instanceof Interceptor) {
                       
                        Interceptor i = (Interceptor) f;
                       
                        ((Interceptor) f).onCreated(value);
                }
        }
       
        @Override
        public Factory ioc(String key, Factory factory, Scope scope) {
               
                factoriesByName.put(key, factory);
               
                singletonsCache.remove(key); // just in case we are overriding a previous singleton bean...
               
                threadLocalsCache.remove(key); // just in case we are overriding a previous thread local...
               
                scopes.put(key, scope);
               
                forConstructMethod.add(new ConstructorDependency(key, factory.getType()));
               
                return factory;
        }
       
        @Override
        public Factory ioc(String key, Factory factory) {
               
                return ioc(key, factory, Scope.NONE);
        }
       
        @Override
        public ConfigurableFactory ioc(String key, Class<? extends Object> klass) {
               
                ConfigurableFactory cc = new ClassFactory(this, klass);
               
                ioc(key, cc);
               
                return cc;
        }
       
        @Override
        public ConfigurableFactory ioc(String key, Class<? extends Object> klass, Scope scope) {
               
                ConfigurableFactory cc = new ClassFactory(this, klass);
               
                ioc(key, cc, scope);
               
                return cc;
        }
       
        @Override
        public void autowire(String sourceFromContainer) {
               
                // autowire by constructor and setter...
               
                autowireBySetter(sourceFromContainer);
               
                autowireByConstructor(sourceFromContainer);
        }
       
        @Override
        public void autowire(String sourceFromContainer, String beanProperty) {
               
                // autowire by constructor and setter...
               
                autowireBySetter(beanProperty, sourceFromContainer);
               
                autowireByConstructor(sourceFromContainer);
        }

        private void autowireBySetter(String targetProperty, String sourceFromContainer) {
               
                Class<? extends Object> sourceType = getType(sourceFromContainer);

                SetterDependency d = new SetterDependency(targetProperty, sourceFromContainer, sourceType);
               
                setterDependencies.add(d);
        }
       
        private void autowireBySetter(String targetProperty) {
               
                autowireBySetter(targetProperty, targetProperty);
        }
       
        private void autowireByConstructor(String sourceFromContainer) {
               
                Class<? extends Object> sourceType = getType(sourceFromContainer);
               
                ConstructorDependency d = new ConstructorDependency(sourceFromContainer, sourceType);
               
                constructorDependencies.add(d);
        }
       
        Set<ConstructorDependency> getConstructorDependencies() {
               
                return constructorDependencies;
        }
       
        @Override
        public <T> T construct(Class<? extends Object> klass) {
               
                ClassFactory f = new ClassFactory(this, klass, forConstructMethod);
               
                return (T) f.getInstance();
        }

        @Override
        public void inject(Object bean) {
               
                Provider p = new Provider() {
                       
                        @Override
                        public Object get(String key) {
                               
                                return MentaContainer.this.get(key);
                        }
                       
                        @Override
                        public boolean hasValue(String key) {
                               
                                return MentaContainer.this.check(key);
                        }
                       
                };
               
                try {

                        InjectionUtils.getObject(bean, p, false, null, true, false, true);
                       
                } catch(Exception e) {
                       
                        throw new RuntimeException("Error populating bean: " + bean, e);
                }
        }

        @Override
        public synchronized boolean check(String key) {
               
                if (!factoriesByName.containsKey(key)) return false;
               
                Scope scope = scopes.get(key);
               
                if (scope == Scope.NONE) {
                       
                        return false; // always...
                       
                } else if (scope == Scope.SINGLETON) {
                       
                        return singletonsCache.containsKey(key);
                       
                } else if (scope == Scope.THREAD) {
                       
                        ThreadLocal<Object> t = threadLocalsCache.get(key);
                       
                        if (t != null) return t.get() != null;
                       
                        return false;
                       
                } else {
                       
                        throw new UnsupportedOperationException("This scope is not supported: " + scope);
                }
        }
       
        private static class ClearableHolder {

                private Interceptor c;
                private Object value;
               
                public ClearableHolder(Interceptor c, Object value) {
                        this.c = c;
                        this.value = value;
                }
               
                public void clear() {
                        c.onCleared(value);
                }
               
        }
}