1 /*
2  *  Licensed to the Apache Software Foundation (ASF) under one or more
3  *  contributor license agreements.  See the NOTICE file distributed with
4  *  this work for additional information regarding copyright ownership.
5  *  The ASF licenses this file to You under the Apache License, Version 2.0
6  *  (the "License"); you may not use this file except in compliance with
7  *  the License.  You may obtain a copy of the License at
8  *
9  *      http://www.apache.org/licenses/LICENSE-2.0
10  *
11  *  Unless required by applicable law or agreed to in writing, software
12  *  distributed under the License is distributed on an "AS IS" BASIS,
13  *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14  *  See the License for the specific language governing permissions and
15  *  limitations under the License.
16  */
17 package org.apache.commons.collections.map;
18 
19 import java.util.AbstractCollection;
20 import java.util.ArrayList;
21 import java.util.Collection;
22 import java.util.HashMap;
23 import java.util.Iterator;
24 import java.util.Map;
25 import java.util.Set;
26 
27 import org.apache.commons.collections.Factory;
28 import org.apache.commons.collections.FunctorException;
29 import org.apache.commons.collections.MultiMap;
30 import org.apache.commons.collections.iterators.EmptyIterator;
31 import org.apache.commons.collections.iterators.IteratorChain;
32 
33 /**
34  * A MultiValueMap decorates another map, allowing it to have
35  * more than one value for a key.
36  * <p>
37  * A <code>MultiMap</code> is a Map with slightly different semantics.
38  * Putting a value into the map will add the value to a Collection at that key.
39  * Getting a value will return a Collection, holding all the values put to that key.
40  * <p>
41  * This implementation is a decorator, allowing any Map implementation
42  * to be used as the base.
43  * <p>
44  * In addition, this implementation allows the type of collection used
45  * for the values to be controlled. By default, an <code>ArrayList</code>
46  * is used, however a <code>Class</code> to instantiate may be specified,
47  * or a factory that returns a <code>Collection</code> instance.
48  * <p>
49  * <strong>Note that MultiValueMap is not synchronized and is not thread-safe.</strong>
50  * If you wish to use this map from multiple threads concurrently, you must use
51  * appropriate synchronization. This class may throw exceptions when accessed
52  * by concurrent threads without synchronization.
53  *
54  * @author James Carman
55  * @author Christopher Berry
56  * @author James Strachan
57  * @author Steve Downey
58  * @author Stephen Colebourne
59  * @author Julien Buret
60  * @author Serhiy Yevtushenko
61  * @version $Revision: 1713175 $ $Date: 2015-11-07 21:47:04 +0100 (Sat, 07 Nov 2015) $
62  * @since Commons Collections 3.2
63  */
64 public class MultiValueMap extends AbstractMapDecorator implements MultiMap {
65 
66     /** The factory for creating value collections. */
67     private final Factory collectionFactory;
68     /** The cached values. */
69     private transient Collection values;
70 
71     /**
72      * Creates a map which wraps the given map and
73      * maps keys to ArrayLists.
74      *
75      * @param map  the map to wrap
76      */
decorate(Map map)77     public static MultiValueMap decorate(Map map) {
78         return new MultiValueMap(map, new ReflectionFactory(ArrayList.class));
79     }
80 
81     /**
82      * Creates a map which decorates the given <code>map</code> and
83      * maps keys to collections of type <code>collectionClass</code>.
84      *
85      * @param map  the map to wrap
86      * @param collectionClass  the type of the collection class
87      */
decorate(Map map, Class collectionClass)88     public static MultiValueMap decorate(Map map, Class collectionClass) {
89         return new MultiValueMap(map, new ReflectionFactory(collectionClass));
90     }
91 
92     /**
93      * Creates a map which decorates the given <code>map</code> and
94      * creates the value collections using the supplied <code>collectionFactory</code>.
95      *
96      * @param map  the map to decorate
97      * @param collectionFactory  the collection factory (must return a Collection object).
98      */
decorate(Map map, Factory collectionFactory)99     public static MultiValueMap decorate(Map map, Factory collectionFactory) {
100         return new MultiValueMap(map, collectionFactory);
101     }
102 
103     //-----------------------------------------------------------------------
104     /**
105      * Creates a MultiValueMap based on a <code>HashMap</code> and
106      * storing the multiple values in an <code>ArrayList</code>.
107      */
MultiValueMap()108     public MultiValueMap() {
109         this(new HashMap(), new ReflectionFactory(ArrayList.class));
110     }
111 
112     /**
113      * Creates a MultiValueMap which decorates the given <code>map</code> and
114      * creates the value collections using the supplied <code>collectionFactory</code>.
115      *
116      * @param map  the map to decorate
117      * @param collectionFactory  the collection factory which must return a Collection instance
118      */
MultiValueMap(Map map, Factory collectionFactory)119     protected MultiValueMap(Map map, Factory collectionFactory) {
120         super(map);
121         if (collectionFactory == null) {
122             throw new IllegalArgumentException("The factory must not be null");
123         }
124         this.collectionFactory = collectionFactory;
125     }
126 
127     //-----------------------------------------------------------------------
128     /**
129      * Clear the map.
130      */
clear()131     public void clear() {
132         // If you believe that you have GC issues here, try uncommenting this code
133 //        Set pairs = getMap().entrySet();
134 //        Iterator pairsIterator = pairs.iterator();
135 //        while (pairsIterator.hasNext()) {
136 //            Map.Entry keyValuePair = (Map.Entry) pairsIterator.next();
137 //            Collection coll = (Collection) keyValuePair.getValue();
138 //            coll.clear();
139 //        }
140         getMap().clear();
141     }
142 
143     /**
144      * Removes a specific value from map.
145      * <p>
146      * The item is removed from the collection mapped to the specified key.
147      * Other values attached to that key are unaffected.
148      * <p>
149      * If the last value for a key is removed, <code>null</code> will be returned
150      * from a subsequant <code>get(key)</code>.
151      *
152      * @param key  the key to remove from
153      * @param value the value to remove
154      * @return the value removed (which was passed in), null if nothing removed
155      */
remove(Object key, Object value)156     public boolean remove(Object key, Object value) {
157         Collection valuesForKey = getCollection(key);
158         if (valuesForKey == null) {
159             return false;
160         }
161         boolean removed = valuesForKey.remove(value);
162         if (removed == false) {
163             return false;
164         }
165         if (valuesForKey.isEmpty()) {
166             remove(key);
167         }
168         return true;
169     }
170 
171     /**
172      * Checks whether the map contains the value specified.
173      * <p>
174      * This checks all collections against all keys for the value, and thus could be slow.
175      *
176      * @param value  the value to search for
177      * @return true if the map contains the value
178      */
containsValue(Object value)179     public boolean containsValue(Object value) {
180         Set pairs = getMap().entrySet();
181         if (pairs == null) {
182             return false;
183         }
184         Iterator pairsIterator = pairs.iterator();
185         while (pairsIterator.hasNext()) {
186             Map.Entry keyValuePair = (Map.Entry) pairsIterator.next();
187             Collection coll = (Collection) keyValuePair.getValue();
188             if (coll.contains(value)) {
189                 return true;
190             }
191         }
192         return false;
193     }
194 
195     /**
196      * Adds the value to the collection associated with the specified key.
197      * <p>
198      * Unlike a normal <code>Map</code> the previous value is not replaced.
199      * Instead the new value is added to the collection stored against the key.
200      *
201      * @param key  the key to store against
202      * @param value  the value to add to the collection at the key
203      * @return the value added if the map changed and null if the map did not change
204      */
put(Object key, Object value)205     public Object put(Object key, Object value) {
206         boolean result = false;
207         Collection coll = getCollection(key);
208         if (coll == null) {
209             coll = createCollection(1);
210             result = coll.add(value);
211             if (coll.size() > 0) {
212                 // only add if non-zero size to maintain class state
213                 getMap().put(key, coll);
214                 result = true;  // map definitely changed
215             }
216         } else {
217             result = coll.add(value);
218         }
219         return (result ? value : null);
220     }
221 
222     /**
223      * Override superclass to ensure that MultiMap instances are
224      * correctly handled.
225      * <p>
226      * If you call this method with a normal map, each entry is
227      * added using <code>put(Object,Object)</code>.
228      * If you call this method with a multi map, each entry is
229      * added using <code>putAll(Object,Collection)</code>.
230      *
231      * @param map  the map to copy (either a normal or multi map)
232      */
putAll(Map map)233     public void putAll(Map map) {
234         if (map instanceof MultiMap) {
235             for (Iterator it = map.entrySet().iterator(); it.hasNext();) {
236                 Map.Entry entry = (Map.Entry) it.next();
237                 Collection coll = (Collection) entry.getValue();
238                 putAll(entry.getKey(), coll);
239             }
240         } else {
241             for (Iterator it = map.entrySet().iterator(); it.hasNext();) {
242                 Map.Entry entry = (Map.Entry) it.next();
243                 put(entry.getKey(), entry.getValue());
244             }
245         }
246     }
247 
248     /**
249      * Gets a collection containing all the values in the map.
250      * <p>
251      * This returns a collection containing the combination of values from all keys.
252      *
253      * @return a collection view of the values contained in this map
254      */
values()255     public Collection values() {
256         Collection vs = values;
257         return (vs != null ? vs : (values = new Values()));
258     }
259 
260     /**
261      * Checks whether the collection at the specified key contains the value.
262      *
263      * @param value  the value to search for
264      * @return true if the map contains the value
265      */
containsValue(Object key, Object value)266     public boolean containsValue(Object key, Object value) {
267         Collection coll = getCollection(key);
268         if (coll == null) {
269             return false;
270         }
271         return coll.contains(value);
272     }
273 
274     /**
275      * Gets the collection mapped to the specified key.
276      * This method is a convenience method to typecast the result of <code>get(key)</code>.
277      *
278      * @param key  the key to retrieve
279      * @return the collection mapped to the key, null if no mapping
280      */
getCollection(Object key)281     public Collection getCollection(Object key) {
282         return (Collection) getMap().get(key);
283     }
284 
285     /**
286      * Gets the size of the collection mapped to the specified key.
287      *
288      * @param key  the key to get size for
289      * @return the size of the collection at the key, zero if key not in map
290      */
size(Object key)291     public int size(Object key) {
292         Collection coll = getCollection(key);
293         if (coll == null) {
294             return 0;
295         }
296         return coll.size();
297     }
298 
299     /**
300      * Adds a collection of values to the collection associated with
301      * the specified key.
302      *
303      * @param key  the key to store against
304      * @param values  the values to add to the collection at the key, null ignored
305      * @return true if this map changed
306      */
putAll(Object key, Collection values)307     public boolean putAll(Object key, Collection values) {
308         if (values == null || values.size() == 0) {
309             return false;
310         }
311         boolean result = false;
312         Collection coll = getCollection(key);
313         if (coll == null) {
314             coll = createCollection(values.size());
315             coll.addAll(values);
316             if (coll.size() > 0) {
317                 // only add if non-zero size to maintain class state
318                 getMap().put(key, coll);
319                 result = true;  // map definitely changed
320             }
321         } else {
322             result = coll.addAll(values);
323         }
324         return result;
325     }
326 
327     /**
328      * Gets an iterator for the collection mapped to the specified key.
329      *
330      * @param key  the key to get an iterator for
331      * @return the iterator of the collection at the key, empty iterator if key not in map
332      */
iterator(Object key)333     public Iterator iterator(Object key) {
334         if (!containsKey(key)) {
335             return EmptyIterator.INSTANCE;
336         } else {
337             return new ValuesIterator(key);
338         }
339     }
340 
341     /**
342      * Gets the total size of the map by counting all the values.
343      *
344      * @return the total size of the map counting all values
345      */
totalSize()346     public int totalSize() {
347         int total = 0;
348         Collection values = getMap().values();
349         for (Iterator it = values.iterator(); it.hasNext();) {
350             Collection coll = (Collection) it.next();
351             total += coll.size();
352         }
353         return total;
354     }
355 
356     /**
357      * Creates a new instance of the map value Collection container
358      * using the factory.
359      * <p>
360      * This method can be overridden to perform your own processing
361      * instead of using the factory.
362      *
363      * @param size  the collection size that is about to be added
364      * @return the new collection
365      */
createCollection(int size)366     protected Collection createCollection(int size) {
367         return (Collection) collectionFactory.create();
368     }
369 
370     //-----------------------------------------------------------------------
371     /**
372      * Inner class that provides the values view.
373      */
374     private class Values extends AbstractCollection {
iterator()375         public Iterator iterator() {
376             final IteratorChain chain = new IteratorChain();
377             for (Iterator it = keySet().iterator(); it.hasNext();) {
378                 chain.addIterator(new ValuesIterator(it.next()));
379             }
380             return chain;
381         }
382 
size()383         public int size() {
384             return totalSize();
385         }
386 
clear()387         public void clear() {
388             MultiValueMap.this.clear();
389         }
390     }
391 
392     /**
393      * Inner class that provides the values iterator.
394      */
395     private class ValuesIterator implements Iterator {
396         private final Object key;
397         private final Collection values;
398         private final Iterator iterator;
399 
ValuesIterator(Object key)400         public ValuesIterator(Object key) {
401             this.key = key;
402             this.values = getCollection(key);
403             this.iterator = values.iterator();
404         }
405 
remove()406         public void remove() {
407             iterator.remove();
408             if (values.isEmpty()) {
409                 MultiValueMap.this.remove(key);
410             }
411         }
412 
hasNext()413         public boolean hasNext() {
414             return iterator.hasNext();
415         }
416 
next()417         public Object next() {
418             return iterator.next();
419         }
420     }
421 
422     /**
423      * Inner class that provides a simple reflection factory.
424      */
425     private static class ReflectionFactory implements Factory {
426         private final Class clazz;
427 
ReflectionFactory(Class clazz)428         public ReflectionFactory(Class clazz) {
429             this.clazz = clazz;
430         }
431 
create()432         public Object create() {
433             try {
434                 return clazz.newInstance();
435             } catch (Exception ex) {
436                 throw new FunctorException("Cannot instantiate class: " + clazz, ex);
437             }
438         }
439     }
440 
441 }
442