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