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