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    }