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