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     *****************************************************************************/
009    
010    package org.picocontainer.injectors;
011    
012    import java.lang.reflect.InvocationTargetException;
013    import java.lang.reflect.Method;
014    import java.lang.reflect.Type;
015    import java.lang.reflect.Member;
016    import java.lang.reflect.AccessibleObject;
017    import java.lang.annotation.Annotation;
018    
019    import org.picocontainer.ComponentMonitor;
020    import org.picocontainer.LifecycleStrategy;
021    import org.picocontainer.Parameter;
022    import org.picocontainer.PicoCompositionException;
023    import org.picocontainer.PicoContainer;
024    import org.picocontainer.annotations.Nullable;
025    
026    /**
027     * Injection will happen through a single method for the component.
028     *
029     * Most likely it is a method called 'inject', though that can be overridden.
030     *
031     * @author Paul Hammant
032     * @author Aslak Hellesøy
033     * @author Jon Tirsén
034     * @author Zohar Melamed
035     * @author Jörg Schaible
036     * @author Mauro Talevi
037     */
038    @SuppressWarnings("serial")
039    public class MethodInjector<T> extends SingleMemberInjector<T> {
040        private transient ThreadLocalCyclicDependencyGuard instantiationGuard;
041        private final String methodName;
042    
043        /**
044         * Creates a MethodInjector
045         *
046         * @param componentKey            the search key for this implementation
047         * @param componentImplementation the concrete implementation
048         * @param parameters              the parameters to use for the initialization
049         * @param monitor                 the component monitor used by this addAdapter
050         * @param lifecycleStrategy       the component lifecycle strategy used by this addAdapter
051         * @param methodName              the method name
052         * @param useNames                use argument names when looking up dependencies
053         * @throws AbstractInjector.NotConcreteRegistrationException
054         *                              if the implementation is not a concrete class.
055         * @throws NullPointerException if one of the parameters is <code>null</code>
056         */
057        public MethodInjector(final Object componentKey, final Class componentImplementation, Parameter[] parameters, ComponentMonitor monitor,
058                              LifecycleStrategy lifecycleStrategy, String methodName, boolean useNames) throws AbstractInjector.NotConcreteRegistrationException {
059            super(componentKey, componentImplementation, parameters, monitor, lifecycleStrategy, useNames);
060            this.methodName = methodName;
061        }
062    
063        protected Method getInjectorMethod() {
064            Method[] methods = new Method[0];
065            try {
066                methods = super.getComponentImplementation().getMethods();
067            } catch (AmbiguousComponentResolutionException e) {
068                e.setComponent(getComponentImplementation());
069                throw e;
070            }
071            for (Method method : methods) {
072                if (method.getName().equals(methodName)) {
073                    return method;
074                }
075            }
076            return null;
077        }
078    
079        public T getComponentInstance(final PicoContainer container, Type into) throws PicoCompositionException {
080            if (instantiationGuard == null) {
081                instantiationGuard = new ThreadLocalCyclicDependencyGuard() {
082                    public Object run() {
083                        Method method = getInjectorMethod();
084                        T inst = null;
085                        ComponentMonitor componentMonitor = currentMonitor();
086                        try {
087                            componentMonitor.instantiating(container, MethodInjector.this, null);
088                            long startTime = System.currentTimeMillis();
089                            Object[] parameters = null;
090                            inst = getComponentImplementation().newInstance();
091                            if (method != null) {
092                                parameters = getMemberArguments(guardedContainer, method);
093                                invokeMethod(method, parameters, inst, container);
094                            }
095                            componentMonitor.instantiated(container, MethodInjector.this,
096                                                          null, inst, parameters, System.currentTimeMillis() - startTime);
097                            return inst;
098                        } catch (InstantiationException e) {
099                            return caughtInstantiationException(componentMonitor, null, e, container);
100                        } catch (IllegalAccessException e) {
101                            return caughtIllegalAccessException(componentMonitor, method, inst, e);
102    
103                        }
104                    }
105                };
106            }
107            instantiationGuard.setGuardedContainer(container);
108            return (T) instantiationGuard.observe(getComponentImplementation());
109        }
110    
111        protected Object[] getMemberArguments(PicoContainer container, final Method method) {
112            return super.getMemberArguments(container, method, method.getParameterTypes(), getBindings(method.getParameterAnnotations()));
113        }
114    
115        @Override
116        public Object decorateComponentInstance(final PicoContainer container, final Type into, final T instance) {
117            if (instantiationGuard == null) {
118                instantiationGuard = new ThreadLocalCyclicDependencyGuard() {
119                    public Object run() {
120                        Method method = getInjectorMethod();
121                        if (method.getDeclaringClass().isAssignableFrom(instance.getClass())) {
122                            Object[] parameters = getMemberArguments(guardedContainer, method);
123                            return invokeMethod(method, parameters, instance, container);
124                        }
125                        return null;
126                    }
127                };
128            }
129            instantiationGuard.setGuardedContainer(container);
130            return instantiationGuard.observe(getComponentImplementation());
131    
132        }
133    
134        private Object invokeMethod(Method method, Object[] parameters, T instance, PicoContainer container) {
135            try {
136                currentMonitor().invoking(container, MethodInjector.this, (Member) method, instance);
137                return method.invoke(instance, parameters);
138            } catch (IllegalAccessException e) {
139                return caughtIllegalAccessException(currentMonitor(), method, instance, e);
140            } catch (InvocationTargetException e) {
141                currentMonitor().invocationFailed(method, instance, e);
142                if (e.getTargetException() instanceof RuntimeException) {
143                    throw (RuntimeException) e.getTargetException();
144                } else if (e.getTargetException() instanceof Error) {
145                    throw (Error) e.getTargetException();
146                }
147                return null;
148            }
149        }
150    
151    
152        @Override
153        public void verify(final PicoContainer container) throws PicoCompositionException {
154            if (verifyingGuard == null) {
155                verifyingGuard = new ThreadLocalCyclicDependencyGuard() {
156                    public Object run() {
157                        final Method method = getInjectorMethod();
158                        final Class[] parameterTypes = method.getParameterTypes();
159                        final Parameter[] currentParameters = parameters != null ? parameters : createDefaultParameters(parameterTypes);
160                        for (int i = 0; i < currentParameters.length; i++) {
161                            currentParameters[i].verify(container, MethodInjector.this, parameterTypes[i],
162                                new ParameterNameBinding(getParanamer(), getComponentImplementation(), method, i), useNames(),
163                                                        getBindings(method.getParameterAnnotations())[i]);
164                        }
165                        return null;
166                    }
167                };
168            }
169            verifyingGuard.setGuardedContainer(container);
170            verifyingGuard.observe(getComponentImplementation());
171        }
172    
173        public String getDescriptor() {
174            return "MethodInjector-";
175        }
176    
177        protected boolean isNullParamAllowed(AccessibleObject member, int i) {
178            Annotation[] annotations = ((Method) member).getParameterAnnotations()[i];
179            for (Annotation annotation : annotations) {
180                if (annotation instanceof Nullable) {
181                    return true;
182                }
183            }
184            return false;
185        }
186    
187    
188        public static class ByReflectionMethod extends MethodInjector {
189            private final Method injectionMethod;
190    
191            public ByReflectionMethod(Object componentKey, Class componentImplementation, Parameter[] parameters, ComponentMonitor monitor, LifecycleStrategy lifecycleStrategy, Method injectionMethod, boolean useNames) throws NotConcreteRegistrationException {
192                super(componentKey, componentImplementation, parameters, monitor, lifecycleStrategy, null, useNames);
193                this.injectionMethod = injectionMethod;
194            }
195            
196            @Override
197            protected Method getInjectorMethod() {
198                return injectionMethod;
199            }
200            public String getDescriptor() {
201                return "ReflectionMethodInjector[" + injectionMethod + "]-";
202            }
203    
204        }
205    
206    }