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    }