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<Type>> unsatisfiableDependencyTypes = new HashSet<List<Type>>();
106 if (sortedMatchingConstructors == null) {
107 sortedMatchingConstructors = getSortedMatchingConstructors();
108 }
109 Constructor<T> greediestConstructor = null;
110 int lastSatisfiableConstructorSize = -1;
111 Type unsatisfiedDependencyType = null;
112 for (final Constructor<T> sortedMatchingConstructor : sortedMatchingConstructors) {
113 boolean failedDependency = false;
114 Type[] parameterTypes = sortedMatchingConstructor.getGenericParameterTypes();
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 satisfiable
121 Type 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.getGenericParameterTypes(), 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 }