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