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 *****************************************************************************/
009package org.picocontainer.injectors;
010
011import com.thoughtworks.paranamer.AdaptiveParanamer;
012import com.thoughtworks.paranamer.AnnotationParanamer;
013import com.thoughtworks.paranamer.CachingParanamer;
014import com.thoughtworks.paranamer.Paranamer;
015import org.picocontainer.ComponentAdapter;
016import org.picocontainer.ComponentMonitor;
017import org.picocontainer.Parameter;
018import org.picocontainer.PicoCompositionException;
019import org.picocontainer.PicoContainer;
020import org.picocontainer.annotations.Bind;
021
022import java.lang.annotation.Annotation;
023import java.lang.reflect.AccessibleObject;
024import java.lang.reflect.Type;
025
026import static org.picocontainer.injectors.PrimitiveMemberChecker.isPrimitiveArgument;
027
028/**
029 * Injection will happen in a single member function on the component.
030 *
031 * @author Paul Hammant 
032 * 
033 */
034@SuppressWarnings("serial")
035public abstract class SingleMemberInjector<T> extends AbstractInjector<T> {
036
037    private transient Paranamer paranamer;
038
039    public SingleMemberInjector(Object componentKey,
040                                Class componentImplementation,
041                                Parameter[] parameters,
042                                ComponentMonitor monitor,
043                                boolean useNames) {
044        super(componentKey, componentImplementation, parameters, monitor, useNames);
045    }
046
047    protected Paranamer getParanamer() {
048        if (paranamer == null) {
049            paranamer = new CachingParanamer(new AnnotationParanamer(new AdaptiveParanamer()));
050        }
051        return paranamer;
052    }
053
054    @SuppressWarnings("unchecked")
055    protected Object[] getMemberArguments(PicoContainer container, final AccessibleObject member, final Type[] parameterTypes, final Annotation[] bindings) {
056        boxParameters(parameterTypes);
057        Object[] result = new Object[parameterTypes.length];
058        final Parameter[] currentParameters = parameters != null ? parameters : createDefaultParameters(parameterTypes.length);
059
060        for (int i = 0; i < currentParameters.length; i++) {
061            result[i] = getParameter(container, member, i, parameterTypes[i], bindings[i], currentParameters[i], null);
062        }
063
064        return result;
065    }
066
067    protected void boxParameters(Type[] parameterTypes) {
068        for (int i = 0; i < parameterTypes.length; i++) {
069            parameterTypes[i] = box(parameterTypes[i]);
070        }
071    }
072
073    protected Object getParameter(PicoContainer container, AccessibleObject member, int i, Type parameterType, Annotation binding,
074                                  Parameter currentParameter, ComponentAdapter<?> injecteeAdapter) {
075        ParameterNameBinding expectedNameBinding = new ParameterNameBinding(getParanamer(), member, i);
076        Object result = null;
077        try {
078            result = currentParameter.resolve(container, this, injecteeAdapter, parameterType, expectedNameBinding, useNames(), binding).resolveInstance();
079        } catch (AmbiguousComponentResolutionException e) {
080            e.setMember(member);
081            throw e;
082        }
083        nullCheck(member, i, expectedNameBinding, result);
084        return result;
085    }
086
087    @SuppressWarnings("synthetic-access")
088    protected void nullCheck(AccessibleObject member, int i, ParameterNameBinding expectedNameBinding, Object result) {
089        if (result == null && !isNullParamAllowed(member, i)) {
090            throw new ParameterCannotBeNullException(i, member, expectedNameBinding.getName());
091        }
092    }
093
094    /**
095     * Checks to see if a null parameter is allowed in the given
096     * constructor/field/method.  The default version allows null 
097     * if the target object is not a primitive type.
098     * @param member constructor method or field
099     * @param i parameter #.
100     * @return true if the null parameter might be allowed.
101     */
102    protected boolean isNullParamAllowed(AccessibleObject member, int i) {
103        return !(isPrimitiveArgument(member, i));
104    }
105
106    protected Annotation[] getBindings(Annotation[][] annotationss) {
107        Annotation[] retVal = new Annotation[annotationss.length];
108        for (int i = 0; i < annotationss.length; i++) {
109            Annotation[] annotations = annotationss[i];
110            for (Annotation annotation : annotations) {
111                if (annotation.annotationType().getAnnotation(Bind.class) != null) {
112                    retVal[i] = annotation;
113                    break;
114                }
115            }
116        }
117        return retVal;
118    }
119
120    public static class ParameterCannotBeNullException extends PicoCompositionException {
121        private final String name;
122        private ParameterCannotBeNullException(int ix, AccessibleObject member, String name) {
123            super("Parameter " + ix + " of '" + member + "' named '" + name + "' cannot be null");
124            this.name = name;
125        }
126        public String getParameterName() {
127            return name;
128        }
129    }
130
131}