    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     * Original code by                                                          *
    009     *****************************************************************************/
    010    package org.picocontainer.parameters;
    011
    012    import java.io.File;
    013    import java.io.Serializable;
    014    import java.lang.reflect.Constructor;
    015    import java.lang.reflect.InvocationTargetException;
    016    import java.lang.reflect.ParameterizedType;
    017    import java.lang.reflect.Type;
    018    import java.lang.annotation.Annotation;
    019    import java.util.HashMap;
    020    import java.util.HashSet;
    021    import java.util.List;
    022    import java.util.Map;
    023    import java.util.Set;
    024
    025    import org.picocontainer.ComponentAdapter;
    026    import org.picocontainer.Parameter;
    027    import org.picocontainer.NameBinding;
    028    import org.picocontainer.PicoContainer;
    029    import org.picocontainer.PicoVisitor;
    030    import org.picocontainer.DefaultPicoContainer;
    031    import org.picocontainer.injectors.AbstractInjector;
    032
    033    /**
    034     * A BasicComponentParameter should be used to pass in a particular component as argument to a
    035     * different component's constructor. This is particularly useful in cases where several
    036     * components of the same type have been registered, but with a different key. Passing a
    037     * ComponentParameter as a parameter when registering a component will give PicoContainer a hint
    038     * about what other component to use in the constructor. This Parameter will never resolve
    039     * against a collecting type, that is not directly registered in the PicoContainer itself.
    040     *
    041     * @author Jon Tirs&eacute;n
    042     * @author Aslak Helles&oslash;y
    043     * @author J&ouml;rg Schaible
    044     * @author Thomas Heller
    045     */
    046    @SuppressWarnings("serial")
    047    public class BasicComponentParameter implements Parameter, Serializable {
    048
    049        private static interface Converter {
    050            Object convert(String paramValue);
    051        }
    052
    053        private static class NewInstanceConverter implements Converter {
    054            private Constructor c;
    055
    056            private NewInstanceConverter(Class clazz) {
    057                try {
    058                    c = clazz.getConstructor(String.class);
    059                } catch (NoSuchMethodException e) {
    060                }
    061            }
    062
    063            public Object convert(String paramValue) {
    064                try {
    065                    return c.newInstance(paramValue);
    066                } catch (IllegalAccessException e) {
    067                } catch (InvocationTargetException e) {
    068                } catch (InstantiationException e) {
    069                }
    070                return null;
    071            }
    072        }
    073
    074        /** <code>BASIC_DEFAULT</code> is an instance of BasicComponentParameter using the default constructor. */
    075        public static final BasicComponentParameter BASIC_DEFAULT = new BasicComponentParameter();
    076
    077        private Object componentKey;
    078
    079
    080        private static final Map<Class, Converter> stringConverters = new HashMap<Class, Converter>();
    081        static {
    082            stringConverters.put(Integer.class, new Converter(){
    083                public Object convert(String paramValue) {
    084                    return Integer.valueOf(paramValue);
    085                }
    086            });
    087            stringConverters.put(Double.class, new Converter() {
    088                public Object convert(String paramValue) {
    089                    return Double.valueOf(paramValue);
    090                }
    091            });
    092            stringConverters.put(Boolean.class, new Converter(){
    093                public Object convert(String paramValue) {
    094                    return Boolean.valueOf(paramValue);
    095                }
    096            });
    097            stringConverters.put(Long.class, new Converter() {
    098                public Object convert(String paramValue) {
    099                    return Long.valueOf(paramValue);
    100                }
    101            });
    102            stringConverters.put(Float.class, new Converter() {
    103                public Object convert(String paramValue) {
    104                    return Float.valueOf(paramValue);
    105                }
    106            });
    107            stringConverters.put(Character.class, new Converter() {
    108                public Object convert(String paramValue) {
    109                    return paramValue.charAt(0);
    110                }
    111            });
    112            stringConverters.put(Byte.class, new Converter() {
    113                public Object convert(String paramValue) {
    114                    return Byte.valueOf(paramValue);
    115                }
    116            });
    117            stringConverters.put(Short.class, new Converter() {
    118                public Object convert(String paramValue) {
    119                    return Short.valueOf(paramValue);
    120                }
    121            });
    122            stringConverters.put(File.class, new Converter() {
    123                public Object convert(String paramValue) {
    124                    return new File(paramValue);
    125                }
    126            });
    127
    128        }
    129
    130
    131        /**
    132         * Expect a parameter matching a component of a specific key.
    133         *
    134         * @param componentKey the key of the desired addComponent
    135         */
    136        public BasicComponentParameter(Object componentKey) {
    137            this.componentKey = componentKey;
    138        }
    139
    140        /** Expect any parameter of the appropriate type. */
    141        public BasicComponentParameter() {
    142        }
    143
    144        /**
    145         * Check whether the given Parameter can be satisfied by the container.
    146         *
    147         * @return <code>true</code> if the Parameter can be verified.
    148         *
    149         * @throws org.picocontainer.PicoCompositionException
    150         *          {@inheritDoc}
    151         * @see Parameter#isResolvable(PicoContainer, ComponentAdapter, Class, NameBinding ,boolean, Annotation)
    152         */
    153        public Resolver resolve(final PicoContainer container,
    154                                final ComponentAdapter<?> forAdapter,
    155                                ComponentAdapter<?> injecteeAdapter, final Type expectedType,
    156                                NameBinding expectedNameBinding, boolean useNames, Annotation binding) {
    157
    158            Class<?> resolvedClassType = null;
    159            // TODO take this out for Pico3
    160            if (!(expectedType instanceof Class)) {
    161                    if (expectedType instanceof ParameterizedType) {
    162                            resolvedClassType = (Class<?>) ((ParameterizedType)expectedType).getRawType();
    163                    } else {
    164                            return new Parameter.NotResolved();
    165                    }
    166            } else {
    167                    resolvedClassType = (Class<?>)expectedType;
    168            }
    169            assert resolvedClassType != null;
    170
    171            ComponentAdapter<?> componentAdapter0;
    172            if (injecteeAdapter == null) {
    173                componentAdapter0 = resolveAdapter(container, forAdapter, resolvedClassType, expectedNameBinding, useNames, binding);
    174            } else {
    175                componentAdapter0 = injecteeAdapter;
    176            }
    177            final ComponentAdapter<?> componentAdapter = componentAdapter0;
    178            return new Resolver() {
    179                public boolean isResolved() {
    180                    return componentAdapter != null;
    181                }
    182                public Object resolveInstance() {
    183                    if (componentAdapter == null) {
    184                        return null;
    185                    }
    186                    Object o;
    187                    if (componentAdapter instanceof DefaultPicoContainer.LateInstance) {
    188                        o = ((DefaultPicoContainer.LateInstance) componentAdapter).getComponentInstance();
    189                    } else {
    190                        o = container.getComponent(componentAdapter.getComponentKey(), forAdapter.getComponentImplementation());
    191                    }
    192                    if (o instanceof String && expectedType != String.class) {
    193                        Converter converter = stringConverters.get(expectedType);
    194                        return converter.convert((String) o);
    195                    }
    196                    return o;
    197
    198                }
    199
    200                public ComponentAdapter<?> getComponentAdapter() {
    201                    return componentAdapter;
    202                }
    203            };
    204        }
    205
    206        public void verify(PicoContainer container,
    207                           ComponentAdapter<?> forAdapter,
    208                           Type expectedType,
    209                           NameBinding expectedNameBinding, boolean useNames, Annotation binding) {
    210            final ComponentAdapter componentAdapter =
    211                resolveAdapter(container, forAdapter, (Class<?>)expectedType, expectedNameBinding, useNames, binding);
    212            if (componentAdapter == null) {
    213                final Set<Type> set = new HashSet<Type>();
    214                set.add(expectedType);
    215                throw new AbstractInjector.UnsatisfiableDependenciesException(forAdapter, null, set, container);
    216            }
    217            componentAdapter.verify(container);
    218        }
    219
    220        /**
    221         * Visit the current {@link Parameter}.
    222         *
    223         * @see org.picocontainer.Parameter#accept(org.picocontainer.PicoVisitor)
    224         */
    225        public void accept(final PicoVisitor visitor) {
    226            visitor.visitParameter(this);
    227        }
    228
    229        protected <T> ComponentAdapter<T> resolveAdapter(PicoContainer container,
    230                                                       ComponentAdapter adapter,
    231                                                       Class<T> expectedType,
    232                                                       NameBinding expectedNameBinding, boolean useNames, Annotation binding) {
    233            Class type = expectedType;
    234            if (type.isPrimitive()) {
    235                String expectedTypeName = expectedType.getName();
    236                if (expectedTypeName == "int") {
    237                    type = Integer.class;
    238                } else if (expectedTypeName == "long") {
    239                    type = Long.class;
    240                } else if (expectedTypeName == "float") {
    241                    type = Float.class;
    242                } else if (expectedTypeName == "double") {
    243                    type = Double.class;
    244                } else if (expectedTypeName == "boolean") {
    245                    type = Boolean.class;
    246                } else if (expectedTypeName == "char") {
    247                    type = Character.class;
    248                } else if (expectedTypeName == "short") {
    249                    type = Short.class;
    250                } else if (expectedTypeName == "byte") {
    251                    type = Byte.class;
    252                }
    253            }
    254
    255            final ComponentAdapter<T> result = getTargetAdapter(container, type, expectedNameBinding, adapter, useNames,
    256                                                                binding);
    257            if (result == null) {
    258                return null;
    259            }
    260            if (!type.isAssignableFrom(result.getComponentImplementation())) {
    261                if (!(result.getComponentImplementation() == String.class && stringConverters.containsKey(type))) {
    262                    return null;
    263                }
    264            }
    265            return result;
    266        }
    267
    268        @SuppressWarnings({ "unchecked" })
    269        private static <T> ComponentAdapter<T> typeComponentAdapter(ComponentAdapter<?> componentAdapter) {
    270            return (ComponentAdapter<T>)componentAdapter;
    271        }
    272
    273        private <T> ComponentAdapter<T> getTargetAdapter(PicoContainer container, Class<T> expectedType,
    274                                                         NameBinding expectedNameBinding,
    275                                                         ComponentAdapter excludeAdapter, boolean useNames, Annotation binding) {
    276            if (componentKey != null) {
    277                // key tells us where to look so we follow
    278                return typeComponentAdapter(container.getComponentAdapter(componentKey));
    279            } else if (excludeAdapter == null) {
    280                return container.getComponentAdapter(expectedType, (NameBinding) null);
    281            } else {
    282                return findTargetAdapter(container, expectedType, expectedNameBinding, excludeAdapter, useNames, binding);
    283            }
    284        }
    285
    286        private <T> ComponentAdapter<T> findTargetAdapter(PicoContainer container, Class<T> expectedType,
    287                                                          NameBinding expectedNameBinding, ComponentAdapter excludeAdapter,
    288                                                          boolean useNames, Annotation binding) {
    289            Object excludeKey = excludeAdapter.getComponentKey();
    290            ComponentAdapter byKey = container.getComponentAdapter((Object)expectedType);
    291            if (byKey != null && !excludeKey.equals(byKey.getComponentKey())) {
    292                return typeComponentAdapter(byKey);
    293            }
    294            if (useNames) {
    295                ComponentAdapter found = container.getComponentAdapter(expectedNameBinding.getName());
    296                if ((found != null) && areCompatible(expectedType, found) && found != excludeAdapter) {
    297                    return (ComponentAdapter<T>) found;
    298                }
    299            }
    300            List<ComponentAdapter<T>> found = binding == null ? container.getComponentAdapters(expectedType) :
    301                                              container.getComponentAdapters(expectedType, binding.annotationType());
    302            removeExcludedAdapterIfApplicable(excludeKey, found);
    303            if (found.size() == 0) {
    304                return noMatchingAdaptersFound(container, expectedType, expectedNameBinding, binding);
    305            } else if (found.size() == 1) {
    306                return found.get(0);
    307            } else {
    308                throw tooManyMatchingAdaptersFound(expectedType, found);
    309            }
    310        }
    311
    312        private <T> ComponentAdapter<T> noMatchingAdaptersFound(PicoContainer container, Class<T> expectedType,
    313                                                                NameBinding expectedNameBinding, Annotation binding) {
    314            if (container.getParent() != null) {
    315                if (binding != null) {
    316                    return container.getParent().getComponentAdapter(expectedType, binding.getClass());
    317                } else {
    318                    return container.getParent().getComponentAdapter(expectedType, expectedNameBinding);
    319                }
    320            } else {
    321                return null;
    322            }
    323        }
    324
    325        private <T> AbstractInjector.AmbiguousComponentResolutionException tooManyMatchingAdaptersFound(Class<T> expectedType, List<ComponentAdapter<T>> found) {
    326            Class[] foundClasses = new Class[found.size()];
    327            for (int i = 0; i < foundClasses.length; i++) {
    328                foundClasses[i] = found.get(i).getComponentImplementation();
    329            }
    330            AbstractInjector.AmbiguousComponentResolutionException exception = new AbstractInjector.AmbiguousComponentResolutionException(expectedType, foundClasses);
    331            return exception;
    332        }
    333
    334        private <T> void removeExcludedAdapterIfApplicable(Object excludeKey, List<ComponentAdapter<T>> found) {
    335            ComponentAdapter exclude = null;
    336            for (ComponentAdapter work : found) {
    337                if (work.getComponentKey().equals(excludeKey)) {
    338                    exclude = work;
    339                    break;
    340                }
    341            }
    342            found.remove(exclude);
    343        }
    344
    345        private <T> boolean areCompatible(Class<T> expectedType, ComponentAdapter found) {
    346            Class foundImpl = found.getComponentImplementation();
    347            return expectedType.isAssignableFrom(foundImpl) ||
    348                   (foundImpl == String.class && stringConverters.containsKey(expectedType))  ;
    349        }
    350    }