001package org.picocontainer.injectors;
002
003import com.thoughtworks.paranamer.AdaptiveParanamer;
004import com.thoughtworks.paranamer.AnnotationParanamer;
005import com.thoughtworks.paranamer.CachingParanamer;
006import com.thoughtworks.paranamer.Paranamer;
007import org.picocontainer.ComponentMonitor;
008import org.picocontainer.NameBinding;
009import org.picocontainer.Parameter;
010import org.picocontainer.PicoCompositionException;
011import org.picocontainer.PicoContainer;
012import org.picocontainer.annotations.Bind;
013
014import java.lang.annotation.Annotation;
015import java.lang.reflect.AccessibleObject;
016import java.lang.reflect.Constructor;
017import java.lang.reflect.InvocationTargetException;
018import java.lang.reflect.Member;
019import java.lang.reflect.Method;
020import java.lang.reflect.Type;
021import java.lang.reflect.TypeVariable;
022import java.security.AccessController;
023import java.security.PrivilegedAction;
024import java.util.ArrayList;
025import java.util.Collections;
026import java.util.HashSet;
027import java.util.List;
028import java.util.Set;
029
030/**
031 * Injection will happen iteratively after component instantiation
032 */
033public 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}