MentaContainer

Rev

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

package org.mentacontainer.impl;

import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.mentacontainer.ConfigurableFactory;
import org.mentacontainer.util.FindConstructor;
import org.mentacontainer.util.FindMethod;
import org.mentacontainer.util.InjectionUtils;

/**
 * The implementation of the Configurable Factory.
 *
 * @author sergio.oliveira.jr@gmail.com
 */

class ClassFactory implements ConfigurableFactory {
       
                private final MentaContainer container;
       
            private final Class<? extends Object> klass;
           
            private Map<String, Object> props = null;
           
            private List<Object> initValues = null;
           
            private List<Class<? extends Object>> initTypes = null;
           
            private Constructor<? extends Object> constructor = null;
           
            private Map<String, Method> cache = null;
           
            private boolean useZeroArgumentsConstructor = false;
           
            private final Set<ConstructorDependency> constructorDependencies;
           
            public ClassFactory(MentaContainer container, Class<? extends Object> klass) {

                this(container, klass, null);
            }
           
            ClassFactory(MentaContainer container, Class<? extends Object> klass, Set<ConstructorDependency> constructorDependencies) {
               
                this.container = container;
               
                this.klass = klass;
               
                this.constructorDependencies = constructorDependencies;
               
            }

           
            @Override
            public ConfigurableFactory addPropertyValue(String name, Object value) {
               
                if (props == null) {
                       
                    props = new HashMap<String, Object>();
                   
                    cache = new HashMap<String, Method>();
                }
               
                props.put(name, value);
               
                return this;
            }
           
            @Override
            public ConfigurableFactory useZeroArgumentConstructor() {
               
                this.useZeroArgumentsConstructor = true;
               
                return this;
            }
           
            @Override
            public ConfigurableFactory addPropertyDependency(String property, Object key) {
               
                String k = InjectionUtils.getKeyName(key);
               
                return addPropertyValue(property, new DependencyKey(k));
            }
           
            @Override
            public ConfigurableFactory addPropertyDependency(String property) {
               
                return addPropertyDependency(property, property);
            }
           
            @Override
            public ConfigurableFactory addConstructorDependency(Object key) {
               
                String k = InjectionUtils.getKeyName(key);
               
                return addInitValue(new DependencyKey(k), container.getType(k));
            }
           
            private ConfigurableFactory addInitValue(Object value, Class<? extends Object> type) {
               
                if (initValues == null) {
                       
                    initValues = new LinkedList<Object>();
                   
                    initTypes = new LinkedList<Class<? extends Object>>();
                }
               
                initValues.add(value);
               
                initTypes.add(type);
               
                return this;
            }
           
            @Override
            public ConfigurableFactory addInitValue(Object value) {
               
                return addInitValue(value, value.getClass());
            }
           
            @Override
            public ConfigurableFactory addInitPrimitive(Object value) {
               
                Class<? extends Object> primitive = getPrimitiveFrom(value);
               
                if (primitive == null) throw new IllegalArgumentException("Value is not a primitive: " + value);
               
                return addInitValue(value, primitive);
            }
           
            private List<Class<? extends Object>> convertToPrimitives(List<Class<? extends Object>> list) {
               
                if (list == null) return null;
               
                Iterator<Class<? extends Object>> iter = list.iterator();
               
                List<Class<? extends Object>> results = new LinkedList<Class<? extends Object>>();
               
                while(iter.hasNext()) {
                       
                        Class<? extends Object> klass = iter.next();
                       
                        Class<? extends Object> primitive = getPrimitiveFrom(klass);
                       
                        if (primitive != null) {
                               
                                results.add(primitive);
                               
                        } else {
                               
                                results.add(klass);
                        }
                }
               
                return results;
            }
           
            private Class<? extends Object>[] getClasses(List<Class<? extends Object>> values) {
               
                if (values == null) return new Class[0];
               
                Class<? extends Object>[] types = (Class<? extends Object>[]) new Class[values.size()];
               
                return values.toArray(types);
            }
           
            private Object [] getValues(List<Object> values) throws InstantiationException {
               
                if (values == null) return null;
               
                Object [] array = new Object[values.size()];
               
                int index = 0;
               
                Iterator<Object> iter = values.iterator();
               
                while(iter.hasNext()) {
               
                        Object obj = iter.next();
                       
                        if (obj instanceof DependencyKey) {
                               
                                DependencyKey dk = (DependencyKey) obj;
                               
                                array[index++] = container.get(dk.getKey());
                               
                        } else {
                   
                                array[index++] = obj;
                        }
                }
               
                return array;
            }
           
