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
010package org.picocontainer.injectors;
011
012import org.picocontainer.ComponentMonitor;
013import org.picocontainer.Parameter;
014import org.picocontainer.PicoCompositionException;
015import org.picocontainer.PicoContainer;
016import org.picocontainer.annotations.Nullable;
017
018import java.lang.annotation.Annotation;
019import java.lang.reflect.AccessibleObject;
020import java.lang.reflect.InvocationTargetException;
021import java.lang.reflect.Member;
022import java.lang.reflect.Method;
023import java.lang.reflect.Type;
024import 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")
039public 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}