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