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 org.picocontainer.ComponentAdapter; 013 import org.picocontainer.Parameter; 014 import org.picocontainer.NameBinding; 015 import org.picocontainer.PicoContainer; 016 import org.picocontainer.PicoCompositionException; 017 import org.picocontainer.PicoVisitor; 018 019 import java.io.Serializable; 020 import java.lang.reflect.Array; 021 import java.lang.annotation.Annotation; 022 import java.util.ArrayList; 023 import java.util.Collection; 024 import java.util.HashMap; 025 import java.util.HashSet; 026 import java.util.LinkedHashMap; 027 import java.util.List; 028 import java.util.Map; 029 import java.util.Set; 030 import java.util.SortedMap; 031 import java.util.SortedSet; 032 import java.util.TreeMap; 033 import java.util.TreeSet; 034 035 036 /** 037 * A CollectionComponentParameter should be used to support inject an {@link Array}, a 038 * {@link Collection}or {@link Map}of components automatically. The collection will contain 039 * all components of a special type and additionally the type of the key may be specified. In 040 * case of a map, the map's keys are the one of the component adapter. 041 * 042 * @author Aslak Hellesøy 043 * @author Jörg Schaible 044 */ 045 @SuppressWarnings("serial") 046 public class CollectionComponentParameter 047 implements Parameter, Serializable 048 { 049 050 /** Use <code>ARRAY</code> as {@link Parameter}for an Array that must have elements. */ 051 public static final CollectionComponentParameter ARRAY = new CollectionComponentParameter(); 052 /** 053 * Use <code>ARRAY_ALLOW_EMPTY</code> as {@link Parameter}for an Array that may have no 054 * elements. 055 */ 056 public static final CollectionComponentParameter ARRAY_ALLOW_EMPTY = new CollectionComponentParameter(true); 057 058 private final boolean emptyCollection; 059 private final Class componentKeyType; 060 private final Class componentValueType; 061 062 /** 063 * Expect an {@link Array}of an appropriate type as parameter. At least one component of 064 * the array's component type must exist. 065 */ 066 public CollectionComponentParameter() { 067 this(false); 068 } 069 070 /** 071 * Expect an {@link Array}of an appropriate type as parameter. 072 * 073 * @param emptyCollection <code>true</code> if an empty array also is a valid dependency 074 * resolution. 075 */ 076 public CollectionComponentParameter(boolean emptyCollection) { 077 this(Void.TYPE, emptyCollection); 078 } 079 080 /** 081 * Expect any of the collection types {@link Array},{@link Collection}or {@link Map}as 082 * parameter. 083 * 084 * @param componentValueType the type of the components (ignored in case of an Array) 085 * @param emptyCollection <code>true</code> if an empty collection resolves the 086 * dependency. 087 */ 088 public CollectionComponentParameter(Class componentValueType, boolean emptyCollection) { 089 this(Object.class, componentValueType, emptyCollection); 090 } 091 092 /** 093 * Expect any of the collection types {@link Array},{@link Collection}or {@link Map}as 094 * parameter. 095 * 096 * @param componentKeyType the type of the component's key 097 * @param componentValueType the type of the components (ignored in case of an Array) 098 * @param emptyCollection <code>true</code> if an empty collection resolves the 099 * dependency. 100 */ 101 public CollectionComponentParameter(Class componentKeyType, Class componentValueType, boolean emptyCollection) { 102 this.emptyCollection = emptyCollection; 103 this.componentKeyType = componentKeyType; 104 this.componentValueType = componentValueType; 105 } 106 107 /** 108 * Resolve the parameter for the expected type. The method will return <code>null</code> 109 * If the expected type is not one of the collection types {@link Array}, 110 * {@link Collection}or {@link Map}. An empty collection is only a valid resolution, if 111 * the <code>emptyCollection</code> flag was set. 112 * 113 * @param container {@inheritDoc} 114 * @param adapter {@inheritDoc} 115 * @param expectedType {@inheritDoc} 116 * @param expectedNameBinding {@inheritDoc} 117 * 118 * @param useNames 119 * @param binding 120 * @return the instance of the collection type or <code>null</code> 121 * 122 * @throws PicoCompositionException {@inheritDoc} 123 */ 124 @SuppressWarnings({ "unchecked" }) 125 public Object resolveInstance(PicoContainer container, 126 ComponentAdapter adapter, 127 Class expectedType, 128 NameBinding expectedNameBinding, boolean useNames, Annotation binding) 129 { 130 // type check is done in isResolvable 131 Object result = null; 132 final Class collectionType = getCollectionType(expectedType); 133 if (collectionType != null) { 134 final Map<Object, ComponentAdapter<?>> adapterMap = 135 getMatchingComponentAdapters(container, adapter, componentKeyType, getValueType(expectedType)); 136 if (Array.class.isAssignableFrom(collectionType)) { 137 result = getArrayInstance(container, expectedType, adapterMap); 138 } else if (Map.class.isAssignableFrom(collectionType)) { 139 result = getMapInstance(container, expectedType, adapterMap); 140 } else if (Collection.class.isAssignableFrom(collectionType)) { 141 result = getCollectionInstance(container, (Class<? extends Collection>)expectedType, adapterMap); 142 } else { 143 throw new PicoCompositionException(expectedType.getName() + " is not a collective type"); 144 } 145 } 146 return result; 147 } 148 149 /** 150 * Check for a successful dependency resolution of the parameter for the expected type. The 151 * dependency can only be satisfied if the expected type is one of the collection types 152 * {@link Array},{@link Collection}or {@link Map}. An empty collection is only a valid 153 * resolution, if the <code>emptyCollection</code> flag was set. 154 * 155 * @param container {@inheritDoc} 156 * @param adapter {@inheritDoc} 157 * @param expectedType {@inheritDoc} 158 * @param expectedNameBinding {@inheritDoc} 159 * 160 * @param useNames 161 * @param binding 162 * @return <code>true</code> if matching components were found or an empty collective type 163 * is allowed 164 */ 165 public boolean isResolvable(PicoContainer container, 166 ComponentAdapter adapter, 167 Class expectedType, 168 NameBinding expectedNameBinding, boolean useNames, Annotation binding) { 169 final Class collectionType = getCollectionType(expectedType); 170 final Class valueType = getValueType(expectedType); 171 return collectionType != null && (emptyCollection || getMatchingComponentAdapters(container, 172 adapter, 173 componentKeyType, 174 valueType).size() > 0); 175 } 176 177 /** 178 * Verify a successful dependency resolution of the parameter for the expected type. The 179 * method will only return if the expected type is one of the collection types {@link Array}, 180 * {@link Collection}or {@link Map}. An empty collection is only a valid resolution, if 181 * the <code>emptyCollection</code> flag was set. 182 * 183 * @param container {@inheritDoc} 184 * @param adapter {@inheritDoc} 185 * @param expectedType {@inheritDoc} 186 * @param expectedNameBinding {@inheritDoc} 187 * 188 * @param useNames 189 * @param binding 190 * @throws PicoCompositionException {@inheritDoc} 191 */ 192 public void verify(PicoContainer container, 193 ComponentAdapter adapter, 194 Class expectedType, 195 NameBinding expectedNameBinding, boolean useNames, Annotation binding) 196 { 197 final Class collectionType = getCollectionType(expectedType); 198 if (collectionType != null) { 199 final Class valueType = getValueType(expectedType); 200 final Collection componentAdapters = 201 getMatchingComponentAdapters(container, adapter, componentKeyType, valueType).values(); 202 if (componentAdapters.isEmpty()) { 203 if (!emptyCollection) { 204 throw new PicoCompositionException(expectedType.getName() 205 + " not resolvable, no components of type " 206 + getValueType(expectedType).getName() 207 + " available"); 208 } 209 } else { 210 for (Object componentAdapter1 : componentAdapters) { 211 final ComponentAdapter componentAdapter = (ComponentAdapter)componentAdapter1; 212 componentAdapter.verify(container); 213 } 214 } 215 } else { 216 throw new PicoCompositionException(expectedType.getName() + " is not a collective type"); 217 } 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 /** 230 * Evaluate whether the given component adapter will be part of the collective type. 231 * 232 * @param adapter a <code>ComponentAdapter</code> value 233 * 234 * @return <code>true</code> if the adapter takes part 235 */ 236 protected boolean evaluate(final ComponentAdapter adapter) { 237 return adapter != null; // use parameter, prevent compiler warning 238 } 239 240 /** 241 * Collect the matching ComponentAdapter instances. 242 * 243 * @param container container to use for dependency resolution 244 * @param adapter {@link ComponentAdapter} to exclude 245 * @param keyType the compatible type of the key 246 * @param valueType the compatible type of the addComponent 247 * 248 * @return a {@link Map} with the ComponentAdapter instances and their component keys as map key. 249 */ 250 @SuppressWarnings({ "unchecked" }) 251 protected Map<Object, ComponentAdapter<?>> getMatchingComponentAdapters(PicoContainer container, 252 ComponentAdapter adapter, 253 Class keyType, 254 Class valueType) 255 { 256 final Map<Object, ComponentAdapter<?>> adapterMap = new LinkedHashMap<Object, ComponentAdapter<?>>(); 257 final PicoContainer parent = container.getParent(); 258 if (parent != null) { 259 adapterMap.putAll(getMatchingComponentAdapters(parent, adapter, keyType, valueType)); 260 } 261 final Collection<ComponentAdapter<?>> allAdapters = container.getComponentAdapters(); 262 for (ComponentAdapter componentAdapter : allAdapters) { 263 adapterMap.remove(componentAdapter.getComponentKey()); 264 } 265 final List<ComponentAdapter> adapterList = container.getComponentAdapters(valueType); 266 for (ComponentAdapter componentAdapter : adapterList) { 267 final Object key = componentAdapter.getComponentKey(); 268 if (adapter != null && key.equals(adapter.getComponentKey())) { 269 continue; 270 } 271 if (keyType.isAssignableFrom(key.getClass()) && evaluate(componentAdapter)) { 272 adapterMap.put(key, componentAdapter); 273 } 274 } 275 return adapterMap; 276 } 277 278 private Class getCollectionType(final Class collectionType) { 279 Class collectionClass = null; 280 if (collectionType.isArray()) { 281 collectionClass = Array.class; 282 } else if (Map.class.isAssignableFrom(collectionType)) { 283 collectionClass = Map.class; 284 } else if (Collection.class.isAssignableFrom(collectionType)) { 285 collectionClass = Collection.class; 286 } 287 return collectionClass; 288 } 289 290 private Class getValueType(final Class collectionType) { 291 Class valueType = componentValueType; 292 if (collectionType.isArray()) { 293 valueType = collectionType.getComponentType(); 294 } 295 return valueType; 296 } 297 298 private Object[] getArrayInstance(final PicoContainer container, 299 final Class expectedType, 300 final Map<Object, ComponentAdapter<?>> adapterList) 301 { 302 final Object[] result = (Object[])Array.newInstance(expectedType.getComponentType(), adapterList.size()); 303 int i = 0; 304 for (ComponentAdapter componentAdapter : adapterList.values()) { 305 result[i] = container.getComponent(componentAdapter.getComponentKey()); 306 i++; 307 } 308 return result; 309 } 310 311 @SuppressWarnings({ "unchecked" }) 312 private Collection getCollectionInstance(final PicoContainer container, 313 final Class<? extends Collection> expectedType, 314 final Map<Object, ComponentAdapter<?>> adapterList) 315 { 316 Class<? extends Collection> collectionType = expectedType; 317 if (collectionType.isInterface()) { 318 // The order of tests are significant. The least generic types last. 319 if (List.class.isAssignableFrom(collectionType)) { 320 collectionType = ArrayList.class; 321 // } else if (BlockingQueue.class.isAssignableFrom(collectionType)) { 322 // collectionType = ArrayBlockingQueue.class; 323 // } else if (Queue.class.isAssignableFrom(collectionType)) { 324 // collectionType = LinkedList.class; 325 } else if (SortedSet.class.isAssignableFrom(collectionType)) { 326 collectionType = TreeSet.class; 327 } else if (Set.class.isAssignableFrom(collectionType)) { 328 collectionType = HashSet.class; 329 } else if (Collection.class.isAssignableFrom(collectionType)) { 330 collectionType = ArrayList.class; 331 } 332 } 333 try { 334 Collection result = collectionType.newInstance(); 335 for (ComponentAdapter componentAdapter : adapterList.values()) { 336 result.add(container.getComponent(componentAdapter.getComponentKey())); 337 } 338 return result; 339 } catch (InstantiationException e) { 340 ///CLOVER:OFF 341 throw new PicoCompositionException(e); 342 ///CLOVER:ON 343 } catch (IllegalAccessException e) { 344 ///CLOVER:OFF 345 throw new PicoCompositionException(e); 346 ///CLOVER:ON 347 } 348 } 349 350 @SuppressWarnings({ "unchecked" }) 351 private Map getMapInstance(final PicoContainer container, 352 final Class<? extends Map> expectedType, 353 final Map<Object, ComponentAdapter<?>> adapterList) 354 { 355 Class<? extends Map> collectionType = expectedType; 356 if (collectionType.isInterface()) { 357 // The order of tests are significant. The least generic types last. 358 if (SortedMap.class.isAssignableFrom(collectionType)) { 359 collectionType = TreeMap.class; 360 // } else if (ConcurrentMap.class.isAssignableFrom(collectionType)) { 361 // collectionType = ConcurrentHashMap.class; 362 } else if (Map.class.isAssignableFrom(collectionType)) { 363 collectionType = HashMap.class; 364 } 365 } 366 try { 367 Map result = collectionType.newInstance(); 368 for (Map.Entry<Object, ComponentAdapter<?>> entry : adapterList.entrySet()) { 369 final Object key = entry.getKey(); 370 result.put(key, container.getComponent(key)); 371 } 372 return result; 373 } catch (InstantiationException e) { 374 ///CLOVER:OFF 375 throw new PicoCompositionException(e); 376 ///CLOVER:ON 377 } catch (IllegalAccessException e) { 378 ///CLOVER:OFF 379 throw new PicoCompositionException(e); 380 ///CLOVER:ON 381 } 382 } 383 }