                /*
                 * Use reflection to set a property in the bean
                 */

                private void setValue(Object bean, String name, Object value) {
               
                        try {
                               
                                StringBuffer sb = new StringBuffer(30);
                                sb.append("set");
                                sb.append(name.substring(0,1).toUpperCase());
                               
                                if (name.length() > 1) sb.append(name.substring(1));
                   
                    String methodName = sb.toString();
                   
                    if (!cache.containsKey(name)) {
                       
                        Method m = null;
                       
                        try {
                               
                            m = FindMethod.getMethod(klass, methodName, new Class[] { value.getClass() });
                           
                        } catch(Exception e) {
                               
                            // try primitive...
                               
                            Class<? extends Object> primitive = getPrimitiveFrom(value);
                           
                            if (primitive != null) {
                               
                                try {
                                       
                                    m = klass.getMethod(methodName, new Class[] { primitive });
                                   
                                } catch(Exception ex) {
                                        // not found!
                                }
                            }
                           
                            if (m == null) {
                               
                                throw new InstantiationException("Cannot find method for property: " + name);
                            }
                        }
                       
                        if (m != null) {
                               
                            cache.put(name, m);
                           
                            m.setAccessible(true);
                        }
                    }    

                    Method m = cache.get(name);
                   
                    if (m != null) {
                       
                        m.invoke(bean, new Object[] { value });
                    }                
                   
                        } catch(Exception e) {
                               
                                throw new RuntimeException("Error trying to set a property with reflection: " + name, e);
                        }
                }
           
            private static Class<? extends Object> getPrimitiveFrom(Object w) {
                if (w instanceof Boolean) { return Boolean.TYPE; }
                else if (w instanceof Byte) { return Byte.TYPE; }
                else if (w instanceof Short) { return Short.TYPE; }
                else if (w instanceof Character) { return Character.TYPE; }
                else if (w instanceof Integer) { return Integer.TYPE; }
                else if (w instanceof Long) { return Long.TYPE; }
                else if (w instanceof Float) { return Float.TYPE; }
                else if (w instanceof Double) { return Double.TYPE; }
                return null;
            }
           
            private static Class<? extends Object> getPrimitiveFrom(Class<? extends Object> klass) {
                if (klass.equals(Boolean.class)) { return Boolean.TYPE; }
                else if (klass.equals(Byte.class)) { return Byte.TYPE; }
                else if (klass.equals(Short.class)) { return Short.TYPE; }
                else if (klass.equals(Character.class)) { return Character.TYPE; }
                else if (klass.equals(Integer.class)) { return Integer.TYPE; }
                else if (klass.equals(Long.class)) { return Long.TYPE; }
                else if (klass.equals(Float.class)) { return Float.TYPE; }
                else if (klass.equals(Double.class)) { return Double.TYPE; }
                return null;
            }
           
            @Override
            public <T> T getInstance()  {
               
                Object obj = null;
               
                Object[] values = null;
               
                synchronized(this) {
               
                if (constructor == null) {
                       
                        if (!useZeroArgumentsConstructor) {
                               
                                checkConstructorDependencies();
                               
                        } else {
                               
                                if (initTypes != null) initTypes = null; // just in case client did something stupid...
                                if (initValues != null) initValues = null; // just in case client did something stupid...
                        }
                       
                    try {
                       
                        //constructor = klass.getConstructor(getClasses(initTypes));
                       
                        constructor = FindConstructor.getConstructor(klass, getClasses(initTypes));
                       
                    } catch(Exception e) {
                       
                        // try primitives...
                       
                        try {
                       
                                //constructor = klass.getConstructor(getClasses(convertToPrimitives(initTypes)));
                               
                                constructor = FindConstructor.getConstructor(klass, getClasses(convertToPrimitives(initTypes)));
                               
                        } catch(Exception ee) {
   
                                throw new RuntimeException("Cannot find a constructor for class: " + klass);
                        }
                    }
                }
               
                try {
                       
                        values = getValues(initValues);
                       
                } catch(Exception e) {
                       
                        new RuntimeException("Cannot instantiate values for constructor!", e);
                }
                }
           
            try {
               
                obj = constructor.newInstance(values);
               
            } catch(Exception e) {
               
                throw new RuntimeException("Cannot create instance from constructor: " + constructor, e);
            }
               
                if (props != null && props.size() > 0) {
                       
                    Iterator<String> iter = props.keySet().iterator();
                   
                    while(iter.hasNext()) {
                       
                        String name = iter.next();
                       
                        Object value = props.get(name);
                       
                        if (value instanceof DependencyKey) {
                               
                                DependencyKey dk = (DependencyKey) value;
                               
                                value = container.get(dk.getKey());
                        }
                       
                        setValue(obj, name, value);
                    }
                }
               
                return (T) obj;
            }
           
