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øy
041 * @author Jon Tirsén
042 * @author Zohar Melamed
043 * @author Jö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 }