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