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