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