    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.Method;
    017    import java.lang.reflect.ParameterizedType;
    018    import java.lang.reflect.Type;
    019    import java.lang.annotation.Annotation;
    020    import java.util.HashMap;
    021    import java.util.HashSet;
    022    import java.util.List;
    023    import java.util.Map;
    024    import java.util.Set;
    025
    026    import org.picocontainer.ComponentAdapter;
    027    import org.picocontainer.Parameter;
    028    import org.picocontainer.NameBinding;
    029    import org.picocontainer.PicoContainer;
    030    import org.picocontainer.PicoVisitor;
    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        private static class ValueOfConverter implements Converter {
    053            private Method m;
    054            private ValueOfConverter(Class clazz) {
    055                try {
    056                    m = clazz.getMethod("valueOf", String.class);
    057                } catch (NoSuchMethodException e) {
    058                }
    059            }
    060
    061            public Object convert(String paramValue) {
    062                try {
    063                    return m.invoke(null, paramValue);
    064                } catch (IllegalAccessException e) {
    065                } catch (InvocationTargetException e) {
    066                }
    067                return null;
    068
    069            }
    070        }
    071        private static class NewInstanceConverter implements Converter {
    072            private Constructor c;
    073
    074            private NewInstanceConverter(Class clazz) {
    075                try {
    076                    c = clazz.getConstructor(String.class);
    077                } catch (NoSuchMethodException e) {
    078                }
    079            }
    080
    081            public Object convert(String paramValue) {
    082                try {
    083                    return c.newInstance(paramValue);
    084                } catch (IllegalAccessException e) {
    085                } catch (InvocationTargetException e) {
    086                } catch (InstantiationException e) {
    087                }
    088                return null;
    089            }
    090        }
    091
    092        /** <code>BASIC_DEFAULT</code> is an instance of BasicComponentParameter using the default constructor. */
    093        public static final BasicComponentParameter BASIC_DEFAULT = new BasicComponentParameter();
    094
    095        private Object componentKey;
    096
    097
    098        private static final Map<Class, Converter> stringConverters = new HashMap<Class, Converter>();
    099        static {
    100            stringConverters.put(Integer.class, new ValueOfConverter(Integer.class));
    101            stringConverters.put(Double.class, new ValueOfConverter(Double.class));
    102            stringConverters.put(Boolean.class, new ValueOfConverter(Boolean.class));
    103            stringConverters.put(Long.class, new ValueOfConverter(Long.class));
    104            stringConverters.put(Float.class, new ValueOfConverter(Float.class));
    105            stringConverters.put(Character.class, new ValueOfConverter(Character.class));
    106            stringConverters.put(Byte.class, new ValueOfConverter(Byte.class));
    107            stringConverters.put(Byte.class, new ValueOfConverter(Short.class));
    108            stringConverters.put(File.class, new NewInstanceConverter(File.class));
    109
    110        }
    111
    112
    113        /**
    114         * Expect a parameter matching a component of a specific key.
    115         *
    116         * @param componentKey the key of the desired addComponent
    117         */
    118        public BasicComponentParameter(Object componentKey) {
    119            this.componentKey = componentKey;
    120        }
    121
    122        /** Expect any parameter of the appropriate type. */
    123        public BasicComponentParameter() {
    124        }
    125
    126        /**
    127         * Check whether the given Parameter can be satisfied by the container.
    128         *
    129         * @return <code>true</code> if the Parameter can be verified.
    130         *
    131         * @throws org.picocontainer.PicoCompositionException
    132         *          {@inheritDoc}
    133         * @see Parameter#isResolvable(PicoContainer, ComponentAdapter, Class, NameBinding ,boolean, Annotation)
    134         */
    135        public boolean isResolvable(PicoContainer container,
    136                                    ComponentAdapter<?> adapter,
    137                                    Type expectedType,
    138                                    NameBinding expectedNameBinding, boolean useNames, Annotation binding) {
    139
    140            Class<?> resolvedClassType = null;
    141            // TODO take this out for Pico3
    142            if (!(expectedType instanceof Class)) {
    143                    if (expectedType instanceof ParameterizedType) {
    144                            resolvedClassType = (Class<?>) ((ParameterizedType)expectedType).getRawType();
    145                    } else {
    146                            return false;
    147                    }
    148            } else {
    149                    resolvedClassType = (Class<?>)expectedType;
    150            }
    151            assert resolvedClassType != null;
    152
    153            return resolveAdapter(container, adapter, resolvedClassType, expectedNameBinding, useNames, binding) != null;
    154        }
    155
    156
    157        public Object resolveInstance(PicoContainer container,
    158                                     ComponentAdapter<?> adapter,
    159                                     Type expectedType,
    160                                     NameBinding expectedNameBinding, boolean useNames, Annotation binding) {
    161            final ComponentAdapter componentAdapter =
    162                resolveAdapter(container, adapter, (Class<?>)expectedType, expectedNameBinding, useNames, binding);
    163            if (componentAdapter != null) {
    164                Object o = container.getComponent(componentAdapter.getComponentKey(), adapter.getComponentImplementation());
    165                if (o instanceof String && expectedType != String.class) {
    166                    Converter converter = stringConverters.get(expectedType);
    167                    return converter.convert((String) o);
    168                }
    169                return o;
    170            }
    171            return null;
    172        }
    173
    174        public void verify(PicoContainer container,
    175                           ComponentAdapter<?> adapter,
    176                           Type expectedType,
    177                           NameBinding expectedNameBinding, boolean useNames, Annotation binding) {
    178            final ComponentAdapter componentAdapter =
    179                resolveAdapter(container, adapter, (Class<?>)expectedType, expectedNameBinding, useNames, binding);
    180            if (componentAdapter == null) {
    181                final Set<Type> set = new HashSet<Type>();
    182                set.add(expectedType);
    183                throw new AbstractInjector.UnsatisfiableDependenciesException(adapter, null, set, container);
    184            }
    185            componentAdapter.verify(container);
    186        }
    187
    188        /**
    189         * Visit the current {@link Parameter}.
    190         *
    191         * @see org.picocontainer.Parameter#accept(org.picocontainer.PicoVisitor)
    192         */
    193        public void accept(final PicoVisitor visitor) {
    194            visitor.visitParameter(this);
    195        }
    196
    197        private <T> ComponentAdapter<T> resolveAdapter(PicoContainer container,
    198                                                       ComponentAdapter adapter,
    199                                                       Class<T> expectedType,
    200                                                       NameBinding expectedNameBinding, boolean useNames, Annotation binding) {
    201            Class type = expectedType;
    202            if (type.isPrimitive()) {
    203                String expectedTypeName = expectedType.getName();
    204                if (expectedTypeName == "int") {
    205                    type = Integer.class;
    206                } else if (expectedTypeName == "long") {
    207                    type = Long.class;
    208                } else if (expectedTypeName == "float") {
    209                    type = Float.class;
    210                } else if (expectedTypeName == "double") {
    211                    type = Double.class;
    212                } else if (expectedTypeName == "boolean") {
    213                    type = Boolean.class;
    214                } else if (expectedTypeName == "char") {
    215                    type = Character.class;
    216                } else if (expectedTypeName == "short") {
    217                    type = Short.class;
    218                } else if (expectedTypeName == "byte") {
    219                    type = Byte.class;
    220                }
    221            }
    222
    223            final ComponentAdapter<T> result = getTargetAdapter(container, type, expectedNameBinding, adapter, useNames,
    224                                                                binding);
    225            if (result == null) {
    226                return null;
    227            }
    228
    229            if (!type.isAssignableFrom(result.getComponentImplementation())) {
    230                if (!(result.getComponentImplementation() == String.class && stringConverters.containsKey(type))) {
    231                    return null;
    232                }
    233            }
    234            return result;
    235        }
    236
    237        @SuppressWarnings({ "unchecked" })
    238        private static <T> ComponentAdapter<T> typeComponentAdapter(ComponentAdapter<?> componentAdapter) {
    239            return (ComponentAdapter<T>)componentAdapter;
    240        }
    241
    242        private <T> ComponentAdapter<T> getTargetAdapter(PicoContainer container, Class<T> expectedType,
    243                                                         NameBinding expectedNameBinding,
    244                                                         ComponentAdapter excludeAdapter, boolean useNames, Annotation binding) {
    245            if (componentKey != null) {
    246                // key tells us where to look so we follow
    247                return typeComponentAdapter(container.getComponentAdapter(componentKey));
    248            } else if (excludeAdapter == null) {
    249                return container.getComponentAdapter(expectedType, (NameBinding) null);
    250            } else {
    251                return findTargetAdapter(container, expectedType, expectedNameBinding, excludeAdapter, useNames, binding);
    252            }
    253        }
    254
    255        private <T> ComponentAdapter<T> findTargetAdapter(PicoContainer container, Class<T> expectedType,
    256                                                          NameBinding expectedNameBinding, ComponentAdapter excludeAdapter,
    257                                                          boolean useNames, Annotation binding) {
    258            Object excludeKey = excludeAdapter.getComponentKey();
    259            ComponentAdapter byKey = container.getComponentAdapter((Object)expectedType);
    260            if (byKey != null && !excludeKey.equals(byKey.getComponentKey())) {
    261                return typeComponentAdapter(byKey);
    262            }
    263            if (useNames) {
    264                ComponentAdapter found = container.getComponentAdapter(expectedNameBinding.getName());
    265                if ((found != null) && areCompatible(expectedType, found) && found != excludeAdapter) {
    266                    return (ComponentAdapter<T>) found;
    267                }
    268            }
    269            List<ComponentAdapter<T>> found = binding == null ? container.getComponentAdapters(expectedType) :
    270                                              container.getComponentAdapters(expectedType, binding.annotationType());
    271            removeExcludedAdapterIfApplicable(excludeKey, found);
    272            if (found.size() == 0) {
    273                return noMatchingAdaptersFound(container, expectedType, expectedNameBinding, binding);
    274            } else if (found.size() == 1) {
    275                return found.get(0);
    276            } else {
    277                throw tooManyMatchingAdaptersFound(expectedType, found);
    278            }
    279        }
    280
    281        private <T> ComponentAdapter<T> noMatchingAdaptersFound(PicoContainer container, Class<T> expectedType,
    282                                                                NameBinding expectedNameBinding, Annotation binding) {
    283            if (container.getParent() != null) {
    284                if (binding != null) {
    285                    return container.getParent().getComponentAdapter(expectedType, binding.getClass());
    286                } else {
    287                    return container.getParent().getComponentAdapter(expectedType, expectedNameBinding);
    288                }
    289            } else {
    290                return null;
    291            }
    292        }
    293
    294        private <T> AbstractInjector.AmbiguousComponentResolutionException tooManyMatchingAdaptersFound(Class<T> expectedType, List<ComponentAdapter<T>> found) {
    295            Class[] foundClasses = new Class[found.size()];
    296            for (int i = 0; i < foundClasses.length; i++) {
    297                foundClasses[i] = found.get(i).getComponentImplementation();
    298            }
    299            AbstractInjector.AmbiguousComponentResolutionException exception = new AbstractInjector.AmbiguousComponentResolutionException(expectedType, foundClasses);
    300            return exception;
    301        }
    302
    303        private <T> void removeExcludedAdapterIfApplicable(Object excludeKey, List<ComponentAdapter<T>> found) {
    304            ComponentAdapter exclude = null;
    305            for (ComponentAdapter work : found) {
    306                if (work.getComponentKey().equals(excludeKey)) {
    307                    exclude = work;
    308                    break;
    309                }
    310            }
    311            found.remove(exclude);
    312        }
    313
    314        private <T> boolean areCompatible(Class<T> expectedType, ComponentAdapter found) {
    315            Class foundImpl = found.getComponentImplementation();
    316            return expectedType.isAssignableFrom(foundImpl) ||
    317                   (foundImpl == String.class && stringConverters.containsKey(expectedType))  ;
    318        }
    319    }