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    }