    001    /*****************************************************************************
    002     * Copyright (c) PicoContainer Organization. All rights reserved.            *
    003     * ------------------------------------------------------------------------- *
    004     * The software in this package is published under the terms of the BSD      *
    005     * style license a copy of which has been included with this distribution in *
    006     * the LICENSE.txt file.                                                     *
    007     *                                                                           *
    008     * Idea by Rachel Davies, Original code by Aslak Hellesoy and Paul Hammant   *
    009     *****************************************************************************/
    010
    011    package org.picocontainer.injectors;
    012
    013    import org.picocontainer.ComponentMonitor;
    014    import org.picocontainer.LifecycleStrategy;
    015    import org.picocontainer.Parameter;
    016    import org.picocontainer.PicoCompositionException;
    017    import org.picocontainer.PicoContainer;
    018    import org.picocontainer.lifecycle.NullLifecycleStrategy;
    019    import org.picocontainer.monitors.NullComponentMonitor;
    020
    021    import java.lang.reflect.Constructor;
    022    import java.lang.reflect.InvocationTargetException;
    023    import java.lang.reflect.Modifier;
    024    import java.lang.reflect.Type;
    025    import java.lang.reflect.TypeVariable;
    026    import java.lang.annotation.Annotation;
    027    import java.security.AccessController;
    028    import java.security.PrivilegedAction;
    029    import java.util.ArrayList;
    030    import java.util.Arrays;
    031    import java.util.Collections;
    032    import java.util.Comparator;
    033    import java.util.HashSet;
    034    import java.util.List;
    035    import java.util.Set;
    036
    037    /**
    038     * Injection will happen through a constructor for the component.
    039     *
    040     * @author Paul Hammant
    041     * @author Aslak Helles&oslash;y
    042     * @author Jon Tirs&eacute;n
    043     * @author Zohar Melamed
    044     * @author J&ouml;rg Schaible
    045     * @author Mauro Talevi
    046     */
    047    @SuppressWarnings("serial")
    048    public class ConstructorInjector<T> extends SingleMemberInjector<T> {
    049
    050            private transient List<Constructor<T>> sortedMatchingConstructors;
    051        private transient ThreadLocalCyclicDependencyGuard<T> instantiationGuard;
    052        private boolean rememberChosenConstructor = true;
    053        private transient Constructor<T> chosenConstructor;
    054
    055        /**
    056         * Constructor injector that uses no monitor and no lifecycle adapter.  This is a more
    057         * convenient constructor for use when instantiating a constructor injector directly.
    058         * @param componentKey the search key for this implementation
    059         * @param componentImplementation the concrete implementation
    060         * @param parameters the parameters used for initialization
    061         */
    062        public ConstructorInjector(final Object componentKey, final Class<?> componentImplementation, Parameter... parameters) {
    063            this(componentKey, componentImplementation, parameters, new NullComponentMonitor(), new NullLifecycleStrategy(), false);
    064        }
    065
    066        /**
    067         * Creates a ConstructorInjector
    068         *
    069         * @param componentKey            the search key for this implementation
    070         * @param componentImplementation the concrete implementation
    071         * @param parameters              the parameters to use for the initialization
    072         * @param monitor                 the component monitor used by this addAdapter
    073         * @param lifecycleStrategy       the component lifecycle strategy used by this addAdapter
    074         * @param useNames                use argument names when looking up dependencies
    075         * @throws org.picocontainer.injectors.AbstractInjector.NotConcreteRegistrationException
    076         *                              if the implementation is not a concrete class.
    077         * @throws NullPointerException if one of the parameters is <code>null</code>
    078         */
    079        public ConstructorInjector(final Object componentKey, final Class componentImplementation, Parameter[] parameters, ComponentMonitor monitor,
    080                                   LifecycleStrategy lifecycleStrategy, boolean useNames) throws  NotConcreteRegistrationException {
    081            super(componentKey, componentImplementation, parameters, monitor, lifecycleStrategy, useNames);
    082        }
    083
    084        /**
    085         * Creates a ConstructorInjector
    086         *
    087         * @param componentKey            the search key for this implementation
    088         * @param componentImplementation the concrete implementation
    089         * @param parameters              the parameters to use for the initialization
    090         * @param monitor                 the component monitor used by this addAdapter
    091         * @param lifecycleStrategy       the component lifecycle strategy used by this addAdapter
    092         * @param useNames                use argument names when looking up dependencies
    093         * @param rememberChosenCtor      remember the chosen constructor (to speed up second/subsequent calls)
    094         * @throws org.picocontainer.injectors.AbstractInjector.NotConcreteRegistrationException
    095         *                              if the implementation is not a concrete class.
    096         * @throws NullPointerException if one of the parameters is <code>null</code>
    097         */
    098        public ConstructorInjector(final Object componentKey, final Class componentImplementation, Parameter[] parameters, ComponentMonitor monitor,
    099                                   LifecycleStrategy lifecycleStrategy, boolean useNames, boolean rememberChosenCtor) throws  NotConcreteRegistrationException {
    100            super(componentKey, componentImplementation, parameters, monitor, lifecycleStrategy, useNames);
    101            this.rememberChosenConstructor = rememberChosenCtor;
    102        }
    103
    104        protected Constructor<T> getGreediestSatisfiableConstructor(PicoContainer container) throws PicoCompositionException {
    105            final Set<Constructor> conflicts = new HashSet<Constructor>();
    106            final Set<List<Type>> unsatisfiableDependencyTypes = new HashSet<List<Type>>();
    107            if (sortedMatchingConstructors == null) {
    108                sortedMatchingConstructors = getSortedMatchingConstructors();
    109            }
    110            Constructor<T> greediestConstructor = null;
    111            int lastSatisfiableConstructorSize = -1;
    112            Type unsatisfiedDependencyType = null;
    113            for (final Constructor<T> sortedMatchingConstructor : sortedMatchingConstructors) {
    114                boolean failedDependency = false;
    115                Type[] parameterTypes = sortedMatchingConstructor.getGenericParameterTypes();
    116                fixParameterType(sortedMatchingConstructor, parameterTypes);
    117                Annotation[] bindings = getBindings(sortedMatchingConstructor.getParameterAnnotations());
    118                Parameter[] currentParameters = parameters != null ? parameters : createDefaultParameters(parameterTypes);
    119
    120                // remember: all constructors with less arguments than the given parameters are filtered out already
    121                for (int j = 0; j < currentParameters.length; j++) {
    122                    // check whether this constructor is satisfiable
    123                    Type boxed = box(parameterTypes[j]);
    124                    boolean un = useNames();
    125                    if (currentParameters[j].isResolvable(container, this, boxed,
    126                            new ParameterNameBinding(getParanamer(), getComponentImplementation(), sortedMatchingConstructor, j),
    127                            un, bindings[j])) {
    128                        continue;
    129                    }
    130                    unsatisfiableDependencyTypes.add(Arrays.asList(parameterTypes));
    131                    unsatisfiedDependencyType = box(parameterTypes[j]);
    132                    failedDependency = true;
    133                    break;
    134                }
    135
    136                if (greediestConstructor != null && parameterTypes.length != lastSatisfiableConstructorSize) {
    137                    if (conflicts.isEmpty()) {
    138                        // we found our match [aka. greedy and satisfied]
    139                        return greediestConstructor;
    140                    } else {
    141                        // fits although not greedy
    142                        conflicts.add(sortedMatchingConstructor);
    143                    }
    144                } else if (!failedDependency && lastSatisfiableConstructorSize == parameterTypes.length) {
    145                    // satisfied and same size as previous one?
    146                    conflicts.add(sortedMatchingConstructor);
    147                    conflicts.add(greediestConstructor);
    148                } else if (!failedDependency) {
    149                    greediestConstructor = sortedMatchingConstructor;
    150                    lastSatisfiableConstructorSize = parameterTypes.length;
    151                }
    152            }
    153            if (!conflicts.isEmpty()) {
    154                throw new PicoCompositionException(conflicts.size() + " satisfiable constructors is too many for '"+getComponentImplementation()+"'. Constructor List:" + conflicts.toString().replace(getComponentImplementation().getName(),"<init>").replace("public <i","<i"));
    155            } else if (greediestConstructor == null && !unsatisfiableDependencyTypes.isEmpty()) {
    156                throw new UnsatisfiableDependenciesException(this, unsatisfiedDependencyType, unsatisfiableDependencyTypes, container);
    157            } else if (greediestConstructor == null) {
    158                // be nice to the user, show all constructors that were filtered out
    159                final Set<Constructor> nonMatching = new HashSet<Constructor>();
    160                for (Constructor constructor : getConstructors()) {
    161                    nonMatching.add(constructor);
    162                }
    163                throw new PicoCompositionException("Either the specified parameters do not match any of the following constructors: " + nonMatching.toString() + "; OR the constructors were not accessible for '" + getComponentImplementation().getName() + "'");
    164            }
    165            return greediestConstructor;
    166        }
    167
    168        private void fixParameterType(Constructor<T> sortedMatchingConstructor, Type[] parameterTypes) {
    169            for (int i = 0; i < parameterTypes.length; i++) {
    170                Type parameterType = parameterTypes[i];
    171                if (parameterType instanceof TypeVariable) {
    172                    parameterTypes[i] = sortedMatchingConstructor.getParameterTypes()[i];
    173                }
    174            }
    175        }
    176
    177        public T getComponentInstance(final PicoContainer container, Type into) throws PicoCompositionException {
    178            if (instantiationGuard == null) {
    179                instantiationGuard = new ThreadLocalCyclicDependencyGuard<T>() {
    180                    public T run() {
    181                        Constructor<T> ctor = null;
    182                        try {
    183                            if (chosenConstructor == null) {
    184                                ctor = getGreediestSatisfiableConstructor(guardedContainer);
    185                            }
    186                            if (rememberChosenConstructor) {
    187                                if (chosenConstructor == null) {
    188                                    chosenConstructor = ctor;
    189                                } else {
    190                                    ctor = chosenConstructor;
    191                                }
    192                            }
    193                        } catch (AmbiguousComponentResolutionException e) {
    194                            e.setComponent(getComponentImplementation());
    195                            throw e;
    196                        }
    197                        ComponentMonitor componentMonitor = currentMonitor();
    198                        try {
    199                            Object[] parameters = getMemberArguments(guardedContainer, ctor);
    200                            ctor = componentMonitor.instantiating(container, ConstructorInjector.this, ctor);
    201                            if(ctor == null) {
    202                                throw new NullPointerException("Component Monitor " + componentMonitor
    203                                                + " returned a null constructor from method 'instantiating' after passing in " + ctor);
    204                            }
    205                            long startTime = System.currentTimeMillis();
    206                            T inst = instantiate(ctor, parameters);
    207                            componentMonitor.instantiated(container,
    208                                                          ConstructorInjector.this,
    209                                    ctor, inst, parameters, System.currentTimeMillis() - startTime);
    210                            return inst;
    211                        } catch (InvocationTargetException e) {
    212                            componentMonitor.instantiationFailed(container, ConstructorInjector.this, ctor, e);
    213                            if (e.getTargetException() instanceof RuntimeException) {
    214                                throw (RuntimeException) e.getTargetException();
    215                            } else if (e.getTargetException() instanceof Error) {
    216                                throw (Error) e.getTargetException();
    217                            }
    218                            throw new PicoCompositionException(e.getTargetException());
    219                        } catch (InstantiationException e) {
    220                            return caughtInstantiationException(componentMonitor, ctor, e, container);
    221                        } catch (IllegalAccessException e) {
    222                            return caughtIllegalAccessException(componentMonitor, ctor, e, container);
    223
    224                        }
    225                    }
    226                };
    227            }
    228            instantiationGuard.setGuardedContainer(container);
    229            return instantiationGuard.observe(getComponentImplementation());
    230        }
    231
    232        protected T instantiate(Constructor<T> constructor, Object[] parameters) throws InstantiationException, IllegalAccessException, InvocationTargetException {
    233            T inst = newInstance(constructor, parameters);
    234            return inst;
    235        }
    236
    237        protected Object[] getMemberArguments(PicoContainer container, final Constructor ctor) {
    238            Type[] parameterTypes = ctor.getGenericParameterTypes();
    239            fixParameterType(ctor, parameterTypes);
    240            return super.getMemberArguments(container, ctor, parameterTypes, getBindings(ctor.getParameterAnnotations()));
    241        }
    242
    243        private List<Constructor<T>> getSortedMatchingConstructors() {
    244            List<Constructor<T>> matchingConstructors = new ArrayList<Constructor<T>>();
    245            Constructor<T>[] allConstructors = getConstructors();
    246            // filter out all constructors that will definately not match
    247            for (Constructor<T> constructor : allConstructors) {
    248                if ((parameters == null || constructor.getParameterTypes().length == parameters.length) && (constructor.getModifiers() & Modifier.PUBLIC) != 0) {
    249                    matchingConstructors.add(constructor);
    250                }
    251            }
    252            // optimize list of constructors moving the longest at the beginning
    253            if (parameters == null) {
    254                Collections.sort(matchingConstructors, new Comparator<Constructor>() {
    255                    public int compare(Constructor arg0, Constructor arg1) {
    256                        return arg1.getParameterTypes().length - arg0.getParameterTypes().length;
    257                    }
    258                });
    259            }
    260            return matchingConstructors;
    261        }
    262
    263        private Constructor<T>[] getConstructors() {
    264            return AccessController.doPrivileged(new PrivilegedAction<Constructor<T>[]>() {
    265                public Constructor<T>[] run() {
    266                    return (Constructor<T>[]) getComponentImplementation().getDeclaredConstructors();
    267                }
    268            });
    269        }
    270
    271        @Override
    272        public void verify(final PicoContainer container) throws PicoCompositionException {
    273            if (verifyingGuard == null) {
    274                verifyingGuard = new ThreadLocalCyclicDependencyGuard() {
    275                    public Object run() {
    276                        final Constructor constructor = getGreediestSatisfiableConstructor(guardedContainer);
    277                        final Class[] parameterTypes = constructor.getParameterTypes();
    278                        final Parameter[] currentParameters = parameters != null ? parameters : createDefaultParameters(parameterTypes);
    279                        for (int i = 0; i < currentParameters.length; i++) {
    280                            currentParameters[i].verify(container, ConstructorInjector.this, box(parameterTypes[i]),
    281                                new ParameterNameBinding(getParanamer(), getComponentImplementation(),  constructor, i),
    282                                    useNames(), getBindings(constructor.getParameterAnnotations())[i]);
    283                        }
    284                        return null;
    285                    }
    286                };
    287            }
    288            verifyingGuard.setGuardedContainer(container);
    289            verifyingGuard.observe(getComponentImplementation());
    290        }
    291
    292        public String getDescriptor() {
    293            return "ConstructorInjector-";
    294        }
    295
    296
    297    }