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