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