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 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 * Resolve the parameter for the expected type. The method will return <code>null</code>
111 * If the expected type is not one of the collection types {@link Array},
112 * {@link Collection}or {@link Map}. An empty collection is only a valid resolution, if
113 * the <code>emptyCollection</code> flag was set.
114 *
115 * @param container {@inheritDoc}
116 * @param adapter {@inheritDoc}
117 * @param expectedType {@inheritDoc}
118 * @param expectedNameBinding {@inheritDoc}
119 * @param useNames
120 * @param binding
121 * @return the instance of the collection type or <code>null</code>
122 * @throws PicoCompositionException {@inheritDoc}
123 */
124 @SuppressWarnings({"unchecked"})
125 public Object resolveInstance(PicoContainer container,
126 ComponentAdapter<?> adapter,
127 Type expectedType, NameBinding expectedNameBinding,
128 boolean useNames, Annotation binding) {
129 // type check is done in isResolvable
130 Object result = null;
131 final Class collectionType = getCollectionType(expectedType);
132 if (collectionType != null) {
133 final Map<Object, ComponentAdapter<?>> adapterMap =
134 getMatchingComponentAdapters(container, adapter, componentKeyType, getValueType(expectedType));
135 if (collectionType.isArray()) {
136 result = getArrayInstance(container, collectionType, adapterMap);
137 } else if (Map.class.isAssignableFrom(collectionType)) {
138 result = getMapInstance(container, collectionType, adapterMap);
139 } else if (Collection.class.isAssignableFrom(collectionType)) {
140 result = getCollectionInstance(container, (Class<? extends Collection>) collectionType, adapterMap);
141 } else {
142 throw new PicoCompositionException(expectedType + " is not a collective type");
143 }
144 }
145 return result;
146 }
147
148 /**
149 * Check for a successful dependency resolution of the parameter for the expected type. The
150 * dependency can only be satisfied if the expected type is one of the collection types
151 * {@link Array},{@link Collection}or {@link Map}. An empty collection is only a valid
152 * resolution, if the <code>emptyCollection</code> flag was set.
153 *
154 * @param container {@inheritDoc}
155 * @param adapter {@inheritDoc}
156 * @param expectedType {@inheritDoc}
157 * @param expectedNameBinding {@inheritDoc}
158 * @param useNames
159 * @param binding
160 * @return <code>true</code> if matching components were found or an empty collective type
161 * is allowed
162 */
163 public boolean isResolvable(PicoContainer container, ComponentAdapter<?> adapter,
164 Type expectedType, NameBinding expectedNameBinding,
165 boolean useNames, Annotation binding) {
166 return getCollectionType(expectedType) != null &&
167 (emptyCollection || getMatchingComponentAdapters(container, adapter,
168 componentKeyType, getValueType(expectedType)).size() > 0);
169 }
170
171 private Class getCollectionType(Type expectedType) {
172 if (expectedType instanceof Class) {
173 return getCollectionType((Class) expectedType);
174 } else if (expectedType instanceof ParameterizedType) {
175 ParameterizedType type = (ParameterizedType) expectedType;
176
177 return getCollectionType(type.getRawType());
178 }
179
180 throw new IllegalArgumentException("Unable to get collection type from " + expectedType);
181 }
182
183 /**
184 * Verify a successful dependency resolution of the parameter for the expected type. The
185 * method will only return if the expected type is one of the collection types {@link Array},
186 * {@link Collection}or {@link Map}. An empty collection is only a valid resolution, if
187 * the <code>emptyCollection</code> flag was set.
188 *
189 * @param container {@inheritDoc}
190 * @param adapter {@inheritDoc}
191 * @param expectedType {@inheritDoc}
192 * @param expectedNameBinding {@inheritDoc}
193 * @param useNames
194 * @param binding
195 * @throws PicoCompositionException {@inheritDoc}
196 */
197 public void verify(PicoContainer container,
198 ComponentAdapter<?> adapter,
199 Type expectedType,
200 NameBinding expectedNameBinding, boolean useNames, Annotation binding) {
201 final Class collectionType = getCollectionType(expectedType);
202 if (collectionType != null) {
203 final Class valueType = getValueType(expectedType);
204 final Collection componentAdapters =
205 getMatchingComponentAdapters(container, adapter, componentKeyType, valueType).values();
206 if (componentAdapters.isEmpty()) {
207 if (!emptyCollection) {
208 throw new PicoCompositionException(expectedType
209 + " not resolvable, no components of type "
210 + valueType.getName()
211 + " available");
212 }
213 } else {
214 for (Object componentAdapter1 : componentAdapters) {
215 final ComponentAdapter componentAdapter = (ComponentAdapter) componentAdapter1;
216 componentAdapter.verify(container);
217 }
218 }
219 } else {
220 throw new PicoCompositionException(expectedType + " is not a collective type");
221 }
222 }
223
224 /**
225 * Visit the current {@link Parameter}.
226 *
227 * @see org.picocontainer.Parameter#accept(org.picocontainer.PicoVisitor)
228 */
229 public void accept(final PicoVisitor visitor) {
230 visitor.visitParameter(this);
231 }
232
233 /**
234 * Evaluate whether the given component adapter will be part of the collective type.
235 *
236 * @param adapter a <code>ComponentAdapter</code> value
237 * @return <code>true</code> if the adapter takes part
238 */
239 protected boolean evaluate(final ComponentAdapter adapter) {
240 return adapter != null; // use parameter, prevent compiler warning
241 }
242
243 /**
244 * Collect the matching ComponentAdapter instances.
245 *
246 * @param container container to use for dependency resolution
247 * @param adapter {@link ComponentAdapter} to exclude
248 * @param keyType the compatible type of the key
249 * @param valueType the compatible type of the addComponent
250 * @return a {@link Map} with the ComponentAdapter instances and their component keys as map key.
251 */
252 @SuppressWarnings({"unchecked"})
253 protected Map<Object, ComponentAdapter<?>>
254 getMatchingComponentAdapters(PicoContainer container, ComponentAdapter adapter,
255 Class keyType, Class valueType) {
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 if (collectionType.isArray() ||
280 Map.class.isAssignableFrom(collectionType) ||
281 Collection.class.isAssignableFrom(collectionType)) {
282 return collectionType;
283 }
284
285 return null;
286 }
287
288 private Class getValueType(Type collectionType) {
289 if (collectionType instanceof Class) {
290 return getValueType((Class) collectionType);
291 } else if (collectionType instanceof ParameterizedType) {
292 return getValueType((ParameterizedType) collectionType); }
293 throw new IllegalArgumentException("Unable to determine collection type from " + collectionType);
294 }
295
296 private Class getValueType(final Class collectionType) {
297 Class valueType = componentValueType;
298 if (collectionType.isArray()) {
299 valueType = collectionType.getComponentType();
300 }
301 return valueType;
302 }
303
304 private Class getValueType(final ParameterizedType collectionType) {
305 Class valueType = componentValueType;
306 if (Collection.class.isAssignableFrom((Class<?>) collectionType.getRawType())) {
307 Type type = collectionType.getActualTypeArguments()[0];
308 if (type instanceof Class) {
309 if (((Class)type).isAssignableFrom(valueType)) {
310 return valueType;
311 }
312 valueType = (Class) type;
313 }
314 }
315 return valueType;
316 }
317
318 private Object[] getArrayInstance(final PicoContainer container,
319 final Class expectedType,
320 final Map<Object, ComponentAdapter<?>> adapterList) {
321 final Object[] result = (Object[]) Array.newInstance(expectedType.getComponentType(), adapterList.size());
322 int i = 0;
323 for (ComponentAdapter componentAdapter : adapterList.values()) {
324 result[i] = container.getComponent(componentAdapter.getComponentKey());
325 i++;
326 }
327 return result;
328 }
329
330 @SuppressWarnings({"unchecked"})
331 private Collection getCollectionInstance(final PicoContainer container,
332 final Class<? extends Collection> expectedType,
333 final Map<Object, ComponentAdapter<?>> adapterList) {
334 Class<? extends Collection> collectionType = expectedType;
335 if (collectionType.isInterface()) {
336 // The order of tests are significant. The least generic types last.
337 if (List.class.isAssignableFrom(collectionType)) {
338 collectionType = ArrayList.class;
339 // } else if (BlockingQueue.class.isAssignableFrom(collectionType)) {
340 // collectionType = ArrayBlockingQueue.class;
341 // } else if (Queue.class.isAssignableFrom(collectionType)) {
342 // collectionType = LinkedList.class;
343 } else if (SortedSet.class.isAssignableFrom(collectionType)) {
344 collectionType = TreeSet.class;
345 } else if (Set.class.isAssignableFrom(collectionType)) {
346 collectionType = HashSet.class;
347 } else if (Collection.class.isAssignableFrom(collectionType)) {
348 collectionType = ArrayList.class;
349 }
350 }
351 try {
352 Collection result = collectionType.newInstance();
353 for (ComponentAdapter componentAdapter : adapterList.values()) {
354 result.add(container.getComponent(componentAdapter.getComponentKey()));
355 }
356 return result;
357 } catch (InstantiationException e) {
358 ///CLOVER:OFF
359 throw new PicoCompositionException(e);
360 ///CLOVER:ON
361 } catch (IllegalAccessException e) {
362 ///CLOVER:OFF
363 throw new PicoCompositionException(e);
364 ///CLOVER:ON
365 }
366 }
367
368 @SuppressWarnings({"unchecked"})
369 private Map getMapInstance(final PicoContainer container,
370 final Class<? extends Map> expectedType,
371 final Map<Object, ComponentAdapter<?>> adapterList) {
372 Class<? extends Map> collectionType = expectedType;
373 if (collectionType.isInterface()) {
374 // The order of tests are significant. The least generic types last.
375 if (SortedMap.class.isAssignableFrom(collectionType)) {
376 collectionType = TreeMap.class;
377 // } else if (ConcurrentMap.class.isAssignableFrom(collectionType)) {
378 // collectionType = ConcurrentHashMap.class;
379 } else if (Map.class.isAssignableFrom(collectionType)) {
380 collectionType = HashMap.class;
381 }
382 }
383 try {
384 Map result = collectionType.newInstance();
385 for (Map.Entry<Object, ComponentAdapter<?>> entry : adapterList.entrySet()) {
386 final Object key = entry.getKey();
387 result.put(key, container.getComponent(key));
388 }
389 return result;
390 } catch (InstantiationException e) {
391 ///CLOVER:OFF
392 throw new PicoCompositionException(e);
393 ///CLOVER:ON
394 } catch (IllegalAccessException e) {
395 ///CLOVER:OFF
396 throw new PicoCompositionException(e);
397 ///CLOVER:ON
398 }
399 }
400 }