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