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