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    package org.picocontainer.injectors;
010    
011    import com.thoughtworks.paranamer.AdaptiveParanamer;
012    import com.thoughtworks.paranamer.AnnotationParanamer;
013    import com.thoughtworks.paranamer.CachingParanamer;
014    import com.thoughtworks.paranamer.Paranamer;
015    import org.picocontainer.ComponentAdapter;
016    import org.picocontainer.ComponentMonitor;
017    import org.picocontainer.Parameter;
018    import org.picocontainer.PicoCompositionException;
019    import org.picocontainer.PicoContainer;
020    import org.picocontainer.annotations.Bind;
021    
022    import java.lang.annotation.Annotation;
023    import java.lang.reflect.AccessibleObject;
024    import java.lang.reflect.Type;
025    
026    import 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")
035    public 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    }