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.defaults;
011
012 import org.picocontainer.ComponentMonitor;
013 import org.picocontainer.Parameter;
014 import org.picocontainer.PicoContainer;
015 import org.picocontainer.PicoInitializationException;
016 import org.picocontainer.PicoIntrospectionException;
017
018 import java.lang.reflect.Constructor;
019 import java.lang.reflect.InvocationTargetException;
020 import java.lang.reflect.Method;
021 import java.security.AccessController;
022 import java.security.PrivilegedAction;
023 import java.util.ArrayList;
024 import java.util.Collections;
025 import java.util.HashSet;
026 import java.util.List;
027 import java.util.Set;
028
029 /**
030 * Instantiates components using empty constructors and
031 * <a href="http://docs.codehaus.org/display/PICO/Setter+Injection">Setter Injection</a>.
032 * For easy setting of primitive properties, also see {@link BeanPropertyComponentAdapter}.
033 * <p/>
034 * <em>
035 * Note that this class doesn't cache instances. If you want caching,
036 * use a {@link CachingComponentAdapter} around this one.
037 * </em>
038 * </p>
039 *
040 * @author Aslak Hellesøy
041 * @author Jörg Schaible
042 * @author Mauro Talevi
043 * @version $Revision: 2971 $
044 */
045 public class SetterInjectionComponentAdapter extends InstantiatingComponentAdapter {
046 private transient Guard instantiationGuard;
047 private transient List setters;
048 private transient List setterNames;
049 private transient Class[] setterTypes;
050
051 /**
052 * Constructs a SetterInjectionComponentAdapter
053 *
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 allowNonPublicClasses flag to allow instantiation of non-public classes.
058 * @param monitor the component monitor used by this adapter
059 * @param lifecycleStrategy the component lifecycle strategy used by this adapter
060 * @throws AssignabilityRegistrationException
061 * if the key is a type and the implementation cannot be assigned to.
062 * @throws NotConcreteRegistrationException
063 * if the implementation is not a concrete class.
064 * @throws NullPointerException if one of the parameters is <code>null</code>
065 */
066 public SetterInjectionComponentAdapter(final Object componentKey, final Class componentImplementation, Parameter[] parameters, boolean allowNonPublicClasses, ComponentMonitor monitor, LifecycleStrategy lifecycleStrategy) throws AssignabilityRegistrationException, NotConcreteRegistrationException {
067 super(componentKey, componentImplementation, parameters, allowNonPublicClasses, monitor, lifecycleStrategy);
068 }
069
070
071 /**
072 * Constructs a SetterInjectionComponentAdapter
073 *
074 * @param componentKey the search key for this implementation
075 * @param componentImplementation the concrete implementation
076 * @param parameters the parameters to use for the initialization
077 * @param allowNonPublicClasses flag to allow instantiation of non-public classes.
078 * @param monitor the component monitor used by this adapter
079 * @throws AssignabilityRegistrationException
080 * if the key is a type and the implementation cannot be assigned to.
081 * @throws NotConcreteRegistrationException
082 * if the implementation is not a concrete class.
083 * @throws NullPointerException if one of the parameters is <code>null</code>
084 */
085 public SetterInjectionComponentAdapter(final Object componentKey, final Class componentImplementation, Parameter[] parameters, boolean allowNonPublicClasses, ComponentMonitor monitor) throws AssignabilityRegistrationException, NotConcreteRegistrationException {
086 super(componentKey, componentImplementation, parameters, allowNonPublicClasses, monitor);
087 }
088
089 /**
090 * Constructs a SetterInjectionComponentAdapter with a {@link DelegatingComponentMonitor} as default.
091 *
092 * @param componentKey the search key for this implementation
093 * @param componentImplementation the concrete implementation
094 * @param parameters the parameters to use for the initialization
095 * @param allowNonPublicClasses flag to allow instantiation of non-public classes.
096 * @throws AssignabilityRegistrationException
097 * if the key is a type and the implementation cannot be assigned to.
098 * @throws NotConcreteRegistrationException
099 * if the implementation is not a concrete class.
100 * @throws NullPointerException if one of the parameters is <code>null</code>
101 */
102 public SetterInjectionComponentAdapter(final Object componentKey, final Class componentImplementation, Parameter[] parameters, boolean allowNonPublicClasses) throws AssignabilityRegistrationException, NotConcreteRegistrationException {
103 super(componentKey, componentImplementation, parameters, allowNonPublicClasses);
104 }
105
106 /**
107 * Constructs a SetterInjectionComponentAdapter with key, implementation and parameters.
108 *
109 * @param componentKey the search key for this implementation
110 * @param componentImplementation the concrete implementation
111 * @param parameters the parameters to use for the initialization
112 * @throws AssignabilityRegistrationException
113 * if the key is a type and the implementation cannot be assigned to.
114 * @throws NotConcreteRegistrationException
115 * if the implementation is not a concrete class.
116 * @throws NullPointerException if one of the parameters is <code>null</code>
117 */
118 public SetterInjectionComponentAdapter(final Object componentKey, final Class componentImplementation, Parameter[] parameters) throws AssignabilityRegistrationException, NotConcreteRegistrationException {
119 this(componentKey, componentImplementation, parameters, false);
120 }
121
122 protected Constructor getGreediestSatisfiableConstructor(PicoContainer container) throws PicoIntrospectionException, UnsatisfiableDependenciesException, AmbiguousComponentResolutionException, AssignabilityRegistrationException, NotConcreteRegistrationException {
123 final Constructor constructor = getConstructor();
124 getMatchingParameterListForSetters(container);
125 return constructor;
126 }
127
128 private Constructor getConstructor() throws PicoInvocationTargetInitializationException {
129 Object retVal = AccessController.doPrivileged(new PrivilegedAction() {
130 public Object run() {
131 try {
132 return getComponentImplementation().getConstructor((Class[])null);
133 } catch (NoSuchMethodException e) {
134 return new PicoInvocationTargetInitializationException(e);
135 } catch (SecurityException e) {
136 return new PicoInvocationTargetInitializationException(e);
137 }
138 }
139 });
140 if (retVal instanceof Constructor) {
141 return (Constructor) retVal;
142 } else {
143 throw (PicoInitializationException) retVal;
144 }
145 }
146
147 private Parameter[] getMatchingParameterListForSetters(PicoContainer container) throws PicoInitializationException, UnsatisfiableDependenciesException {
148 if (setters == null) {
149 initializeSetterAndTypeLists();
150 }
151
152 final List matchingParameterList = new ArrayList(Collections.nCopies(setters.size(), null));
153 final Set nonMatchingParameterPositions = new HashSet();
154 final Parameter[] currentParameters = parameters != null ? parameters : createDefaultParameters(setterTypes);
155 for (int i = 0; i < currentParameters.length; i++) {
156 final Parameter parameter = currentParameters[i];
157 boolean failedDependency = true;
158 for (int j = 0; j < setterTypes.length; j++) {
159 if (matchingParameterList.get(j) == null && parameter.isResolvable(container, this, setterTypes[j])) {
160 matchingParameterList.set(j, parameter);
161 failedDependency = false;
162 break;
163 }
164 }
165 if (failedDependency) {
166 nonMatchingParameterPositions.add(new Integer(i));
167 }
168 }
169
170 final Set unsatisfiableDependencyTypes = new HashSet();
171 for (int i = 0; i < matchingParameterList.size(); i++) {
172 if (matchingParameterList.get(i) == null) {
173 unsatisfiableDependencyTypes.add(setterTypes[i]);
174 }
175 }
176 if (unsatisfiableDependencyTypes.size() > 0) {
177 throw new UnsatisfiableDependenciesException(this, unsatisfiableDependencyTypes, container);
178 } else if (nonMatchingParameterPositions.size() > 0) {
179 throw new PicoInitializationException("Following parameters do not match any of the setters for " + getComponentImplementation() + ": " + nonMatchingParameterPositions.toString());
180 }
181 return (Parameter[]) matchingParameterList.toArray(new Parameter[matchingParameterList.size()]);
182 }
183
184 public Object getComponentInstance(final PicoContainer container) throws PicoInitializationException, PicoIntrospectionException, AssignabilityRegistrationException, NotConcreteRegistrationException {
185 final Constructor constructor = getConstructor();
186 if (instantiationGuard == null) {
187 instantiationGuard = new Guard() {
188 public Object run() {
189 final Parameter[] matchingParameters = getMatchingParameterListForSetters(guardedContainer);
190 ComponentMonitor componentMonitor = currentMonitor();
191 Object componentInstance;
192 long startTime = System.currentTimeMillis();
193 try {
194 componentMonitor.instantiating(constructor);
195 componentInstance = newInstance(constructor, null);
196 } catch (InvocationTargetException e) {
197 componentMonitor.instantiationFailed(constructor, e);
198 if (e.getTargetException() instanceof RuntimeException) {
199 throw (RuntimeException) e.getTargetException();
200 } else if (e.getTargetException() instanceof Error) {
201 throw (Error) e.getTargetException();
202 }
203 throw new PicoInvocationTargetInitializationException(e.getTargetException());
204 } catch (InstantiationException e) {
205 // can't get here because checkConcrete() will catch it earlier, but see PICO-191
206 ///CLOVER:OFF
207 componentMonitor.instantiationFailed(constructor, e);
208 throw new PicoInitializationException("Should never get here");
209 ///CLOVER:ON
210 } catch (IllegalAccessException e) {
211 // can't get here because either filtered or access mode set
212 ///CLOVER:OFF
213 componentMonitor.instantiationFailed(constructor, e);
214 throw new PicoInitializationException(e);
215 ///CLOVER:ON
216 }
217 Method setter = null;
218 Object injected[] = new Object[setters.size()];
219 try {
220 for (int i = 0; i < setters.size(); i++) {
221 setter = (Method) setters.get(i);
222 componentMonitor.invoking(setter, componentInstance);
223 Object toInject = matchingParameters[i].resolveInstance(guardedContainer, SetterInjectionComponentAdapter.this, setterTypes[i]);
224 setter.invoke(componentInstance, new Object[]{toInject});
225 injected[i] = toInject;
226 //componentMonitor.invoked(setter, componentInstance, System.currentTimeMillis() - startTime);
227 }
228 componentMonitor.instantiated(constructor, componentInstance, injected, System.currentTimeMillis() - startTime);
229 return componentInstance;
230 } catch (InvocationTargetException e) {
231 //componentMonitor.invocationFailed(setter, componentInstance, e);
232 if (e.getTargetException() instanceof RuntimeException) {
233 throw (RuntimeException) e.getTargetException();
234 } else if (e.getTargetException() instanceof Error) {
235 throw (Error) e.getTargetException();
236 }
237 throw new PicoInvocationTargetInitializationException(e.getTargetException());
238 } catch (IllegalAccessException e) {
239 //componentMonitor.invocationFailed(setter, componentInstance, e);
240 throw new PicoInvocationTargetInitializationException(e);
241 }
242
243 }
244 };
245 }
246 instantiationGuard.setArguments(container);
247 return instantiationGuard.observe(getComponentImplementation());
248 }
249
250 public void verify(final PicoContainer container) throws PicoIntrospectionException {
251 if (verifyingGuard == null) {
252 verifyingGuard = new Guard() {
253 public Object run() {
254 final Parameter[] currentParameters = getMatchingParameterListForSetters(guardedContainer);
255 for (int i = 0; i < currentParameters.length; i++) {
256 currentParameters[i].verify(container, SetterInjectionComponentAdapter.this, setterTypes[i]);
257 }
258 return null;
259 }
260 };
261 }
262 verifyingGuard.setArguments(container);
263 verifyingGuard.observe(getComponentImplementation());
264 }
265
266 private void initializeSetterAndTypeLists() {
267 setters = new ArrayList();
268 setterNames = new ArrayList();
269 final List typeList = new ArrayList();
270 final Method[] methods = getMethods();
271 for (int i = 0; i < methods.length; i++) {
272 final Method method = methods[i];
273 final Class[] parameterTypes = method.getParameterTypes();
274 // We're only interested if there is only one parameter and the method name is bean-style.
275 if (parameterTypes.length == 1) {
276 String methodName = method.getName();
277 boolean isBeanStyle = methodName.length() >= 4 && methodName.startsWith("set") && Character.isUpperCase(methodName.charAt(3));
278 if (isBeanStyle) {
279 String attribute = Character.toLowerCase(methodName.charAt(3)) + methodName.substring(4);
280 setters.add(method);
281 setterNames.add(attribute);
282 typeList.add(parameterTypes[0]);
283 }
284 }
285 }
286 setterTypes = (Class[]) typeList.toArray(new Class[0]);
287 }
288
289 private Method[] getMethods() {
290 return (Method[]) AccessController.doPrivileged(new PrivilegedAction() {
291 public Object run() {
292 return getComponentImplementation().getMethods();
293 }
294 });
295 }
296 }