MentaContainer

Rev

Rev 66 | Rev 76 | 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.Clearable;
import org.mentacontainer.Component;
import org.mentacontainer.ConfigurableComponent;
import org.mentacontainer.Container;
import org.mentacontainer.Dependency;
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, Component> beans = new Hashtable<String, Component>();
       
        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<Dependency> dependencies = Collections.synchronizedSet(new HashSet<Dependency>());
       
        public void clear(Scope scope) {
               
                if (scope == Scope.SINGLETON) {
                       
                        List<ClearablePair> listToClear = new LinkedList<ClearablePair>();
                       
                        synchronized(this) {
                       
                        for(String key : singletonsCache.keySet()) {
                               
                                Component comp = beans.get(key);
                               
                                if (comp instanceof Clearable) {
                                       
                                        Clearable<Object> c = (Clearable<Object>) comp;
                               
                                        Object value = singletonsCache.get(key);
                                       
                                        listToClear.add(new ClearablePair(c, value));
                                }
                        }
                       
                        singletonsCache.clear();
                        }
                       
                        // clear everything inside a non-synchronized block...
                       
                        for(ClearablePair cp : listToClear) cp.c.onCleared(cp.value);
                       
                } else if (scope == Scope.THREAD) {
                       
                        List<ClearablePair> listToClear = new LinkedList<ClearablePair>();
                       
                        synchronized(this) {
                       
                        for(String key : threadLocalsCache.keySet()) {
                               
                                Component comp = beans.get(key);
                               
                                if (comp instanceof Clearable) {
                                       
                                        Clearable<Object> c = (Clearable<Object>) comp;
                               
                                        ThreadLocal<Object> t = threadLocalsCache.get(key);
                                       
                                        Object value = t.get();
                                       
                                        if (value != null) listToClear.add(new ClearablePair(c, value));
                                }
                        }
                       
                        for(ThreadLocal<Object> t : threadLocalsCache.values()) {
                               
                                t.set(null);
                        }
                        }
                       
                        // clear everything inside a non-synchronized block...
                       
                        for(ClearablePair cp : listToClear) cp.c.onCleared(cp.value);
                }
        }
       
        public <T> T clear(String key) {
               
                if (!beans.containsKey(key)) return null;
               
                Scope scope = scopes.get(key);
               
                if (scope == Scope.SINGLETON) {
                       
                        ClearablePair cp = null;
                       
                        Object value = null;
                       
                        synchronized(this) {
                       
                        value = singletonsCache.remove(key);
                       
                        if (value != null) {
                               
                                Component comp = beans.get(key);
                               
                                if (comp instanceof Clearable) {
                                       
                                        Clearable<Object> c = (Clearable<Object>) comp;
                                       
                                        cp = new ClearablePair(c, value);
                                }
                        }
                        }
                       
                        if (cp != null) cp.c.onCleared(cp.value);
                       
                        return (T) value;
                       
                } else if (scope == Scope.THREAD) {
                       
                        ClearablePair cp = null;
                       
                        Object retVal = null;
                       
                        synchronized(this) {
                       
                        ThreadLocal<Object> t = threadLocalsCache.get(key);
                       
                        if (t != null) {
                               
                                Object o = t.get();
                               
                                if (o != null) {
                                       
                                        Component comp = beans.get(key);
                                       
                                        if (comp instanceof Clearable) {
                                               
                                                Clearable<Object> c = (Clearable<Object>) comp;
                                               
                                                cp = new ClearablePair(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);
                }
        }

        public <T> T get(String key) {

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

                Component c = beans.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();
                                       
                                        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();
                                       
                                        t.set(target);
                                }
                               
                                if (needsToAddToCache) {
                                       
                                        synchronized(this) {
                                       
                                                threadLocalsCache.put(key, t);
                                        }
                                }
                               
                        } else if (scope == Scope.NONE) {

                                target = c.getInstance();

                               
                        } else {
                               
                                throw new UnsupportedOperationException("Don't know how to handle scope: " + scope);
                        }

                        if (target != null) {

                                for (Dependency d : dependencies) {

                                        // 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);

                                                boolean isAssignable = source != null && d.getType().isAssignableFrom(source.getClass());

                                                // check if we can find the dependency and if it is
                                                // assignable to the target dependency
                                                if (isAssignable) {

                                                        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);
                }
        }

        public Component ioc(String key, Component component, Scope scope) {
               
                beans.put(key, component);
               
                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);
               
                return component;
        }
       
        public Component ioc(String key, Component component) {
               
                return ioc(key, component, Scope.NONE);
        }
       
        public ConfigurableComponent ioc(String key, Class<? extends Object> klass) {
               
                ConfigurableComponent cc = new MentaComponent(klass);
               
                ioc(key, cc);
               
                return cc;
        }
       
        public ConfigurableComponent ioc(String key, Class<? extends Object> klass, Scope scope) {
               
                ConfigurableComponent cc = new MentaComponent(klass);
               
                ioc(key, cc, scope);
               
                return cc;
        }

        public Dependency autowire(Dependency d) {

                dependencies.add(d);

                return d;
        }

        public Dependency autowire(String property, Class<? extends Object> klass) {
               
                return autowire(property, klass, property);
        }
       
        public Dependency autowire(String property, Class<? extends Object> klass, String source) {
               
                return autowire(new MentaDependency(property, klass, source));
        }

        public Container populate(Object bean) {
               
                Provider p = new Provider() {
                       
                        public Object get(String key) {
                               
                                return MentaContainer.this.get(key);
                        }
                       
                        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);
                }

                return this;
        }

        public synchronized boolean check(String key) {
               
                if (!beans.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 ClearablePair {
               
                public ClearablePair(Clearable<Object> c, Object value) {
                        this.c = c;
                        this.value = value;
                }
               
                Clearable<Object> c;
               
                Object value;
        }
}