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