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 }