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 org.picocontainer.ComponentMonitor;
013    import org.picocontainer.Parameter;
014    import org.picocontainer.PicoCompositionException;
015    import org.picocontainer.PicoContainer;
016    import org.picocontainer.annotations.Nullable;
017    
018    import java.lang.annotation.Annotation;
019    import java.lang.reflect.AccessibleObject;
020    import java.lang.reflect.InvocationTargetException;
021    import java.lang.reflect.Member;
022    import java.lang.reflect.Method;
023    import java.lang.reflect.Type;
024    import java.util.Set;
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 abstract 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 methodName              the method name
051         * @param useNames                use argument names when looking up dependencies
052         * @throws AbstractInjector.NotConcreteRegistrationException
053         *                              if the implementation is not a concrete class.
054         * @throws NullPointerException if one of the parameters is <code>null</code>
055         */
056        public MethodInjector(final Object componentKey, final Class componentImplementation, Parameter[] parameters, ComponentMonitor monitor,
057                              String methodName, boolean useNames) throws AbstractInjector.NotConcreteRegistrationException {
058            super(componentKey, componentImplementation, parameters, monitor, useNames);
059            this.methodName = methodName;
060        }
061    
062        protected abstract Method getInjectorMethod();
063    
064        @Override
065        public T getComponentInstance(final PicoContainer container, @SuppressWarnings("unused") Type into) throws PicoCompositionException {
066            if (instantiationGuard == null) {
067                instantiationGuard = new ThreadLocalCyclicDependencyGuard() {
068                    @Override
069                    @SuppressWarnings("synthetic-access")
070                    public Object run(Object instance) {
071                        Method method = getInjectorMethod();
072                        T inst = null;
073                        ComponentMonitor componentMonitor = currentMonitor();
074                        try { // TODO .. instantiating() ???
075                            componentMonitor.instantiating(container, MethodInjector.this, null);
076                            long startTime = System.currentTimeMillis();
077                            Object[] methodParameters = null;
078                            inst = getComponentImplementation().newInstance();
079                            if (method != null) {
080                                methodParameters = getMemberArguments(guardedContainer, method);
081                                invokeMethod(method, methodParameters, inst, container);
082                            }
083                            componentMonitor.instantiated(container, MethodInjector.this,
084                                                          null, inst, methodParameters, System.currentTimeMillis() - startTime);
085                            return inst;
086                        } catch (InstantiationException e) {
087                            return caughtInstantiationException(componentMonitor, null, e, container);
088                        } catch (IllegalAccessException e) {
089                            return caughtIllegalAccessException(componentMonitor, method, inst, e);
090    
091                        }
092                    }
093                };
094            }
095            instantiationGuard.setGuardedContainer(container);
096            return (T) instantiationGuard.observe(getComponentImplementation(), null);
097        }
098    
099        protected Object[] getMemberArguments(PicoContainer container, final Method method) {
100            return super.getMemberArguments(container, method, method.getParameterTypes(), getBindings(method.getParameterAnnotations()));
101        }
102    
103        @Override
104        public Object decorateComponentInstance(final PicoContainer container, @SuppressWarnings("unused") final Type into, final T instance) {
105            if (instantiationGuard == null) {
106                instantiationGuard = new ThreadLocalCyclicDependencyGuard() {
107                    @Override
108                    @SuppressWarnings("synthetic-access")
109                    public Object run(Object inst) {
110                        Method method = getInjectorMethod();
111                        if (method != null && method.getDeclaringClass().isAssignableFrom(inst.getClass())) {
112                            Object[] methodParameters = getMemberArguments(guardedContainer, method);
113                            return invokeMethod(method, methodParameters, (T) inst, container);
114                        }
115                        return null;
116                    }
117                };
118            }
119            instantiationGuard.setGuardedContainer(container);
120            Object o = instantiationGuard.observe(getComponentImplementation(), instance);
121            return o;
122        }
123    
124        private Object invokeMethod(Method method, Object[] methodParameters, T instance, PicoContainer container) {
125            try {
126                Object rv = currentMonitor().invoking(container, MethodInjector.this, (Member) method, instance, methodParameters);
127                if (rv == ComponentMonitor.KEEP) {
128                    long str = System.currentTimeMillis();
129                    rv = method.invoke(instance, methodParameters);
130                    currentMonitor().invoked(container, MethodInjector.this, method, instance, System.currentTimeMillis() - str, methodParameters, rv);
131                }
132                return rv;
133            } catch (IllegalAccessException e) {
134                return caughtIllegalAccessException(currentMonitor(), method, instance, e);
135            } catch (InvocationTargetException e) {
136                currentMonitor().invocationFailed(method, instance, e);
137                if (e.getTargetException() instanceof RuntimeException) {
138                    throw (RuntimeException) e.getTargetException();
139                } else if (e.getTargetException() instanceof Error) {
140                    throw (Error) e.getTargetException();
141                }
142                throw new PicoCompositionException(e);
143            }
144        }
145    
146    
147        @Override
148        public void verify(final PicoContainer container) throws PicoCompositionException {
149            if (verifyingGuard == null) {
150                verifyingGuard = new ThreadLocalCyclicDependencyGuard() {
151                    @Override
152                    public Object run(Object instance) {
153                        final Method method = getInjectorMethod();
154                        final Class[] parameterTypes = method.getParameterTypes();
155                        final Parameter[] currentParameters = parameters != null ? parameters : createDefaultParameters(parameterTypes.length);
156                        for (int i = 0; i < currentParameters.length; i++) {
157                            currentParameters[i].verify(container, MethodInjector.this, parameterTypes[i],
158                                new ParameterNameBinding(getParanamer(), method, i), useNames(),
159                                                        getBindings(method.getParameterAnnotations())[i]);
160                        }
161                        return null;
162                    }
163                };
164            }
165            verifyingGuard.setGuardedContainer(container);
166            verifyingGuard.observe(getComponentImplementation(), null);
167        }
168    
169        @Override
170        protected boolean isNullParamAllowed(AccessibleObject member, int i) {
171            Annotation[] annotations = ((Method) member).getParameterAnnotations()[i];
172            for (Annotation annotation : annotations) {
173                if (annotation instanceof Nullable) {
174                    return true;
175                }
176            }
177            return false;
178        }
179    
180    
181        public static class ByReflectionMethod extends MethodInjector {
182            private final Method injectionMethod;
183    
184            public ByReflectionMethod(Object componentKey, Class componentImplementation, Parameter[] parameters, ComponentMonitor monitor, Method injectionMethod, boolean useNames) throws NotConcreteRegistrationException {
185                super(componentKey, componentImplementation, parameters, monitor, null, useNames);
186                this.injectionMethod = injectionMethod;
187            }
188            
189            @Override
190            protected Method getInjectorMethod() {
191                return injectionMethod;
192            }
193            
194            @Override
195            public String getDescriptor() {
196                return "MethodInjector.ByReflectionMethod[" + injectionMethod + "]-";
197            }
198    
199        }
200    
201        public static class ByMethodName extends MethodInjector {
202            private Set<String> injectionMethodNames;
203    
204            public ByMethodName(Object componentKey, Class componentImplementation, Parameter[] parameters, ComponentMonitor monitor, Set<String> injectionMethodNames, boolean useNames) throws NotConcreteRegistrationException {
205                super(componentKey, componentImplementation, parameters, monitor, null, useNames);
206                ByMethodName.this.injectionMethodNames = injectionMethodNames;
207            }
208    
209            @Override
210            protected Method getInjectorMethod() {
211                for (Method method : super.getComponentImplementation().getMethods()) {
212                    if (injectionMethodNames.contains(method.getName())) {
213                        return method;
214                    }
215                }
216                return null;
217            }
218    
219    
220            @Override
221            public String getDescriptor() {
222                return "MethodInjector.ByMethodName" + injectionMethodNames + "-";
223            }
224    
225        }
226    
227    }