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 }