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 org.picocontainer.ComponentMonitor;
013    import org.picocontainer.ObjectReference;
014    import org.picocontainer.Parameter;
015    import org.picocontainer.PicoCompositionException;
016    import org.picocontainer.PicoContainer;
017    import org.picocontainer.PicoVisitor;
018    import org.picocontainer.adapters.AbstractAdapter;
019    import org.picocontainer.parameters.ComponentParameter;
020    
021    import java.lang.reflect.AccessibleObject;
022    import java.lang.reflect.Constructor;
023    import java.lang.reflect.InvocationTargetException;
024    import java.lang.reflect.Member;
025    import java.lang.reflect.Modifier;
026    import java.lang.reflect.Type;
027    import java.util.Arrays;
028    import java.util.LinkedList;
029    import 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")
043    public 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    }