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ö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øy
267 * @author Jon Tirsé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ø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 }