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