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.ParameterName;
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.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 /**
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 */
044 public class CollectionComponentParameter
045 implements Parameter, Serializable
046 {
047
048 /** Use <code>ARRAY</code> as {@link Parameter}for an Array that must have elements. */
049 public static final CollectionComponentParameter ARRAY = new CollectionComponentParameter();
050 /**
051 * Use <code>ARRAY_ALLOW_EMPTY</code> as {@link Parameter}for an Array that may have no
052 * elements.
053 */
054 public static final CollectionComponentParameter ARRAY_ALLOW_EMPTY = new CollectionComponentParameter(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, boolean emptyCollection) {
087 this(Object.class, componentValueType, emptyCollection);
088 }
089
090 /**
091 * Expect any of the collection types {@link Array},{@link Collection}or {@link Map}as
092 * parameter.
093 *
094 * @param componentKeyType the type of the component's key
095 * @param componentValueType the type of the components (ignored in case of an Array)
096 * @param emptyCollection <code>true</code> if an empty collection resolves the
097 * dependency.
098 */
099 public CollectionComponentParameter(Class componentKeyType, Class componentValueType, boolean emptyCollection) {
100 this.emptyCollection = emptyCollection;
101 this.componentKeyType = componentKeyType;
102 this.componentValueType = componentValueType;
103 }
104
105 /**
106 * Resolve the parameter for the expected type. The method will return <code>null</code>
107 * If the expected type is not one of the collection types {@link Array},
108 * {@link Collection}or {@link Map}. An empty collection is only a valid resolution, if
109 * the <code>emptyCollection</code> flag was set.
110 *
111 * @param container {@inheritDoc}
112 * @param adapter {@inheritDoc}
113 * @param expectedType {@inheritDoc}
114 * @param expectedParameterName {@inheritDoc}
115 *
116 * @return the instance of the collection type or <code>null</code>
117 *
118 * @throws PicoCompositionException {@inheritDoc}
119 */
120 @SuppressWarnings({ "unchecked" })
121 public Object resolveInstance(PicoContainer container,
122 ComponentAdapter adapter,
123 Class expectedType,
124 ParameterName expectedParameterName)
125 {
126 // type check is done in isResolvable
127 Object result = null;
128 final Class collectionType = getCollectionType(expectedType);
129 if (collectionType != null) {
130 final Map<Object, ComponentAdapter<?>> adapterMap =
131 getMatchingComponentAdapters(container, adapter, componentKeyType, getValueType(expectedType));
132 if (Array.class.isAssignableFrom(collectionType)) {
133 result = getArrayInstance(container, expectedType, adapterMap);
134 } else if (Map.class.isAssignableFrom(collectionType)) {
135 result = getMapInstance(container, expectedType, adapterMap);
136 } else if (Collection.class.isAssignableFrom(collectionType)) {
137 result = getCollectionInstance(container, (Class<? extends Collection>)expectedType, adapterMap);
138 } else {
139 throw new PicoCompositionException(expectedType.getName() + " is not a collective type");
140 }
141 }
142 return result;
143 }
144
145 /**
146 * Check for a successful dependency resolution of the parameter for the expected type. The
147 * dependency can only be satisfied if the expected type is one of the collection types
148 * {@link Array},{@link Collection}or {@link Map}. An empty collection is only a valid
149 * resolution, if the <code>emptyCollection</code> flag was set.
150 *
151 * @param container {@inheritDoc}
152 * @param adapter {@inheritDoc}
153 * @param expectedType {@inheritDoc}
154 * @param expectedParameterName {@inheritDoc}
155 *
156 * @return <code>true</code> if matching components were found or an empty collective type
157 * is allowed
158 */
159 public boolean isResolvable(PicoContainer container,
160 ComponentAdapter adapter,
161 Class expectedType,
162 ParameterName expectedParameterName)
163 {
164 final Class collectionType = getCollectionType(expectedType);
165 final Class valueType = getValueType(expectedType);
166 return collectionType != null && (emptyCollection || getMatchingComponentAdapters(container,
167 adapter,
168 componentKeyType,
169 valueType).size() > 0);
170 }
171
172 /**
173 * Verify a successful dependency resolution of the parameter for the expected type. The
174 * method will only return if the expected type is one of the collection types {@link Array},
175 * {@link Collection}or {@link Map}. An empty collection is only a valid resolution, if
176 * the <code>emptyCollection</code> flag was set.
177 *
178 * @param container {@inheritDoc}
179 * @param adapter {@inheritDoc}
180 * @param expectedType {@inheritDoc}
181 * @param expectedParameterName {@inheritDoc}
182 *
183 * @throws PicoCompositionException {@inheritDoc}
184 */
185 public void verify(PicoContainer container,
186 ComponentAdapter adapter,
187 Class expectedType,
188 ParameterName expectedParameterName)
189 {
190 final Class collectionType = getCollectionType(expectedType);
191 if (collectionType != null) {
192 final Class valueType = getValueType(expectedType);
193 final Collection componentAdapters =
194 getMatchingComponentAdapters(container, adapter, componentKeyType, valueType).values();
195 if (componentAdapters.isEmpty()) {
196 if (!emptyCollection) {
197 throw new PicoCompositionException(expectedType.getName()
198 + " not resolvable, no components of type "
199 + getValueType(expectedType).getName()
200 + " available");
201 }
202 } else {
203 for (Object componentAdapter1 : componentAdapters) {
204 final ComponentAdapter componentAdapter = (ComponentAdapter)componentAdapter1;
205 componentAdapter.verify(container);
206 }
207 }
208 } else {
209 throw new PicoCompositionException(expectedType.getName() + " is not a collective type");
210 }
211 }
212
213 /**
214 * Visit the current {@link Parameter}.
215 *
216 * @see org.picocontainer.Parameter#accept(org.picocontainer.PicoVisitor)
217 */
218 public void accept(final PicoVisitor visitor) {
219 visitor.visitParameter(this);
220 }
221
222 /**
223 * Evaluate whether the given component adapter will be part of the collective type.
224 *
225 * @param adapter a <code>ComponentAdapter</code> value
226 *
227 * @return <code>true</code> if the adapter takes part
228 */
229 protected boolean evaluate(final ComponentAdapter adapter) {
230 return adapter != null; // use parameter, prevent compiler warning
231 }
232
233 /**
234 * Collect the matching ComponentAdapter instances.
235 *
236 * @param container container to use for dependency resolution
237 * @param adapter {@link ComponentAdapter} to exclude
238 * @param keyType the compatible type of the key
239 * @param valueType the compatible type of the addComponent
240 *
241 * @return a {@link Map} with the ComponentAdapter instances and their component keys as map key.
242 */
243 @SuppressWarnings({ "unchecked" })
244 protected Map<Object, ComponentAdapter<?>> getMatchingComponentAdapters(PicoContainer container,
245 ComponentAdapter adapter,
246 Class keyType,
247 Class valueType)
248 {
249 final Map<Object, ComponentAdapter<?>> adapterMap = new LinkedHashMap<Object, ComponentAdapter<?>>();
250 final PicoContainer parent = container.getParent();
251 if (parent != null) {
252 adapterMap.putAll(getMatchingComponentAdapters(parent, adapter, keyType, valueType));
253 }
254 final Collection<ComponentAdapter<?>> allAdapters = container.getComponentAdapters();
255 for (ComponentAdapter componentAdapter : allAdapters) {
256 adapterMap.remove(componentAdapter.getComponentKey());
257 }
258 final List<ComponentAdapter> adapterList = container.getComponentAdapters(valueType);
259 for (ComponentAdapter componentAdapter : adapterList) {
260 final Object key = componentAdapter.getComponentKey();
261 if (adapter != null && key.equals(adapter.getComponentKey())) {
262 continue;
263 }
264 if (keyType.isAssignableFrom(key.getClass()) && evaluate(componentAdapter)) {
265 adapterMap.put(key, componentAdapter);
266 }
267 }
268 return adapterMap;
269 }
270
271 private Class getCollectionType(final Class collectionType) {
272 Class collectionClass = null;
273 if (collectionType.isArray()) {
274 collectionClass = Array.class;
275 } else if (Map.class.isAssignableFrom(collectionType)) {
276 collectionClass = Map.class;
277 } else if (Collection.class.isAssignableFrom(collectionType)) {
278 collectionClass = Collection.class;
279 }
280 return collectionClass;
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 Object[] getArrayInstance(final PicoContainer container,
292 final Class expectedType,
293 final Map<Object, ComponentAdapter<?>> adapterList)
294 {
295 final Object[] result = (Object[])Array.newInstance(expectedType.getComponentType(), adapterList.size());
296 int i = 0;
297 for (ComponentAdapter componentAdapter : adapterList.values()) {
298 result[i] = container.getComponent(componentAdapter.getComponentKey());
299 i++;
300 }
301 return result;
302 }
303
304 @SuppressWarnings({ "unchecked" })
305 private Collection getCollectionInstance(final PicoContainer container,
306 final Class<? extends Collection> expectedType,
307 final Map<Object, ComponentAdapter<?>> adapterList)
308 {
309 Class<? extends Collection> collectionType = expectedType;
310 if (collectionType.isInterface()) {
311 // The order of tests are significant. The least generic types last.
312 if (List.class.isAssignableFrom(collectionType)) {
313 collectionType = ArrayList.class;
314 // } else if (BlockingQueue.class.isAssignableFrom(collectionType)) {
315 // collectionType = ArrayBlockingQueue.class;
316 // } else if (Queue.class.isAssignableFrom(collectionType)) {
317 // collectionType = LinkedList.class;
318 } else if (SortedSet.class.isAssignableFrom(collectionType)) {
319 collectionType = TreeSet.class;
320 } else if (Set.class.isAssignableFrom(collectionType)) {
321 collectionType = HashSet.class;
322 } else if (Collection.class.isAssignableFrom(collectionType)) {
323 collectionType = ArrayList.class;
324 }
325 }
326 try {
327 Collection result = collectionType.newInstance();
328 for (ComponentAdapter componentAdapter : adapterList.values()) {
329 result.add(container.getComponent(componentAdapter.getComponentKey()));
330 }
331 return result;
332 } catch (InstantiationException e) {
333 ///CLOVER:OFF
334 throw new PicoCompositionException(e);
335 ///CLOVER:ON
336 } catch (IllegalAccessException e) {
337 ///CLOVER:OFF
338 throw new PicoCompositionException(e);
339 ///CLOVER:ON
340 }
341 }
342
343 @SuppressWarnings({ "unchecked" })
344 private Map getMapInstance(final PicoContainer container,
345 final Class<? extends Map> expectedType,
346 final Map<Object, ComponentAdapter<?>> adapterList)
347 {
348 Class<? extends Map> collectionType = expectedType;
349 if (collectionType.isInterface()) {
350 // The order of tests are significant. The least generic types last.
351 if (SortedMap.class.isAssignableFrom(collectionType)) {
352 collectionType = TreeMap.class;
353 // } else if (ConcurrentMap.class.isAssignableFrom(collectionType)) {
354 // collectionType = ConcurrentHashMap.class;
355 } else if (Map.class.isAssignableFrom(collectionType)) {
356 collectionType = HashMap.class;
357 }
358 }
359 try {
360 Map result = collectionType.newInstance();
361 for (Map.Entry<Object, ComponentAdapter<?>> entry : adapterList.entrySet()) {
362 final Object key = entry.getKey();
363 result.put(key, container.getComponent(key));
364 }
365 return result;
366 } catch (InstantiationException e) {
367 ///CLOVER:OFF
368 throw new PicoCompositionException(e);
369 ///CLOVER:ON
370 } catch (IllegalAccessException e) {
371 ///CLOVER:OFF
372 throw new PicoCompositionException(e);
373 ///CLOVER:ON
374 }
375 }
376 }