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 }