    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    }