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.defaults;
012    
013    import org.picocontainer.ComponentMonitor;
014    import org.picocontainer.Parameter;
015    import org.picocontainer.PicoContainer;
016    import org.picocontainer.PicoInitializationException;
017    import org.picocontainer.PicoIntrospectionException;
018    
019    import java.lang.reflect.Constructor;
020    import java.lang.reflect.InvocationTargetException;
021    import java.lang.reflect.Modifier;
022    import java.security.AccessController;
023    import java.security.PrivilegedAction;
024    import java.util.ArrayList;
025    import java.util.Arrays;
026    import java.util.Collections;
027    import java.util.Comparator;
028    import java.util.HashSet;
029    import java.util.List;
030    import java.util.Set;
031    
032    /**
033     * Instantiates components using Constructor Injection.
034     * <em>
035     * Note that this class doesn't cache instances. If you want caching,
036     * use a {@link CachingComponentAdapter} around this one.
037     * </em>
038     *
039     * @author Paul Hammant
040     * @author Aslak Helles&oslash;y
041     * @author Jon Tirs&eacute;n
042     * @author Zohar Melamed
043     * @author J&ouml;rg Schaible
044     * @author Mauro Talevi
045     * @version $Revision: 2971 $
046     */
047    public class ConstructorInjectionComponentAdapter extends InstantiatingComponentAdapter {
048        private transient List sortedMatchingConstructors;
049        private transient Guard instantiationGuard;
050    
051        private static abstract class Guard extends ThreadLocalCyclicDependencyGuard {
052            protected PicoContainer guardedContainer;
053    
054            private void setArguments(PicoContainer container) {
055                this.guardedContainer = container;
056            }
057        }
058    
059        /**
060         * Creates a ConstructorInjectionComponentAdapter
061         *
062         * @param componentKey            the search key for this implementation
063         * @param componentImplementation the concrete implementation
064         * @param parameters              the parameters to use for the initialization
065         * @param allowNonPublicClasses   flag to allow instantiation of non-public classes.
066         * @param monitor                 the component monitor used by this adapter
067         * @param lifecycleStrategy       the component lifecycle strategy used by this adapter
068         * @throws AssignabilityRegistrationException
069         *                              if the key is a type and the implementation cannot be assigned to.
070         * @throws NotConcreteRegistrationException
071         *                              if the implementation is not a concrete class.
072         * @throws NullPointerException if one of the parameters is <code>null</code>
073         */
074        public ConstructorInjectionComponentAdapter(final Object componentKey, final Class componentImplementation, Parameter[] parameters, boolean allowNonPublicClasses, ComponentMonitor monitor, LifecycleStrategy lifecycleStrategy) throws AssignabilityRegistrationException, NotConcreteRegistrationException {
075            super(componentKey, componentImplementation, parameters, allowNonPublicClasses, monitor, lifecycleStrategy);
076        }
077    
078        /**
079         * Creates a ConstructorInjectionComponentAdapter
080         *
081         * @param componentKey            the search key for this implementation
082         * @param componentImplementation the concrete implementation
083         * @param parameters              the parameters to use for the initialization
084         * @param allowNonPublicClasses   flag to allow instantiation of non-public classes.
085         * @param monitor                 the component monitor used by this adapter
086         * @throws AssignabilityRegistrationException
087         *                              if the key is a type and the implementation cannot be assigned to.
088         * @throws NotConcreteRegistrationException
089         *                              if the implementation is not a concrete class.
090         * @throws NullPointerException if one of the parameters is <code>null</code>
091         */
092        public ConstructorInjectionComponentAdapter(final Object componentKey, final Class componentImplementation, Parameter[] parameters, boolean allowNonPublicClasses, ComponentMonitor monitor) throws AssignabilityRegistrationException, NotConcreteRegistrationException {
093            super(componentKey, componentImplementation, parameters, allowNonPublicClasses, monitor);
094        }
095    
096        /**
097         * Creates a ConstructorInjectionComponentAdapter
098         *
099         * @param componentKey            the search key for this implementation
100         * @param componentImplementation the concrete implementation
101         * @param parameters              the parameters to use for the initialization
102         * @param allowNonPublicClasses   flag to allow instantiation of non-public classes.
103         * @throws AssignabilityRegistrationException
104         *                              if the key is a type and the implementation cannot be assigned to.
105         * @throws NotConcreteRegistrationException
106         *                              if the implementation is not a concrete class.
107         * @throws NullPointerException if one of the parameters is <code>null</code>
108         */
109        public ConstructorInjectionComponentAdapter(final Object componentKey, final Class componentImplementation, Parameter[] parameters, boolean allowNonPublicClasses) throws AssignabilityRegistrationException, NotConcreteRegistrationException {
110            super(componentKey, componentImplementation, parameters, allowNonPublicClasses);
111        }
112    
113        /**
114         * Creates a ConstructorInjectionComponentAdapter with key, implementation and parameters
115         *
116         * @param componentKey            the search key for this implementation
117         * @param componentImplementation the concrete implementation
118         * @param parameters              the parameters to use for the initialization
119         * @throws AssignabilityRegistrationException
120         *                              if the key is a type and the implementation cannot be assigned to.
121         * @throws NotConcreteRegistrationException
122         *                              if the implementation is not a concrete class.
123         * @throws NullPointerException if one of the parameters is <code>null</code>
124         */
125        public ConstructorInjectionComponentAdapter(Object componentKey, Class componentImplementation, Parameter[] parameters) {
126            this(componentKey, componentImplementation, parameters, false);
127        }
128    
129        /**
130         * Creates a ConstructorInjectionComponentAdapter with key and implementation
131         *
132         * @param componentKey            the search key for this implementation
133         * @param componentImplementation the concrete implementation
134         * @throws AssignabilityRegistrationException
135         *                              if the key is a type and the implementation cannot be assigned to.
136         * @throws NotConcreteRegistrationException
137         *                              if the implementation is not a concrete class.
138         * @throws NullPointerException if one of the parameters is <code>null</code>
139         */
140        public ConstructorInjectionComponentAdapter(Object componentKey, Class componentImplementation) throws AssignabilityRegistrationException, NotConcreteRegistrationException {
141            this(componentKey, componentImplementation, null);
142        }
143    
144        protected Constructor getGreediestSatisfiableConstructor(PicoContainer container) throws PicoIntrospectionException, UnsatisfiableDependenciesException, AmbiguousComponentResolutionException, AssignabilityRegistrationException, NotConcreteRegistrationException {
145            final Set conflicts = new HashSet();
146            final Set unsatisfiableDependencyTypes = new HashSet();
147            if (sortedMatchingConstructors == null) {
148                sortedMatchingConstructors = getSortedMatchingConstructors();
149            }
150            Constructor greediestConstructor = null;
151            int lastSatisfiableConstructorSize = -1;
152            Class unsatisfiedDependencyType = null;
153            for (int i = 0; i < sortedMatchingConstructors.size(); i++) {
154                boolean failedDependency = false;
155                Constructor constructor = (Constructor) sortedMatchingConstructors.get(i);
156                Class[] parameterTypes = constructor.getParameterTypes();
157                Parameter[] currentParameters = parameters != null ? parameters : createDefaultParameters(parameterTypes);
158    
159                // remember: all constructors with less arguments than the given parameters are filtered out already
160                for (int j = 0; j < currentParameters.length; j++) {
161                    // check wether this constructor is statisfiable
162                    if (currentParameters[j].isResolvable(container, this, parameterTypes[j])) {
163                        continue;
164                    }
165                    unsatisfiableDependencyTypes.add(Arrays.asList(parameterTypes));
166                    unsatisfiedDependencyType = parameterTypes[j];
167                    failedDependency = true;
168                    break;
169                }
170    
171                if (greediestConstructor != null && parameterTypes.length != lastSatisfiableConstructorSize) {
172                    if (conflicts.isEmpty()) {
173                        // we found our match [aka. greedy and satisfied]
174                        return greediestConstructor;
175                    } else {
176                        // fits although not greedy
177                        conflicts.add(constructor);
178                    }
179                } else if (!failedDependency && lastSatisfiableConstructorSize == parameterTypes.length) {
180                    // satisfied and same size as previous one?
181                    conflicts.add(constructor);
182                    conflicts.add(greediestConstructor);
183                } else if (!failedDependency) {
184                    greediestConstructor = constructor;
185                    lastSatisfiableConstructorSize = parameterTypes.length;
186                }
187            }
188            if (!conflicts.isEmpty()) {
189                throw new TooManySatisfiableConstructorsException(getComponentImplementation(), conflicts);
190            } else if (greediestConstructor == null && !unsatisfiableDependencyTypes.isEmpty()) {
191                throw new UnsatisfiableDependenciesException(this, unsatisfiedDependencyType, unsatisfiableDependencyTypes, container);
192            } else if (greediestConstructor == null) {
193                // be nice to the user, show all constructors that were filtered out
194                final Set nonMatching = new HashSet();
195                final Constructor[] constructors = getConstructors();
196                for (int i = 0; i < constructors.length; i++) {
197                    nonMatching.add(constructors[i]);
198                }
199                throw new PicoInitializationException("Either do the specified parameters not match any of the following constructors: " + nonMatching.toString() + " or the constructors were not accessible for '" + getComponentImplementation() + "'");
200            }
201            return greediestConstructor;
202        }
203    
204        public Object getComponentInstance(PicoContainer container) throws PicoInitializationException, PicoIntrospectionException, AssignabilityRegistrationException, NotConcreteRegistrationException {
205            if (instantiationGuard == null) {
206                instantiationGuard = new Guard() {
207                    public Object run() {
208                        final Constructor constructor;
209                        try {
210                            constructor = getGreediestSatisfiableConstructor(guardedContainer);
211                        } catch (AmbiguousComponentResolutionException e) {
212                            e.setComponent(getComponentImplementation());
213                            throw e;
214                        }
215                        ComponentMonitor componentMonitor = currentMonitor();
216                        try {
217                            Object[] parameters = getConstructorArguments(guardedContainer, constructor);
218                            componentMonitor.instantiating(constructor);
219                            long startTime = System.currentTimeMillis();
220                            Object inst = newInstance(constructor, parameters);
221                            componentMonitor.instantiated(constructor, inst, parameters, System.currentTimeMillis() - startTime);
222                            return inst;
223                        } catch (InvocationTargetException e) {
224                            componentMonitor.instantiationFailed(constructor, e);
225                            if (e.getTargetException() instanceof RuntimeException) {
226                                throw (RuntimeException) e.getTargetException();
227                            } else if (e.getTargetException() instanceof Error) {
228                                throw (Error) e.getTargetException();
229                            }
230                            throw new PicoInvocationTargetInitializationException(e.getTargetException());
231                        } catch (InstantiationException e) {
232                            // can't get here because checkConcrete() will catch it earlier, but see PICO-191
233                            ///CLOVER:OFF
234                            componentMonitor.instantiationFailed(constructor, e);
235                            throw new PicoInitializationException("Should never get here");
236                            ///CLOVER:ON
237                        } catch (IllegalAccessException e) {
238                            // can't get here because either filtered or access mode set
239                            ///CLOVER:OFF
240                            componentMonitor.instantiationFailed(constructor, e);
241                            throw new PicoInitializationException(e);
242                            ///CLOVER:ON
243                        }
244                    }
245                };
246            }
247            instantiationGuard.setArguments(container);
248            return instantiationGuard.observe(getComponentImplementation());
249        }
250    
251        protected Object[] getConstructorArguments(PicoContainer container, Constructor ctor) {
252            Class[] parameterTypes = ctor.getParameterTypes();
253            Object[] result = new Object[parameterTypes.length];
254            Parameter[] currentParameters = parameters != null ? parameters : createDefaultParameters(parameterTypes);
255    
256            for (int i = 0; i < currentParameters.length; i++) {
257                result[i] = currentParameters[i].resolveInstance(container, this, parameterTypes[i]);
258            }
259            return result;
260        }
261    
262        private List getSortedMatchingConstructors() {
263            List matchingConstructors = new ArrayList();
264            Constructor[] allConstructors = getConstructors();
265            // filter out all constructors that will definately not match
266            for (int i = 0; i < allConstructors.length; i++) {
267                Constructor constructor = allConstructors[i];
268                if ((parameters == null || constructor.getParameterTypes().length == parameters.length) && (allowNonPublicClasses || (constructor.getModifiers() & Modifier.PUBLIC) != 0)) {
269                    matchingConstructors.add(constructor);
270                }
271            }
272            // optimize list of constructors moving the longest at the beginning
273            if (parameters == null) {
274                Collections.sort(matchingConstructors, new Comparator() {
275                    public int compare(Object arg0, Object arg1) {
276                        return ((Constructor) arg1).getParameterTypes().length - ((Constructor) arg0).getParameterTypes().length;
277                    }
278                });
279            }
280            return matchingConstructors;
281        }
282    
283        private Constructor[] getConstructors() {
284            return (Constructor[]) AccessController.doPrivileged(new PrivilegedAction() {
285                public Object run() {
286                    return getComponentImplementation().getDeclaredConstructors();
287                }
288            });
289        }
290    }