001    /*****************************************************************************
002     * Copyright (c) PicoContainer Organization. All rights reserved.            *
003     * ------------------------------------------------------------------------- *
004     * The software in this package is published under the terms of the BSD      *
005     * style license a copy of which has been included with this distribution in *
006     * the LICENSE.txt file.                                                     *
007     *                                                                           *
008     * Idea by Rachel Davies, Original code by Aslak Hellesoy and Paul Hammant   *
009     *****************************************************************************/
010    
011    package org.picocontainer.injectors;
012    
013    import org.picocontainer.ComponentMonitor;
014    import org.picocontainer.Parameter;
015    import org.picocontainer.PicoContainer;
016    import org.picocontainer.PicoCompositionException;
017    import org.picocontainer.ParameterName;
018    import org.picocontainer.LifecycleStrategy;
019    import org.picocontainer.behaviors.Cached;
020    
021    import java.lang.reflect.Constructor;
022    import java.lang.reflect.InvocationTargetException;
023    import java.lang.reflect.Modifier;
024    import java.lang.reflect.Method;
025    import java.lang.reflect.AccessibleObject;
026    import java.lang.reflect.Field;
027    import java.security.AccessController;
028    import java.security.PrivilegedAction;
029    import java.util.ArrayList;
030    import java.util.Arrays;
031    import java.util.Collections;
032    import java.util.Comparator;
033    import java.util.HashSet;
034    import java.util.List;
035    import java.util.Set;
036    
037    /**
038     * Instantiates components using Constructor Injection.
039     * <em>
040     * Note that this class doesn't cache instances. If you want caching,
041     * use a {@link Cached} around this one.
042     * </em>
043     *
044     * @author Paul Hammant
045     * @author Aslak Helles&oslash;y
046     * @author Jon Tirs&eacute;n
047     * @author Zohar Melamed
048     * @author J&ouml;rg Schaible
049     * @author Mauro Talevi
050     */
051    public class ConstructorInjector extends SingleMemberInjector {
052        private transient List<Constructor> sortedMatchingConstructors;
053        private transient ThreadLocalCyclicDependencyGuard instantiationGuard;
054    
055        /**
056         * Creates a ConstructorInjector
057         *
058         * @param componentKey            the search key for this implementation
059         * @param componentImplementation the concrete implementation
060         * @param parameters              the parameters to use for the initialization
061         * @param monitor                 the component monitor used by this addAdapter
062         * @param lifecycleStrategy       the component lifecycle strategy used by this addAdapter
063         * @throws org.picocontainer.injectors.AbstractInjector.NotConcreteRegistrationException
064         *                              if the implementation is not a concrete class.
065         * @throws NullPointerException if one of the parameters is <code>null</code>
066         */
067        public ConstructorInjector(final Object componentKey, final Class componentImplementation, Parameter[] parameters, ComponentMonitor monitor, LifecycleStrategy lifecycleStrategy) throws  NotConcreteRegistrationException {
068            super(componentKey, componentImplementation, parameters, monitor, lifecycleStrategy);
069        }
070    
071        protected Constructor getGreediestSatisfiableConstructor(PicoContainer container) throws PicoCompositionException {
072            final Set<Constructor> conflicts = new HashSet<Constructor>();
073            final Set<List<Class>> unsatisfiableDependencyTypes = new HashSet<List<Class>>();
074            if (sortedMatchingConstructors == null) {
075                sortedMatchingConstructors = getSortedMatchingConstructors();
076            }
077            Constructor greediestConstructor = null;
078            int lastSatisfiableConstructorSize = -1;
079            Class unsatisfiedDependencyType = null;
080            for (final Constructor sortedMatchingConstructor : sortedMatchingConstructors) {
081                boolean failedDependency = false;
082                Class[] parameterTypes = sortedMatchingConstructor.getParameterTypes();
083                Parameter[] currentParameters = parameters != null ? parameters : createDefaultParameters(parameterTypes);
084    
085                // remember: all constructors with less arguments than the given parameters are filtered out already
086                for (int j = 0; j < currentParameters.length; j++) {
087                    // check wether this constructor is statisfiable
088                    if (currentParameters[j].isResolvable(container, this, parameterTypes[j],
089                             new MemberInjectorParameterName(sortedMatchingConstructor,j))) {
090                        continue;
091                    }
092                    unsatisfiableDependencyTypes.add(Arrays.asList(parameterTypes));
093                    unsatisfiedDependencyType = parameterTypes[j];
094                    failedDependency = true;
095                    break;
096                }
097    
098                if (greediestConstructor != null && parameterTypes.length != lastSatisfiableConstructorSize) {
099                    if (conflicts.isEmpty()) {
100                        // we found our match [aka. greedy and satisfied]
101                        return greediestConstructor;
102                    } else {
103                        // fits although not greedy
104                        conflicts.add(sortedMatchingConstructor);
105                    }
106                } else if (!failedDependency && lastSatisfiableConstructorSize == parameterTypes.length) {
107                    // satisfied and same size as previous one?
108                    conflicts.add(sortedMatchingConstructor);
109                    conflicts.add(greediestConstructor);
110                } else if (!failedDependency) {
111                    greediestConstructor = sortedMatchingConstructor;
112                    lastSatisfiableConstructorSize = parameterTypes.length;
113                }
114            }
115            if (!conflicts.isEmpty()) {
116                throw new PicoCompositionException(conflicts.size() + " satisfiable constructors is too many for '"+getComponentImplementation()+"'. Constructor List:" + conflicts.toString().replace(getComponentImplementation().getName(),"<init>").replace("public <i","<i"));
117            } else if (greediestConstructor == null && !unsatisfiableDependencyTypes.isEmpty()) {
118                throw new UnsatisfiableDependenciesException(this, unsatisfiedDependencyType, unsatisfiableDependencyTypes, container);
119            } else if (greediestConstructor == null) {
120                // be nice to the user, show all constructors that were filtered out
121                final Set<Constructor> nonMatching = new HashSet<Constructor>();
122                for (Constructor constructor : getConstructors()) {
123                    nonMatching.add(constructor);
124                }
125                throw new PicoCompositionException("Either the specified parameters do not match any of the following constructors: " + nonMatching.toString() + "; OR the constructors were not accessible for '" + getComponentImplementation().getName() + "'");
126            }
127            return greediestConstructor;
128        }
129    
130    
131        public Object getComponentInstance(final PicoContainer container) throws PicoCompositionException {
132            if (instantiationGuard == null) {
133                instantiationGuard = new ThreadLocalCyclicDependencyGuard() {
134                    public Object run() {
135                        Constructor constructor;
136                        try {
137                            constructor = getGreediestSatisfiableConstructor(guardedContainer);
138                        } catch (AmbiguousComponentResolutionException e) {
139                            e.setComponent(getComponentImplementation());
140                            throw e;
141                        }
142                        ComponentMonitor componentMonitor = currentMonitor();
143                        try {
144                            Object[] parameters = getMemberArguments(guardedContainer, constructor);
145                            constructor = componentMonitor.instantiating(container, ConstructorInjector.this, constructor);
146                            long startTime = System.currentTimeMillis();
147                            Object inst = newInstance(constructor, parameters);
148                            componentMonitor.instantiated(container,
149                                                          ConstructorInjector.this,
150                                                          constructor, inst, parameters, System.currentTimeMillis() - startTime);
151                            return inst;
152                        } catch (InvocationTargetException e) {
153                            componentMonitor.instantiationFailed(container, ConstructorInjector.this, constructor, e);
154                            if (e.getTargetException() instanceof RuntimeException) {
155                                throw (RuntimeException) e.getTargetException();
156                            } else if (e.getTargetException() instanceof Error) {
157                                throw (Error) e.getTargetException();
158                            }
159                            throw new PicoCompositionException(e.getTargetException());
160                        } catch (InstantiationException e) {
161                            return caughtInstantiationException(componentMonitor, constructor, e, container);
162                        } catch (IllegalAccessException e) {
163                            return caughtIllegalAccessException(componentMonitor, constructor, e, container);
164    
165                        }
166                    }
167                };
168            }
169            instantiationGuard.setGuardedContainer(container);
170            return instantiationGuard.observe(getComponentImplementation());
171        }
172    
173        protected Object[] getMemberArguments(PicoContainer container, final Constructor ctor) {
174            return super.getMemberArguments(container, ctor, ctor.getParameterTypes());
175        }
176    
177        private List<Constructor> getSortedMatchingConstructors() {
178            List<Constructor> matchingConstructors = new ArrayList<Constructor>();
179            Constructor[] allConstructors = getConstructors();
180            // filter out all constructors that will definately not match
181            for (Constructor constructor : allConstructors) {
182                if ((parameters == null || constructor.getParameterTypes().length == parameters.length) && (constructor.getModifiers() & Modifier.PUBLIC) != 0) {
183                    matchingConstructors.add(constructor);
184                }
185            }
186            // optimize list of constructors moving the longest at the beginning
187            if (parameters == null) {
188                Collections.sort(matchingConstructors, new Comparator() {
189                    public int compare(Object arg0, Object arg1) {
190                        return ((Constructor) arg1).getParameterTypes().length - ((Constructor) arg0).getParameterTypes().length;
191                    }
192                });
193            }
194            return matchingConstructors;
195        }
196    
197        private Constructor[] getConstructors() {
198            return (Constructor[]) AccessController.doPrivileged(new PrivilegedAction() {
199                public Object run() {
200                    return getComponentImplementation().getDeclaredConstructors();
201                }
202            });
203        }
204    
205        public void verify(final PicoContainer container) throws PicoCompositionException {
206            if (verifyingGuard == null) {
207                verifyingGuard = new ThreadLocalCyclicDependencyGuard() {
208                    public Object run() {
209                        final Constructor constructor = getGreediestSatisfiableConstructor(guardedContainer);
210                        final Class[] parameterTypes = constructor.getParameterTypes();
211                        final Parameter[] currentParameters = parameters != null ? parameters : createDefaultParameters(parameterTypes);
212                        for (int i = 0; i < currentParameters.length; i++) {
213                            currentParameters[i].verify(container, ConstructorInjector.this, parameterTypes[i],
214                                                        new MemberInjectorParameterName(constructor, i));
215                        }
216                        return null;
217                    }
218                };
219            }
220            verifyingGuard.setGuardedContainer(container);
221            verifyingGuard.observe(getComponentImplementation());
222        }
223    
224        public String toString() {
225            return "ConstructorInjector-" + super.toString();
226        }
227    
228    
229    }