            private static boolean betterIsAssignableFrom(Class<? extends Object> klass1, Class<? extends Object> klass2) {
               
                // with autoboxing both ways...
               
                if (klass1.isAssignableFrom(klass2)) return true;
               
                Class<? extends Object> k1 = klass1.isPrimitive() ? klass1 : getPrimitiveFrom(klass1);
                Class<? extends Object> k2 = klass2.isPrimitive() ? klass2 : getPrimitiveFrom(klass2);
               
                if (k1 == null || k2 == null) return false;
               
                return k1.isAssignableFrom(k2);
            }
           
            private void checkConstructorDependencies() {
               
                Constructor<? extends Object>[] constructors = klass.getConstructors();
               
                for(Constructor<? extends Object> c : constructors) {
                       
                        LinkedList<Class<? extends Object>> providedInitTypes = null;
                       
                        if (initTypes != null) {
                               
                                providedInitTypes = new LinkedList<Class<? extends Object>>(initTypes);
                               
                        } else {
                               
                                providedInitTypes = new LinkedList<Class<? extends Object>>();
                        }
                       
                        LinkedList<Object> providedInitValues = null;
                       
                        if (initValues != null) {
                               
                                providedInitValues = new LinkedList<Object>(initValues);
                               
                        } else {
                               
                                providedInitValues = new LinkedList<Object>();
                        }
                       
                        LinkedList<Class<? extends Object>> newInitTypes = new LinkedList<Class<? extends Object>>();
                        LinkedList<Object> newInitValues = new LinkedList<Object>();
                       
                        Set<ConstructorDependency> constructorDependencies = this.constructorDependencies != null ? this.constructorDependencies : container.getConstructorDependencies();
                       
                        Set<ConstructorDependency> dependencies = new HashSet<ConstructorDependency>(constructorDependencies);
                       
                        Class<? extends Object>[] constructorParams = c.getParameterTypes();
                       
                        if (constructorParams == null || constructorParams.length == 0) continue; // skip default constructor for now...
                       
                        for(Class<? extends Object> constructorParam : constructorParams) {
                               
                                // first see if it was provided...
                               
                                Class<? extends Object> provided = providedInitTypes.isEmpty() ? null : providedInitTypes.get(0);
                               
                                if (provided != null && constructorParam.isAssignableFrom(provided)) {
                                       
                                        newInitTypes.add(providedInitTypes.removeFirst()); // we matched this one, so remove...
                                       
                                        newInitValues.add(providedInitValues.removeFirst());
                                       
                                        continue;
                                       
                                } else {
                                       
                                        // check auto-wiring...
                                       
                                        Iterator<ConstructorDependency> iter = dependencies.iterator();
                                       
                                        boolean foundMatch = false;
                                       
                                        while(iter.hasNext()) {
                                               
                                                ConstructorDependency d = iter.next();
                                               
                                                if (betterIsAssignableFrom(constructorParam, d.getSourceType())) {
                                                       
                                                        iter.remove();
                                                       
                                                        newInitTypes.add(d.getSourceType());
                                                       
                                                        newInitValues.add(new DependencyKey(d.getSource()));
                                                       
                                                        foundMatch = true;
                                                       
                                                        break;
                                                }
                                        }
                                       
                                        if (foundMatch) {
                                                continue; // next constructor param...
                                        }
                                       
                                }
                               
                                break; // no param... next constructor...
                        }
                       
                        // done, check if found...
                       
                        if (constructorParams.length == newInitTypes.size() && providedInitTypes.isEmpty()) {
                               
                                this.initTypes = newInitTypes;
                               
                                this.initValues = newInitValues;
                        }
                }
            }
           
            private static class DependencyKey {
               
                private String key;
               
                public DependencyKey(String key) { this.key = key; }
               
                private String getKey() { return key; }
            }
           
            @Override
            public Class<? extends Object> getType() {
                return klass;
            }
        }