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én
042     * @author Aslak Hellesøy
043     * @author Jö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    }