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 }