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