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