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.LifecycleStrategy;
015    import org.picocontainer.Parameter;
016    import org.picocontainer.PicoCompositionException;
017    import org.picocontainer.PicoContainer;
018    import org.picocontainer.lifecycle.NullLifecycleStrategy;
019    import org.picocontainer.monitors.NullComponentMonitor;
020    
021    import java.lang.reflect.Constructor;
022    import java.lang.reflect.InvocationTargetException;
023    import java.lang.reflect.Modifier;
024    import java.lang.reflect.Type;
025    import java.lang.reflect.TypeVariable;
026    import java.lang.annotation.Annotation;
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     * Injection will happen through a constructor for the component.
039     *
040     * @author Paul Hammant
041     * @author Aslak Hellesøy
042     * @author Jon Tirsén
043     * @author Zohar Melamed
044     * @author Jörg Schaible
045     * @author Mauro Talevi
046     */
047    @SuppressWarnings("serial")
048    public class ConstructorInjector<T> extends SingleMemberInjector<T> {
049            
050            private transient List<Constructor<T>> sortedMatchingConstructors;
051        private transient ThreadLocalCyclicDependencyGuard<T> instantiationGuard;
052        private boolean rememberChosenConstructor = true;
053        private transient Constructor<T> chosenConstructor;
054    
055        /**
056         * Constructor injector that uses no monitor and no lifecycle adapter.  This is a more
057         * convenient constructor for use when instantiating a constructor injector directly.
058         * @param componentKey the search key for this implementation
059         * @param componentImplementation the concrete implementation
060         * @param parameters the parameters used for initialization
061         */
062        public ConstructorInjector(final Object componentKey, final Class<?> componentImplementation, Parameter... parameters) {
063            this(componentKey, componentImplementation, parameters, new NullComponentMonitor(), new NullLifecycleStrategy(), false);
064        }
065    
066        /**
067         * Creates a ConstructorInjector
068         *
069         * @param componentKey            the search key for this implementation
070         * @param componentImplementation the concrete implementation
071         * @param parameters              the parameters to use for the initialization
072         * @param monitor                 the component monitor used by this addAdapter
073         * @param lifecycleStrategy       the component lifecycle strategy used by this addAdapter
074         * @param useNames                use argument names when looking up dependencies
075         * @throws org.picocontainer.injectors.AbstractInjector.NotConcreteRegistrationException
076         *                              if the implementation is not a concrete class.
077         * @throws NullPointerException if one of the parameters is <code>null</code>
078         */
079        public ConstructorInjector(final Object componentKey, final Class componentImplementation, Parameter[] parameters, ComponentMonitor monitor,
080                                   LifecycleStrategy lifecycleStrategy, boolean useNames) throws  NotConcreteRegistrationException {
081            super(componentKey, componentImplementation, parameters, monitor, lifecycleStrategy, useNames);
082        }
083    
084        /**
085         * Creates a ConstructorInjector
086         *
087         * @param componentKey            the search key for this implementation
088         * @param componentImplementation the concrete implementation
089         * @param parameters              the parameters to use for the initialization
090         * @param monitor                 the component monitor used by this addAdapter
091         * @param lifecycleStrategy       the component lifecycle strategy used by this addAdapter
092         * @param useNames                use argument names when looking up dependencies
093         * @param rememberChosenCtor      remember the chosen constructor (to speed up second/subsequent calls)
094         * @throws org.picocontainer.injectors.AbstractInjector.NotConcreteRegistrationException
095         *                              if the implementation is not a concrete class.
096         * @throws NullPointerException if one of the parameters is <code>null</code>
097         */
098        public ConstructorInjector(final Object componentKey, final Class componentImplementation, Parameter[] parameters, ComponentMonitor monitor,
099                                   LifecycleStrategy lifecycleStrategy, boolean useNames, boolean rememberChosenCtor) throws  NotConcreteRegistrationException {
100            super(componentKey, componentImplementation, parameters, monitor, lifecycleStrategy, useNames);
101            this.rememberChosenConstructor = rememberChosenCtor;
102        }
103    
104        protected Constructor<T> getGreediestSatisfiableConstructor(PicoContainer container) throws PicoCompositionException {
105            final Set<Constructor> conflicts = new HashSet<Constructor>();
106            final Set<List<Type>> unsatisfiableDependencyTypes = new HashSet<List<Type>>();
107            if (sortedMatchingConstructors == null) {
108                sortedMatchingConstructors = getSortedMatchingConstructors();
109            }
110            Constructor<T> greediestConstructor = null;
111            int lastSatisfiableConstructorSize = -1;
112            Type unsatisfiedDependencyType = null;
113            for (final Constructor<T> sortedMatchingConstructor : sortedMatchingConstructors) {
114                boolean failedDependency = false;
115                Type[] parameterTypes = sortedMatchingConstructor.getGenericParameterTypes();
116                fixParameterType(sortedMatchingConstructor, parameterTypes);
117                Annotation[] bindings = getBindings(sortedMatchingConstructor.getParameterAnnotations());
118                Parameter[] currentParameters = parameters != null ? parameters : createDefaultParameters(parameterTypes);
119    
120                // remember: all constructors with less arguments than the given parameters are filtered out already
121                for (int j = 0; j < currentParameters.length; j++) {
122                    // check whether this constructor is satisfiable
123                    Type boxed = box(parameterTypes[j]);
124                    boolean un = useNames();
125                    if (currentParameters[j].isResolvable(container, this, boxed,
126                            new ParameterNameBinding(getParanamer(), getComponentImplementation(), sortedMatchingConstructor, j),
127                            un, bindings[j])) {
128                        continue;
129                    }
130                    unsatisfiableDependencyTypes.add(Arrays.asList(parameterTypes));
131                    unsatisfiedDependencyType = box(parameterTypes[j]);
132                    failedDependency = true;
133                    break;
134                }
135    
136                if (greediestConstructor != null && parameterTypes.length != lastSatisfiableConstructorSize) {
137                    if (conflicts.isEmpty()) {
138                        // we found our match [aka. greedy and satisfied]
139                        return greediestConstructor;
140                    } else {
141                        // fits although not greedy
142                        conflicts.add(sortedMatchingConstructor);
143                    }
144                } else if (!failedDependency && lastSatisfiableConstructorSize == parameterTypes.length) {
145                    // satisfied and same size as previous one?
146                    conflicts.add(sortedMatchingConstructor);
147                    conflicts.add(greediestConstructor);
148                } else if (!failedDependency) {
149                    greediestConstructor = sortedMatchingConstructor;
150                    lastSatisfiableConstructorSize = parameterTypes.length;
151                }
152            }
153            if (!conflicts.isEmpty()) {
154                throw new PicoCompositionException(conflicts.size() + " satisfiable constructors is too many for '"+getComponentImplementation()+"'. Constructor List:" + conflicts.toString().replace(getComponentImplementation().getName(),"<init>").replace("public <i","<i"));
155            } else if (greediestConstructor == null && !unsatisfiableDependencyTypes.isEmpty()) {
156                throw new UnsatisfiableDependenciesException(this, unsatisfiedDependencyType, unsatisfiableDependencyTypes, container);
157            } else if (greediestConstructor == null) {
158                // be nice to the user, show all constructors that were filtered out
159                final Set<Constructor> nonMatching = new HashSet<Constructor>();
160                for (Constructor constructor : getConstructors()) {
161                    nonMatching.add(constructor);
162                }
163                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() + "'");
164            }
165            return greediestConstructor;
166        }
167    
168        private void fixParameterType(Constructor<T> sortedMatchingConstructor, Type[] parameterTypes) {
169            for (int i = 0; i < parameterTypes.length; i++) {
170                Type parameterType = parameterTypes[i];
171                if (parameterType instanceof TypeVariable) {
172                    parameterTypes[i] = sortedMatchingConstructor.getParameterTypes()[i];
173                }
174            }
175        }
176    
177        public T getComponentInstance(final PicoContainer container, Type into) throws PicoCompositionException {
178            if (instantiationGuard == null) {
179                instantiationGuard = new ThreadLocalCyclicDependencyGuard<T>() {
180                    public T run() {
181                        Constructor<T> ctor = null;
182                        try {
183                            if (chosenConstructor == null) {
184                                ctor = getGreediestSatisfiableConstructor(guardedContainer);
185                            }
186                            if (rememberChosenConstructor) {
187                                if (chosenConstructor == null) {
188                                    chosenConstructor = ctor;
189                                } else {
190                                    ctor = chosenConstructor;
191                                }
192                            }
193                        } catch (AmbiguousComponentResolutionException e) {
194                            e.setComponent(getComponentImplementation());
195                            throw e;
196                        }
197                        ComponentMonitor componentMonitor = currentMonitor();
198                        try {
199                            Object[] parameters = getMemberArguments(guardedContainer, ctor);
200                            ctor = componentMonitor.instantiating(container, ConstructorInjector.this, ctor);
201                            if(ctor == null) {
202                                throw new NullPointerException("Component Monitor " + componentMonitor 
203                                                + " returned a null constructor from method 'instantiating' after passing in " + ctor);
204                            }
205                            long startTime = System.currentTimeMillis();
206                            T inst = instantiate(ctor, parameters);
207                            componentMonitor.instantiated(container,
208                                                          ConstructorInjector.this,
209                                    ctor, inst, parameters, System.currentTimeMillis() - startTime);
210                            return inst;
211                        } catch (InvocationTargetException e) {
212                            componentMonitor.instantiationFailed(container, ConstructorInjector.this, ctor, e);
213                            if (e.getTargetException() instanceof RuntimeException) {
214                                throw (RuntimeException) e.getTargetException();
215                            } else if (e.getTargetException() instanceof Error) {
216                                throw (Error) e.getTargetException();
217                            }
218                            throw new PicoCompositionException(e.getTargetException());
219                        } catch (InstantiationException e) {
220                            return caughtInstantiationException(componentMonitor, ctor, e, container);
221                        } catch (IllegalAccessException e) {
222                            return caughtIllegalAccessException(componentMonitor, ctor, e, container);
223    
224                        }
225                    }
226                };
227            }
228            instantiationGuard.setGuardedContainer(container);
229            return instantiationGuard.observe(getComponentImplementation());
230        }
231    
232        protected T instantiate(Constructor<T> constructor, Object[] parameters) throws InstantiationException, IllegalAccessException, InvocationTargetException {
233            T inst = newInstance(constructor, parameters);
234            return inst;
235        }
236    
237        protected Object[] getMemberArguments(PicoContainer container, final Constructor ctor) {
238            Type[] parameterTypes = ctor.getGenericParameterTypes();
239            fixParameterType(ctor, parameterTypes);
240            return super.getMemberArguments(container, ctor, parameterTypes, getBindings(ctor.getParameterAnnotations()));
241        }
242    
243        private List<Constructor<T>> getSortedMatchingConstructors() {
244            List<Constructor<T>> matchingConstructors = new ArrayList<Constructor<T>>();
245            Constructor<T>[] allConstructors = getConstructors();
246            // filter out all constructors that will definately not match
247            for (Constructor<T> constructor : allConstructors) {
248                if ((parameters == null || constructor.getParameterTypes().length == parameters.length) && (constructor.getModifiers() & Modifier.PUBLIC) != 0) {
249                    matchingConstructors.add(constructor);
250                }
251            }
252            // optimize list of constructors moving the longest at the beginning
253            if (parameters == null) {               
254                Collections.sort(matchingConstructors, new Comparator<Constructor>() {
255                    public int compare(Constructor arg0, Constructor arg1) {
256                        return arg1.getParameterTypes().length - arg0.getParameterTypes().length;
257                    }
258                });
259            }
260            return matchingConstructors;
261        }
262    
263        private Constructor<T>[] getConstructors() {
264            return AccessController.doPrivileged(new PrivilegedAction<Constructor<T>[]>() {
265                public Constructor<T>[] run() {
266                    return (Constructor<T>[]) getComponentImplementation().getDeclaredConstructors();
267                }
268            });
269        }
270    
271        @Override
272        public void verify(final PicoContainer container) throws PicoCompositionException {
273            if (verifyingGuard == null) {
274                verifyingGuard = new ThreadLocalCyclicDependencyGuard() {
275                    public Object run() {
276                        final Constructor constructor = getGreediestSatisfiableConstructor(guardedContainer);
277                        final Class[] parameterTypes = constructor.getParameterTypes();
278                        final Parameter[] currentParameters = parameters != null ? parameters : createDefaultParameters(parameterTypes);
279                        for (int i = 0; i < currentParameters.length; i++) {
280                            currentParameters[i].verify(container, ConstructorInjector.this, box(parameterTypes[i]),
281                                new ParameterNameBinding(getParanamer(), getComponentImplementation(),  constructor, i),
282                                    useNames(), getBindings(constructor.getParameterAnnotations())[i]);
283                        }
284                        return null;
285                    }
286                };
287            }
288            verifyingGuard.setGuardedContainer(container);
289            verifyingGuard.observe(getComponentImplementation());
290        }
291    
292        public String getDescriptor() {
293            return "ConstructorInjector-";
294        }
295    
296    
297    }