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