001 package org.picocontainer.injectors;
002
003 import com.thoughtworks.paranamer.AdaptiveParanamer;
004 import com.thoughtworks.paranamer.AnnotationParanamer;
005 import com.thoughtworks.paranamer.CachingParanamer;
006 import com.thoughtworks.paranamer.Paranamer;
007 import org.picocontainer.ComponentMonitor;
008 import org.picocontainer.NameBinding;
009 import org.picocontainer.Parameter;
010 import org.picocontainer.PicoCompositionException;
011 import org.picocontainer.PicoContainer;
012 import org.picocontainer.annotations.Bind;
013
014 import java.lang.annotation.Annotation;
015 import java.lang.reflect.AccessibleObject;
016 import java.lang.reflect.Constructor;
017 import java.lang.reflect.InvocationTargetException;
018 import java.lang.reflect.Member;
019 import java.lang.reflect.Method;
020 import java.lang.reflect.Type;
021 import java.lang.reflect.TypeVariable;
022 import java.security.AccessController;
023 import java.security.PrivilegedAction;
024 import java.util.ArrayList;
025 import java.util.Collections;
026 import java.util.HashSet;
027 import java.util.List;
028 import java.util.Set;
029
030 /**
031 * Injection will happen iteratively after component instantiation
032 */
033 public abstract class IterativeInjector<T> extends AbstractInjector<T> {
034
035 private static final Object[] NONE = new Object[0];
036
037 private transient ThreadLocalCyclicDependencyGuard instantiationGuard;
038 protected transient List<AccessibleObject> injectionMembers;
039 protected transient Type[] injectionTypes;
040 protected transient Annotation[] bindings;
041
042 private transient Paranamer paranamer;
043 private transient volatile boolean initialized;
044
045 /**
046 * Constructs a IterativeInjector
047 *
048 * @param componentKey the search key for this implementation
049 * @param componentImplementation the concrete implementation
050 * @param parameters the parameters to use for the initialization
051 * @param monitor the component monitor used by this addAdapter
052 * @param useNames use argument names when looking up dependencies
053 * @throws org.picocontainer.injectors.AbstractInjector.NotConcreteRegistrationException
054 * if the implementation is not a concrete class.
055 * @throws NullPointerException if one of the parameters is <code>null</code>
056 */
057 public IterativeInjector(final Object componentKey, final Class componentImplementation, Parameter[] parameters, ComponentMonitor monitor,
058 boolean useNames) throws NotConcreteRegistrationException {
059 super(componentKey, componentImplementation, parameters, monitor, useNames);
060 }
061
062 protected Constructor getConstructor() {
063 Object retVal = AccessController.doPrivileged(new PrivilegedAction<Object>() {
064 public Object run() {
065 try {
066 return getComponentImplementation().getConstructor((Class[])null);
067 } catch (NoSuchMethodException e) {
068 return new PicoCompositionException(e);
069 } catch (SecurityException e) {
070 return new PicoCompositionException(e);
071 }
072 }
073 });
074 if (retVal instanceof Constructor) {
075 return (Constructor) retVal;
076 } else {
077 throw (PicoCompositionException) retVal;
078 }
079 }
080
081 private Parameter[] getMatchingParameterListForSetters(PicoContainer container) throws PicoCompositionException {
082 if (initialized == false) {
083 synchronized (this) {
084 if (initialized == false) {
085 initializeInjectionMembersAndTypeLists();
086 }
087 }
088 }
089
090 final List<Object> matchingParameterList = new ArrayList<Object>(Collections.nCopies(injectionMembers.size(), null));
091
092 final Parameter[] currentParameters = parameters != null ? parameters : createDefaultParameters(injectionTypes.length);
093 final Set<Integer> nonMatchingParameterPositions = matchParameters(container, matchingParameterList, currentParameters);
094
095 final Set<Type> unsatisfiableDependencyTypes = new HashSet<Type>();
096 final List<AccessibleObject> unsatisfiableDependencyMembers = new ArrayList<AccessibleObject>();
097 for (int i = 0; i < matchingParameterList.size(); i++) {
098 if (matchingParameterList.get(i) == null) {
099 unsatisfiableDependencyTypes.add(injectionTypes[i]);
100 unsatisfiableDependencyMembers.add(injectionMembers.get(i));
101 }
102 }
103 if (unsatisfiableDependencyTypes.size() > 0) {
104 unsatisfiedDependencies(container, unsatisfiableDependencyTypes, unsatisfiableDependencyMembers);
105 } else if (nonMatchingParameterPositions.size() > 0) {
106 throw new PicoCompositionException("Following parameters do not match any of the injectionMembers for " + getComponentImplementation() + ": " + nonMatchingParameterPositions.toString());
107 }
108 return matchingParameterList.toArray(new Parameter[matchingParameterList.size()]);
109 }
110
111 private Set<Integer> matchParameters(PicoContainer container, List<Object> matchingParameterList, Parameter[] currentParameters) {
112 Set<Integer> unmatchedParameters = new HashSet<Integer>();
113 for (int i = 0; i < currentParameters.length; i++) {
114 if (!matchParameter(container, matchingParameterList, currentParameters[i])) {
115 unmatchedParameters.add(i);
116 }
117 }
118 return unmatchedParameters;
119 }
120
121 private boolean matchParameter(PicoContainer container, List<Object> matchingParameterList, Parameter parameter) {
122 for (int j = 0; j < injectionTypes.length; j++) {
123 Object o = matchingParameterList.get(j);
124 try {
125 if (o == null && parameter.resolve(container, this, null, injectionTypes[j],
126 makeParameterNameImpl(injectionMembers.get(j)),
127 useNames(), bindings[j]).isResolved()) {
128 matchingParameterList.set(j, parameter);
129 return true;
130 }
131 } catch (AmbiguousComponentResolutionException e) {
132 e.setMember(injectionMembers.get(j));
133 throw e;
134 }
135 }
136 return false;
137 }
138
139 protected NameBinding makeParameterNameImpl(AccessibleObject member) {
140 if (paranamer == null) {
141 paranamer = new CachingParanamer(new AnnotationParanamer(new AdaptiveParanamer()));
142 }
143 return new ParameterNameBinding(paranamer, member, 0);
144 }
145
146 protected abstract void unsatisfiedDependencies(PicoContainer container, Set<Type> unsatisfiableDependencyTypes, List<AccessibleObject> unsatisfiableDependencyMembers);
147
148 public T getComponentInstance(final PicoContainer container, Type into) throws PicoCompositionException {
149 final Constructor constructor = getConstructor();
150 if (instantiationGuard == null) {
151 instantiationGuard = new ThreadLocalCyclicDependencyGuard() {
152 public Object run(Object instance) {
153 final Parameter[] matchingParameters = getMatchingParameterListForSetters(guardedContainer);
154 Object componentInstance = makeInstance(container, constructor, currentMonitor());
155 return decorateComponentInstance(matchingParameters, currentMonitor(), componentInstance, container, guardedContainer);
156 }
157 };
158 }
159 instantiationGuard.setGuardedContainer(container);
160 return (T) instantiationGuard.observe(getComponentImplementation(), null);
161 }
162
163 private Object decorateComponentInstance(Parameter[] matchingParameters, ComponentMonitor componentMonitor, Object componentInstance, PicoContainer container, PicoContainer guardedContainer) {
164 AccessibleObject member = null;
165 Object injected[] = new Object[injectionMembers.size()];
166 Object lastReturn = null;
167 try {
168 for (int i = 0; i < injectionMembers.size(); i++) {
169 member = injectionMembers.get(i);
170 if (matchingParameters[i] != null) {
171 Object toInject = matchingParameters[i].resolve(guardedContainer, this, null, injectionTypes[i],
172 makeParameterNameImpl(injectionMembers.get(i)),
173 useNames(), bindings[i]).resolveInstance();
174 Object rv = componentMonitor.invoking(container, this, (Member) member, componentInstance, new Object[] {toInject});
175 if (rv == ComponentMonitor.KEEP) {
176 long str = System.currentTimeMillis();
177 lastReturn = injectIntoMember(member, componentInstance, toInject);
178 componentMonitor.invoked(container, this, (Member) member, componentInstance, System.currentTimeMillis() - str, new Object[] {toInject}, lastReturn);
179 } else {
180 lastReturn = rv;
181 }
182 injected[i] = toInject;
183 }
184 }
185 return memberInvocationReturn(lastReturn, member, componentInstance);
186 } catch (InvocationTargetException e) {
187 return caughtInvocationTargetException(componentMonitor, (Member) member, componentInstance, e);
188 } catch (IllegalAccessException e) {
189 return caughtIllegalAccessException(componentMonitor, (Member) member, componentInstance, e);
190 }
191 }
192
193 protected abstract Object memberInvocationReturn(Object lastReturn, AccessibleObject member, Object instance);
194
195 private Object makeInstance(PicoContainer container, Constructor constructor, ComponentMonitor componentMonitor) {
196 long startTime = System.currentTimeMillis();
197 Constructor constructorToUse = componentMonitor.instantiating(container,
198 IterativeInjector.this, constructor);
199 Object componentInstance;
200 try {
201 componentInstance = newInstance(constructorToUse, null);
202 } catch (InvocationTargetException e) {
203 componentMonitor.instantiationFailed(container, IterativeInjector.this, constructorToUse, e);
204 if (e.getTargetException() instanceof RuntimeException) {
205 throw (RuntimeException)e.getTargetException();
206 } else if (e.getTargetException() instanceof Error) {
207 throw (Error)e.getTargetException();
208 }
209 throw new PicoCompositionException(e.getTargetException());
210 } catch (InstantiationException e) {
211 return caughtInstantiationException(componentMonitor, constructor, e, container);
212 } catch (IllegalAccessException e) {
213 return caughtIllegalAccessException(componentMonitor, constructor, e, container);
214 }
215 componentMonitor.instantiated(container,
216 IterativeInjector.this,
217 constructorToUse,
218 componentInstance,
219 NONE,
220 System.currentTimeMillis() - startTime);
221 return componentInstance;
222 }
223
224 @Override
225 public Object decorateComponentInstance(final PicoContainer container, Type into, final T instance) {
226 if (instantiationGuard == null) {
227 instantiationGuard = new ThreadLocalCyclicDependencyGuard() {
228 public Object run(Object inst) {
229 final Parameter[] matchingParameters = getMatchingParameterListForSetters(guardedContainer);
230 return decorateComponentInstance(matchingParameters, currentMonitor(), inst, container, guardedContainer);
231 }
232 };
233 }
234 instantiationGuard.setGuardedContainer(container);
235 return instantiationGuard.observe(getComponentImplementation(), instance);
236 }
237
238 protected abstract Object injectIntoMember(AccessibleObject member, Object componentInstance, Object toInject) throws IllegalAccessException, InvocationTargetException;
239
240 @Override
241 public void verify(final PicoContainer container) throws PicoCompositionException {
242 if (verifyingGuard == null) {
243 verifyingGuard = new ThreadLocalCyclicDependencyGuard() {
244 public Object run(Object instance) {
245 final Parameter[] currentParameters = getMatchingParameterListForSetters(guardedContainer);
246 for (int i = 0; i < currentParameters.length; i++) {
247 currentParameters[i].verify(container, IterativeInjector.this, injectionTypes[i],
248 makeParameterNameImpl(injectionMembers.get(i)), useNames(), bindings[i]);
249 }
250 return null;
251 }
252 };
253 }
254 verifyingGuard.setGuardedContainer(container);
255 verifyingGuard.observe(getComponentImplementation(), null);
256 }
257
258 protected void initializeInjectionMembersAndTypeLists() {
259 injectionMembers = new ArrayList<AccessibleObject>();
260 Set<String> injectionMemberNames = new HashSet<String>();
261 List<Annotation> bingingIds = new ArrayList<Annotation>();
262 final List<String> nameList = new ArrayList<String>();
263 final List<Type> typeList = new ArrayList<Type>();
264 final Method[] methods = getMethods();
265 for (final Method method : methods) {
266 final Type[] parameterTypes = method.getGenericParameterTypes();
267 fixGenericParameterTypes(method, parameterTypes);
268
269 String methodSignature = crudeMethodSignature(method);
270
271 // We're only interested if there is only one parameter ...
272 if (parameterTypes.length == 1) {
273 boolean isInjector = isInjectorMethod(method);
274 // ... and the method name is bean-style.
275 // We're also not interested in dupes from parent classes (not all JDK impls)
276 if (isInjector && !injectionMemberNames.contains(methodSignature)) {
277 injectionMembers.add(method);
278 injectionMemberNames.add(methodSignature);
279 nameList.add(getName(method));
280 typeList.add(box(parameterTypes[0]));
281 bingingIds.add(getBindings(method, 0));
282 }
283 }
284 }
285 injectionTypes = typeList.toArray(new Type[0]);
286 bindings = bingingIds.toArray(new Annotation[0]);
287 initialized = true;
288 }
289
290 public static String crudeMethodSignature(Method method) {
291 StringBuilder sb = new StringBuilder();
292 sb.append(method.getReturnType().getName());
293 sb.append(method.getName());
294 for (Class<?> pType : method.getParameterTypes()) {
295 sb.append(pType.getName());
296 }
297 return sb.toString();
298 }
299
300 protected String getName(Method method) {
301 return null;
302 }
303
304 private void fixGenericParameterTypes(Method method, Type[] parameterTypes) {
305 for (int i = 0; i < parameterTypes.length; i++) {
306 Type parameterType = parameterTypes[i];
307 if (parameterType instanceof TypeVariable) {
308 parameterTypes[i] = method.getParameterTypes()[i];
309 }
310 }
311 }
312
313
314 private Annotation getBindings(Method method, int i) {
315 Annotation[][] parameterAnnotations = method.getParameterAnnotations();
316 if (parameterAnnotations.length >= i +1 ) {
317 Annotation[] o = parameterAnnotations[i];
318 for (Annotation annotation : o) {
319 if (annotation.annotationType().getAnnotation(Bind.class) != null) {
320 return annotation;
321 }
322 }
323 return null;
324
325 }
326 //TODO - what's this ?
327 if (parameterAnnotations != null) {
328 //return ((Bind) method.getAnnotation(Bind.class)).id();
329 }
330 return null;
331
332 }
333
334 protected boolean isInjectorMethod(Method method) {
335 return false;
336 }
337
338 private Method[] getMethods() {
339 return (Method[]) AccessController.doPrivileged(new PrivilegedAction() {
340 public Object run() {
341 return getComponentImplementation().getMethods();
342 }
343 });
344 }
345
346 }