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 *****************************************************************************/
010package org.picocontainer.parameters;
011
012import org.picocontainer.Behavior;
013import org.picocontainer.ComponentAdapter;
014import org.picocontainer.Converters;
015import org.picocontainer.Converting;
016import org.picocontainer.DefaultPicoContainer;
017import org.picocontainer.LifecycleStrategy;
018import org.picocontainer.NameBinding;
019import org.picocontainer.Parameter;
020import org.picocontainer.PicoContainer;
021import org.picocontainer.PicoVisitor;
022import org.picocontainer.adapters.InstanceAdapter;
023import org.picocontainer.injectors.AbstractInjector;
024import org.picocontainer.injectors.InjectInto;
025import org.picocontainer.injectors.Provider;
026
027import java.io.Serializable;
028import java.lang.annotation.Annotation;
029import java.lang.reflect.ParameterizedType;
030import java.lang.reflect.Type;
031import java.util.Collection;
032import java.util.HashSet;
033import java.util.Iterator;
034import java.util.List;
035import 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")
051public 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}