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