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