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