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 *****************************************************************************/
010package org.picocontainer.parameters;
011
012import java.io.Serializable;
013import java.lang.annotation.Annotation;
014import java.lang.reflect.Array;
015import java.lang.reflect.GenericArrayType;
016import java.lang.reflect.ParameterizedType;
017import java.lang.reflect.Type;
018import java.util.ArrayList;
019import java.util.Collection;
020import java.util.HashMap;
021import java.util.HashSet;
022import java.util.LinkedHashMap;
023import java.util.List;
024import java.util.Map;
025import java.util.Set;
026import java.util.SortedMap;
027import java.util.SortedSet;
028import java.util.TreeMap;
029import java.util.TreeSet;
030
031import org.picocontainer.ComponentAdapter;
032import org.picocontainer.NameBinding;
033import org.picocontainer.Parameter;
034import org.picocontainer.PicoCompositionException;
035import org.picocontainer.PicoContainer;
036import 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")
049public 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}