    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     * Original code by                                                          *
    009     *****************************************************************************/
    010    package org.picocontainer.injectors;
    011
    012    import java.lang.reflect.Constructor;
    013    import java.lang.reflect.InvocationTargetException;
    014    import java.lang.reflect.Member;
    015    import java.lang.reflect.Modifier;
    016    import java.lang.reflect.Type;
    017    import java.util.Arrays;
    018    import java.util.LinkedList;
    019    import java.util.List;
    020    import java.util.Set;
    021
    022    import org.picocontainer.ComponentAdapter;
    023    import org.picocontainer.ComponentMonitor;
    024    import org.picocontainer.Injector;
    025    import org.picocontainer.LifecycleStrategy;
    026    import org.picocontainer.ObjectReference;
    027    import org.picocontainer.Parameter;
    028    import org.picocontainer.PicoCompositionException;
    029    import org.picocontainer.PicoContainer;
    030    import org.picocontainer.PicoVisitor;
    031    import org.picocontainer.adapters.AbstractAdapter;
    032    import org.picocontainer.parameters.ComponentParameter;
    033
    034    /**
    035     * This ComponentAdapter will instantiate a new object for each call to
    036     * {@link org.picocontainer.ComponentAdapter#getComponentInstance(PicoContainer, Class)}.
    037     * That means that when used with a PicoContainer, getComponent will
    038     * return a new object each time.
    039     *
    040     * @author Aslak Helles&oslash;y
    041     * @author Paul Hammant
    042     * @author J&ouml;rg Schaible
    043     * @author Mauro Talevi
    044     */
    045    @SuppressWarnings("serial")
    046    public abstract class AbstractInjector<T> extends AbstractAdapter<T> implements LifecycleStrategy, Injector<T> {
    047        /** The cycle guard for the verification. */
    048        protected transient ThreadLocalCyclicDependencyGuard verifyingGuard;
    049        /** The parameters to use for initialization. */
    050        protected transient Parameter[] parameters;
    051
    052        /** The strategy used to control the lifecycle */
    053        protected LifecycleStrategy lifecycleStrategy;
    054        private final boolean useNames;
    055
    056        /**
    057         * Constructs a new ComponentAdapter for the given key and implementation.
    058         * @param componentKey the search key for this implementation
    059         * @param componentImplementation the concrete implementation
    060         * @param parameters the parameters to use for the initialization
    061         * @param monitor the component monitor used by this ComponentAdapter
    062         * @param lifecycleStrategy the lifecycle strategy used by this ComponentAdapter
    063         * @throws org.picocontainer.injectors.AbstractInjector.NotConcreteRegistrationException if the implementation is not a concrete class
    064         * @throws NullPointerException if one of the parameters is <code>null</code>
    065         */
    066        protected AbstractInjector(final Object componentKey, final Class<?> componentImplementation, final Parameter[] parameters,
    067                                                final ComponentMonitor monitor, final LifecycleStrategy lifecycleStrategy, final boolean useNames) {
    068            super(componentKey, componentImplementation, monitor);
    069            this.useNames = useNames;
    070            checkConcrete();
    071            if (parameters != null) {
    072                for (int i = 0; i < parameters.length; i++) {
    073                    if(parameters[i] == null) {
    074                        throw new NullPointerException("Parameter " + i + " is null");
    075                    }
    076                }
    077            }
    078            this.parameters = parameters;
    079            this.lifecycleStrategy = lifecycleStrategy;
    080        }
    081
    082        public boolean useNames() {
    083            return useNames;
    084        }
    085
    086        private void checkConcrete() throws NotConcreteRegistrationException {
    087            // Assert that the component class is concrete.
    088            boolean isAbstract = (getComponentImplementation().getModifiers() & Modifier.ABSTRACT) == Modifier.ABSTRACT;
    089            if (getComponentImplementation().isInterface() || isAbstract) {
    090                throw new NotConcreteRegistrationException(getComponentImplementation());
    091            }
    092        }
    093
    094        /**
    095         * Create default parameters for the given types.
    096         *
    097         * @param parameters the parameter types
    098         * @return the array with the default parameters.
    099         */
    100        protected Parameter[] createDefaultParameters(final Type[] parameters) {
    101            Parameter[] componentParameters = new Parameter[parameters.length];
    102            for (int i = 0; i < parameters.length; i++) {
    103                componentParameters[i] = ComponentParameter.DEFAULT;
    104            }
    105            return componentParameters;
    106        }
    107
    108        public void verify(PicoContainer container) throws PicoCompositionException {
    109        }
    110
    111        public T getComponentInstance(PicoContainer container) throws PicoCompositionException {
    112            return getComponentInstance(container, NOTHING.class);
    113        }
    114
    115        public abstract T getComponentInstance(PicoContainer container, Type into) throws PicoCompositionException;
    116
    117        public Object decorateComponentInstance(PicoContainer container, Type into, T instance) {
    118            return null;
    119        }
    120
    121        @Override
    122            public void accept(final PicoVisitor visitor) {
    123            super.accept(visitor);
    124            if (parameters != null) {
    125                for (Parameter parameter : parameters) {
    126                    parameter.accept(visitor);
    127                }
    128            }
    129        }
    130        public void start(final Object component) {
    131            lifecycleStrategy.start(component);
    132        }
    133
    134        public void stop(final Object component) {
    135            lifecycleStrategy.stop(component);
    136        }
    137
    138        public void dispose(final Object component) {
    139            lifecycleStrategy.dispose(component);
    140        }
    141
    142        public boolean hasLifecycle(final Class<?> type) {
    143            return lifecycleStrategy.hasLifecycle(type);
    144        }
    145
    146        public String getDescriptor() {
    147            return "Asbtract Injector";
    148        }
    149
    150        /**
    151         * Instantiate an object with given parameters and respect the accessible flag.
    152         *
    153         * @param constructor the constructor to use
    154         * @param parameters the parameters for the constructor
    155         * @return the new object.
    156         * @throws InstantiationException
    157         * @throws IllegalAccessException
    158         * @throws InvocationTargetException
    159         */
    160        protected T newInstance(final Constructor<T> constructor, final Object[] parameters)
    161                throws InstantiationException, IllegalAccessException, InvocationTargetException {
    162            return constructor.newInstance(parameters);
    163        }
    164        /**
    165         * inform monitor about component instantiation failure
    166         * @param componentMonitor
    167         * @param constructor
    168         * @param e
    169         * @param container
    170         * @return
    171         */
    172        protected T caughtInstantiationException(final ComponentMonitor componentMonitor,
    173                                                    final Constructor<T> constructor,
    174                                                    final InstantiationException e, final PicoContainer container) {
    175            // can't get here because checkConcrete() will catch it earlier, but see PICO-191
    176            componentMonitor.instantiationFailed(container, this, constructor, e);
    177            throw new PicoCompositionException("Should never get here");
    178        }
    179
    180        /**
    181         * inform monitor about access exception.
    182         * @param componentMonitor
    183         * @param constructor
    184         * @param e
    185         * @param container
    186         * @return
    187         */
    188        protected T caughtIllegalAccessException(final ComponentMonitor componentMonitor,
    189                                                    final Constructor<T> constructor,
    190                                                    final IllegalAccessException e, final PicoContainer container) {
    191            // can't get here because either filtered or access mode set
    192            componentMonitor.instantiationFailed(container, this, constructor, e);
    193            throw new PicoCompositionException(e);
    194        }
    195
    196        /**
    197         * inform monitor about exception while instantiating component
    198         * @param componentMonitor
    199         * @param member
    200         * @param componentInstance
    201         * @param e
    202         * @return
    203         */
    204        protected T caughtInvocationTargetException(final ComponentMonitor componentMonitor,
    205                                                       final Member member,
    206                                                       final Object componentInstance, final InvocationTargetException e) {
    207            componentMonitor.invocationFailed(member, componentInstance, e);
    208            if (e.getTargetException() instanceof RuntimeException) {
    209                throw (RuntimeException) e.getTargetException();
    210            } else if (e.getTargetException() instanceof Error) {
    211                throw (Error) e.getTargetException();
    212            }
    213            throw new PicoCompositionException(e.getTargetException());
    214        }
    215
    216        protected Object caughtIllegalAccessException(final ComponentMonitor componentMonitor,
    217                                                    final Member member,
    218                                                    final Object componentInstance, final IllegalAccessException e) {
    219            componentMonitor.invocationFailed(member, componentInstance, e);
    220            throw new PicoCompositionException(e);
    221        }
    222
    223        protected Type box(Type parameterType) {
    224            if (parameterType instanceof Class && ((Class) parameterType).isPrimitive()) {
    225                String parameterTypeName = ((Class) parameterType).getName();
    226                if (parameterTypeName == "int") {
    227                    return Integer.class;
    228                } else if (parameterTypeName == "boolean") {
    229                    return Boolean.class;
    230                } else if (parameterTypeName == "long") {
    231                    return Long.class;
    232                } else if (parameterTypeName == "float") {
    233                    return Float.class;
    234                } else if (parameterTypeName == "double") {
    235                    return Double.class;
    236                } else if (parameterTypeName == "char") {
    237                    return Character.class;
    238                } else if (parameterTypeName == "byte") {
    239                    return Byte.class;
    240                } else if (parameterTypeName == "short") {
    241                    return Short.class;
    242                }
    243            }
    244            return parameterType;
    245        }
    246
    247        /**
    248         * Abstract utility class to detect recursion cycles.
    249         * Derive from this class and implement {@link ThreadLocalCyclicDependencyGuard#run}.
    250         * The method will be called by  {@link ThreadLocalCyclicDependencyGuard#observe}. Select
    251         * an appropriate guard for your scope. Any {@link ObjectReference} can be
    252         * used as long as it is initialized with  <code>Boolean.FALSE</code>.
    253         *
    254         * @author J&ouml;rg Schaible
    255         */
    256        static abstract class ThreadLocalCyclicDependencyGuard<T> extends ThreadLocal<Boolean> {
    257
    258            protected PicoContainer guardedContainer;
    259
    260            @Override
    261                    protected Boolean initialValue() {
    262                return Boolean.FALSE;
    263            }
    264
    265            /**
    266             * Derive from this class and implement this function with the functionality
    267             * to observe for a dependency cycle.
    268             *
    269             * @return a value, if the functionality result in an expression,
    270             *      otherwise just return <code>null</code>
    271             */
    272            public abstract T run();
    273
    274            /**
    275             * Call the observing function. The provided guard will hold the {@link Boolean} value.
    276             * If the guard is already <code>Boolean.TRUE</code> a {@link CyclicDependencyException}
    277             * will be  thrown.
    278             *
    279             * @param stackFrame the current stack frame
    280             * @return the result of the <code>run</code> method
    281             */
    282            public final T observe(final Class<?> stackFrame) {
    283                if (Boolean.TRUE.equals(get())) {
    284                    throw new CyclicDependencyException(stackFrame);
    285                }
    286                T result = null;
    287                try {
    288                    set(Boolean.TRUE);
    289                    result = run();
    290                } catch (final CyclicDependencyException e) {
    291                    e.push(stackFrame);
    292                    throw e;
    293                } finally {
    294                    set(Boolean.FALSE);
    295                }
    296                return result;
    297            }
    298
    299            public void setGuardedContainer(final PicoContainer container) {
    300                this.guardedContainer = container;
    301            }
    302
    303        }
    304
    305        @SuppressWarnings("serial")
    306            public static class CyclicDependencyException extends PicoCompositionException {
    307            private final List<Class> stack;
    308
    309            /**
    310             * @param element
    311             */
    312            public CyclicDependencyException(final Class<?> element) {
    313                super((Throwable)null);
    314                this.stack = new LinkedList<Class>();
    315                push(element);
    316            }
    317
    318            /**
    319             * @param element
    320             */
    321            public void push(final Class<?> element) {
    322                stack.add(element);
    323            }
    324
    325            public Class[] getDependencies() {
    326                return stack.toArray(new Class[stack.size()]);
    327            }
    328
    329            @Override
    330                    public String getMessage() {
    331                return "Cyclic dependency: " + stack.toString();
    332            }
    333        }
    334
    335        /**
    336         * Exception that is thrown as part of the introspection. Raised if a PicoContainer cannot resolve a
    337         * type dependency because the registered {@link org.picocontainer.ComponentAdapter}s are not
    338         * distinct.
    339         *
    340         * @author Paul Hammant
    341         * @author Aslak Helles&oslash;y
    342         * @author Jon Tirs&eacute;n
    343         */
    344        @SuppressWarnings("serial")
    345        public static final class AmbiguousComponentResolutionException extends PicoCompositionException {
    346
    347
    348                    private Class<?> component;
    349            private final Class<?> ambiguousDependency;
    350            private final Object[] ambiguousComponentKeys;
    351
    352
    353            /**
    354             * Construct a new exception with the ambigous class type and the ambiguous component keys.
    355             *
    356             * @param ambiguousDependency the unresolved dependency type
    357             * @param componentKeys the ambiguous keys.
    358             */
    359            public AmbiguousComponentResolutionException(final Class<?> ambiguousDependency, final Object[] componentKeys) {
    360                super("");
    361                this.ambiguousDependency = ambiguousDependency;
    362                this.ambiguousComponentKeys = new Class[componentKeys.length];
    363                System.arraycopy(componentKeys, 0, ambiguousComponentKeys, 0, componentKeys.length);
    364            }
    365
    366            /**
    367             * @return Returns a string containing the unresolved class type and the ambiguous keys.
    368             */
    369            @Override
    370                    public String getMessage() {
    371                StringBuffer msg = new StringBuffer();
    372                msg.append(component != null ? component : "<no-component>");
    373                msg.append(" needs a '");
    374                msg.append(ambiguousDependency.getName());
    375                msg.append("' injected, but there are too many choices to inject. These:");
    376                msg.append(Arrays.asList(getAmbiguousComponentKeys()));
    377                msg.append(", refer http://picocontainer.org/ambiguous-injectable-help.html");
    378                return msg.toString();
    379            }
    380
    381            /**
    382             * @return Returns the ambiguous component keys as array.
    383             */
    384            public Object[] getAmbiguousComponentKeys() {
    385                return ambiguousComponentKeys;
    386            }
    387
    388            public void setComponent(final Class<?> component) {
    389                this.component = component;
    390            }
    391        }
    392
    393        /**
    394         * Exception thrown when some of the component's dependencies are not satisfiable.
    395         *
    396         * @author Aslak Helles&oslash;y
    397         * @author Mauro Talevi
    398         */
    399        public static class UnsatisfiableDependenciesException extends PicoCompositionException {
    400
    401
    402                    private final ComponentAdapter<?> instantiatingComponentAdapter;
    403            private final Set unsatisfiableDependencies;
    404            private final Type unsatisfiedDependencyType;
    405
    406            /**
    407             * The original container requesting the instantiation of the component.
    408             */
    409            private final PicoContainer leafContainer;
    410
    411            public UnsatisfiableDependenciesException(final ComponentAdapter<?> instantiatingComponentAdapter,
    412                                                      final Type unsatisfiedDependencyType, final Set unsatisfiableDependencies,
    413                                                      final PicoContainer leafContainer) {
    414                super(instantiatingComponentAdapter.getComponentImplementation().getName() + " has unsatisfied dependency: " + unsatisfiedDependencyType
    415                        +" among unsatisfiable dependencies: "+unsatisfiableDependencies + " where " + leafContainer
    416                        + " was the leaf container being asked for dependencies.");
    417                this.instantiatingComponentAdapter = instantiatingComponentAdapter;
    418                this.unsatisfiableDependencies = unsatisfiableDependencies;
    419                this.unsatisfiedDependencyType = unsatisfiedDependencyType;
    420                this.leafContainer = leafContainer;
    421            }
    422
    423            public ComponentAdapter<?> getUnsatisfiableComponentAdapter() {
    424                return instantiatingComponentAdapter;
    425            }
    426
    427            public Set getUnsatisfiableDependencies() {
    428                return unsatisfiableDependencies;
    429            }
    430
    431            public Type getUnsatisfiedDependencyType() {
    432                return unsatisfiedDependencyType;
    433            }
    434
    435            public PicoContainer getLeafContainer() {
    436                return leafContainer;
    437            }
    438
    439        }
    440
    441        /**
    442         * @author Aslak Hellesoy
    443         */
    444        public static class NotConcreteRegistrationException extends PicoCompositionException {
    445
    446                    private final Class<?> componentImplementation;
    447
    448            public NotConcreteRegistrationException(final Class<?> componentImplementation) {
    449                super("Bad Access: '" + componentImplementation.getName() + "' is not instantiable");
    450                this.componentImplementation = componentImplementation;
    451            }
    452
    453            public Class<?> getComponentImplementation() {
    454                return componentImplementation;
    455            }
    456        }
    457    }