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.Parameter;
015 import org.picocontainer.PicoContainer;
016 import org.picocontainer.PicoCompositionException;
017 import org.picocontainer.ParameterName;
018 import org.picocontainer.LifecycleStrategy;
019 import org.picocontainer.behaviors.Cached;
020
021 import java.lang.reflect.Constructor;
022 import java.lang.reflect.InvocationTargetException;
023 import java.lang.reflect.Modifier;
024 import java.lang.reflect.Method;
025 import java.lang.reflect.AccessibleObject;
026 import java.lang.reflect.Field;
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 * Instantiates components using Constructor Injection.
039 * <em>
040 * Note that this class doesn't cache instances. If you want caching,
041 * use a {@link Cached} around this one.
042 * </em>
043 *
044 * @author Paul Hammant
045 * @author Aslak Hellesøy
046 * @author Jon Tirsén
047 * @author Zohar Melamed
048 * @author Jörg Schaible
049 * @author Mauro Talevi
050 */
051 public class ConstructorInjector extends SingleMemberInjector {
052 private transient List<Constructor> sortedMatchingConstructors;
053 private transient ThreadLocalCyclicDependencyGuard instantiationGuard;
054
055 /**
056 * Creates a ConstructorInjector
057 *
058 * @param componentKey the search key for this implementation
059 * @param componentImplementation the concrete implementation
060 * @param parameters the parameters to use for the initialization
061 * @param monitor the component monitor used by this addAdapter
062 * @param lifecycleStrategy the component lifecycle strategy used by this addAdapter
063 * @throws org.picocontainer.injectors.AbstractInjector.NotConcreteRegistrationException
064 * if the implementation is not a concrete class.
065 * @throws NullPointerException if one of the parameters is <code>null</code>
066 */
067 public ConstructorInjector(final Object componentKey, final Class componentImplementation, Parameter[] parameters, ComponentMonitor monitor, LifecycleStrategy lifecycleStrategy) throws NotConcreteRegistrationException {
068 super(componentKey, componentImplementation, parameters, monitor, lifecycleStrategy);
069 }
070
071 protected Constructor getGreediestSatisfiableConstructor(PicoContainer container) throws PicoCompositionException {
072 final Set<Constructor> conflicts = new HashSet<Constructor>();
073 final Set<List<Class>> unsatisfiableDependencyTypes = new HashSet<List<Class>>();
074 if (sortedMatchingConstructors == null) {
075 sortedMatchingConstructors = getSortedMatchingConstructors();
076 }
077 Constructor greediestConstructor = null;
078 int lastSatisfiableConstructorSize = -1;
079 Class unsatisfiedDependencyType = null;
080 for (final Constructor sortedMatchingConstructor : sortedMatchingConstructors) {
081 boolean failedDependency = false;
082 Class[] parameterTypes = sortedMatchingConstructor.getParameterTypes();
083 Parameter[] currentParameters = parameters != null ? parameters : createDefaultParameters(parameterTypes);
084
085 // remember: all constructors with less arguments than the given parameters are filtered out already
086 for (int j = 0; j < currentParameters.length; j++) {
087 // check wether this constructor is statisfiable
088 if (currentParameters[j].isResolvable(container, this, parameterTypes[j],
089 new MemberInjectorParameterName(sortedMatchingConstructor,j))) {
090 continue;
091 }
092 unsatisfiableDependencyTypes.add(Arrays.asList(parameterTypes));
093 unsatisfiedDependencyType = parameterTypes[j];
094 failedDependency = true;
095 break;
096 }
097
098 if (greediestConstructor != null && parameterTypes.length != lastSatisfiableConstructorSize) {
099 if (conflicts.isEmpty()) {
100 // we found our match [aka. greedy and satisfied]
101 return greediestConstructor;
102 } else {
103 // fits although not greedy
104 conflicts.add(sortedMatchingConstructor);
105 }
106 } else if (!failedDependency && lastSatisfiableConstructorSize == parameterTypes.length) {
107 // satisfied and same size as previous one?
108 conflicts.add(sortedMatchingConstructor);
109 conflicts.add(greediestConstructor);
110 } else if (!failedDependency) {
111 greediestConstructor = sortedMatchingConstructor;
112 lastSatisfiableConstructorSize = parameterTypes.length;
113 }
114 }
115 if (!conflicts.isEmpty()) {
116 throw new PicoCompositionException(conflicts.size() + " satisfiable constructors is too many for '"+getComponentImplementation()+"'. Constructor List:" + conflicts.toString().replace(getComponentImplementation().getName(),"<init>").replace("public <i","<i"));
117 } else if (greediestConstructor == null && !unsatisfiableDependencyTypes.isEmpty()) {
118 throw new UnsatisfiableDependenciesException(this, unsatisfiedDependencyType, unsatisfiableDependencyTypes, container);
119 } else if (greediestConstructor == null) {
120 // be nice to the user, show all constructors that were filtered out
121 final Set<Constructor> nonMatching = new HashSet<Constructor>();
122 for (Constructor constructor : getConstructors()) {
123 nonMatching.add(constructor);
124 }
125 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() + "'");
126 }
127 return greediestConstructor;
128 }
129
130
131 public Object getComponentInstance(final PicoContainer container) throws PicoCompositionException {
132 if (instantiationGuard == null) {
133 instantiationGuard = new ThreadLocalCyclicDependencyGuard() {
134 public Object run() {
135 Constructor constructor;
136 try {
137 constructor = getGreediestSatisfiableConstructor(guardedContainer);
138 } catch (AmbiguousComponentResolutionException e) {
139 e.setComponent(getComponentImplementation());
140 throw e;
141 }
142 ComponentMonitor componentMonitor = currentMonitor();
143 try {
144 Object[] parameters = getMemberArguments(guardedContainer, constructor);
145 constructor = componentMonitor.instantiating(container, ConstructorInjector.this, constructor);
146 long startTime = System.currentTimeMillis();
147 Object inst = newInstance(constructor, parameters);
148 componentMonitor.instantiated(container,
149 ConstructorInjector.this,
150 constructor, inst, parameters, System.currentTimeMillis() - startTime);
151 return inst;
152 } catch (InvocationTargetException e) {
153 componentMonitor.instantiationFailed(container, ConstructorInjector.this, constructor, e);
154 if (e.getTargetException() instanceof RuntimeException) {
155 throw (RuntimeException) e.getTargetException();
156 } else if (e.getTargetException() instanceof Error) {
157 throw (Error) e.getTargetException();
158 }
159 throw new PicoCompositionException(e.getTargetException());
160 } catch (InstantiationException e) {
161 return caughtInstantiationException(componentMonitor, constructor, e, container);
162 } catch (IllegalAccessException e) {
163 return caughtIllegalAccessException(componentMonitor, constructor, e, container);
164
165 }
166 }
167 };
168 }
169 instantiationGuard.setGuardedContainer(container);
170 return instantiationGuard.observe(getComponentImplementation());
171 }
172
173 protected Object[] getMemberArguments(PicoContainer container, final Constructor ctor) {
174 return super.getMemberArguments(container, ctor, ctor.getParameterTypes());
175 }
176
177 private List<Constructor> getSortedMatchingConstructors() {
178 List<Constructor> matchingConstructors = new ArrayList<Constructor>();
179 Constructor[] allConstructors = getConstructors();
180 // filter out all constructors that will definately not match
181 for (Constructor constructor : allConstructors) {
182 if ((parameters == null || constructor.getParameterTypes().length == parameters.length) && (constructor.getModifiers() & Modifier.PUBLIC) != 0) {
183 matchingConstructors.add(constructor);
184 }
185 }
186 // optimize list of constructors moving the longest at the beginning
187 if (parameters == null) {
188 Collections.sort(matchingConstructors, new Comparator() {
189 public int compare(Object arg0, Object arg1) {
190 return ((Constructor) arg1).getParameterTypes().length - ((Constructor) arg0).getParameterTypes().length;
191 }
192 });
193 }
194 return matchingConstructors;
195 }
196
197 private Constructor[] getConstructors() {
198 return (Constructor[]) AccessController.doPrivileged(new PrivilegedAction() {
199 public Object run() {
200 return getComponentImplementation().getDeclaredConstructors();
201 }
202 });
203 }
204
205 public void verify(final PicoContainer container) throws PicoCompositionException {
206 if (verifyingGuard == null) {
207 verifyingGuard = new ThreadLocalCyclicDependencyGuard() {
208 public Object run() {
209 final Constructor constructor = getGreediestSatisfiableConstructor(guardedContainer);
210 final Class[] parameterTypes = constructor.getParameterTypes();
211 final Parameter[] currentParameters = parameters != null ? parameters : createDefaultParameters(parameterTypes);
212 for (int i = 0; i < currentParameters.length; i++) {
213 currentParameters[i].verify(container, ConstructorInjector.this, parameterTypes[i],
214 new MemberInjectorParameterName(constructor, i));
215 }
216 return null;
217 }
218 };
219 }
220 verifyingGuard.setGuardedContainer(container);
221 verifyingGuard.observe(getComponentImplementation());
222 }
223
224 public String toString() {
225 return "ConstructorInjector-" + super.toString();
226 }
227
228
229 }