KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > apache > commons > collections > map > MultiValueMap


1 /*
2  * Copyright 2001-2005 The Apache Software Foundation
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */

16 package org.apache.commons.collections.map;
17
18 import java.util.AbstractCollection JavaDoc;
19 import java.util.ArrayList JavaDoc;
20 import java.util.Collection JavaDoc;
21 import java.util.HashMap JavaDoc;
22 import java.util.Iterator JavaDoc;
23 import java.util.Map JavaDoc;
24 import java.util.Set JavaDoc;
25
26 import org.apache.commons.collections.Factory;
27 import org.apache.commons.collections.FunctorException;
28 import org.apache.commons.collections.MultiMap;
29 import org.apache.commons.collections.iterators.EmptyIterator;
30 import org.apache.commons.collections.iterators.IteratorChain;
31
32 /**
33  * A MultiValueMap decorates another map, allowing it to have
34  * more than one value for a key.
35  * <p>
36  * A <code>MultiMap</code> is a Map with slightly different semantics.
37  * Putting a value into the map will add the value to a Collection at that key.
38  * Getting a value will return a Collection, holding all the values put to that key.
39  * <p>
40  * This implementation is a decorator, allowing any Map implementation
41  * to be used as the base.
42  * <p>
43  * In addition, this implementation allows the type of collection used
44  * for the values to be controlled. By default, an <code>ArrayList</code>
45  * is used, however a <code>Class</code> to instantiate may be specified,
46  * or a factory that returns a <code>Collection</code> instance.
47  * <p>
48  * <strong>Note that MultiValueMap is not synchronized and is not thread-safe.</strong>
49  * If you wish to use this map from multiple threads concurrently, you must use
50  * appropriate synchronization. This class may throw exceptions when accessed
51  * by concurrent threads without synchronization.
52  *
53  * @author James Carman
54  * @author Christopher Berry
55  * @author James Strachan
56  * @author Steve Downey
57  * @author Stephen Colebourne
58  * @author Julien Buret
59  * @author Serhiy Yevtushenko
60  * @version $Revision: 348007 $ $Date: 2005-11-21 22:52:57 +0000 (Mon, 21 Nov 2005) $
61  * @since Commons Collections 3.2
62  */

63 public class MultiValueMap extends AbstractMapDecorator implements MultiMap {
64
65     /** The factory for creating value collections. */
66     private final Factory collectionFactory;
67     /** The cached values. */
68     private transient Collection JavaDoc values;
69
70     /**
71      * Creates a map which wraps the given map and
72      * maps keys to ArrayLists.
73      *
74      * @param map the map to wrap
75      */

76     public static MultiValueMap decorate(Map JavaDoc map) {
77         return new MultiValueMap(map, new ReflectionFactory(ArrayList JavaDoc.class));
78     }
79
80     /**
81      * Creates a map which decorates the given <code>map</code> and
82      * maps keys to collections of type <code>collectionClass</code>.
83      *
84      * @param map the map to wrap
85      * @param collectionClass the type of the collection class
86      */

87     public static MultiValueMap decorate(Map JavaDoc map, Class JavaDoc collectionClass) {
88         return new MultiValueMap(map, new ReflectionFactory(collectionClass));
89     }
90
91     /**
92      * Creates a map which decorates the given <code>map</code> and
93      * creates the value collections using the supplied <code>collectionFactory</code>.
94      *
95      * @param map the map to decorate
96      * @param collectionFactory the collection factory (must return a Collection object).
97      */

98     public static MultiValueMap decorate(Map JavaDoc map, Factory collectionFactory) {
99         return new MultiValueMap(map, collectionFactory);
100     }
101
102     //-----------------------------------------------------------------------
103
/**
104      * Creates a MultiValueMap based on a <code>HashMap</code> and
105      * storing the multiple values in an <code>ArrayList</code>.
106      */

107     public MultiValueMap() {
108         this(new HashMap JavaDoc(), new ReflectionFactory(ArrayList JavaDoc.class));
109     }
110
111     /**
112      * Creates a MultiValueMap which decorates the given <code>map</code> and
113      * creates the value collections using the supplied <code>collectionFactory</code>.
114      *
115      * @param map the map to decorate
116      * @param collectionFactory the collection factory which must return a Collection instance
117      */

118     protected MultiValueMap(Map JavaDoc map, Factory collectionFactory) {
119         super(map);
120         if (collectionFactory == null) {
121             throw new IllegalArgumentException JavaDoc("The factory must not be null");
122         }
123         this.collectionFactory = collectionFactory;
124     }
125
126     //-----------------------------------------------------------------------
127
/**
128      * Clear the map.
129      */

130     public void clear() {
131         // If you believe that you have GC issues here, try uncommenting this code
132
// Set pairs = getMap().entrySet();
133
// Iterator pairsIterator = pairs.iterator();
134
// while (pairsIterator.hasNext()) {
135
// Map.Entry keyValuePair = (Map.Entry) pairsIterator.next();
136
// Collection coll = (Collection) keyValuePair.getValue();
137
// coll.clear();
138
// }
139
getMap().clear();
140     }
141
142     /**
143      * Removes a specific value from map.
144      * <p>
145      * The item is removed from the collection mapped to the specified key.
146      * Other values attached to that key are unaffected.
147      * <p>
148      * If the last value for a key is removed, <code>null</code> will be returned
149      * from a subsequant <code>get(key)</code>.
150      *
151      * @param key the key to remove from
152      * @param value the value to remove
153      * @return the value removed (which was passed in), null if nothing removed
154      */

155     public Object JavaDoc remove(Object JavaDoc key, Object JavaDoc value) {
156         Collection JavaDoc valuesForKey = getCollection(key);
157         if (valuesForKey == null) {
158             return null;
159         }
160         boolean removed = valuesForKey.remove(value);
161         if (removed == false) {
162             return null;
163         }
164         if (valuesForKey.isEmpty()) {
165             remove(key);
166         }
167         return value;
168     }
169
170     /**
171      * Checks whether the map contains the value specified.
172      * <p>
173      * This checks all collections against all keys for the value, and thus could be slow.
174      *
175      * @param value the value to search for
176      * @return true if the map contains the value
177      */

178     public boolean containsValue(Object JavaDoc value) {
179         Set JavaDoc pairs = getMap().entrySet();
180         if (pairs == null) {
181             return false;
182         }
183         Iterator JavaDoc pairsIterator = pairs.iterator();
184         while (pairsIterator.hasNext()) {
185             Map.Entry JavaDoc keyValuePair = (Map.Entry JavaDoc) pairsIterator.next();
186             Collection JavaDoc coll = (Collection JavaDoc) keyValuePair.getValue();
187             if (coll.contains(value)) {
188                 return true;
189             }
190         }
191         return false;
192     }
193
194     /**
195      * Adds the value to the collection associated with the specified key.
196      * <p>
197      * Unlike a normal <code>Map</code> the previous value is not replaced.
198      * Instead the new value is added to the collection stored against the key.
199      *
200      * @param key the key to store against
201      * @param value the value to add to the collection at the key
202      * @return the value added if the map changed and null if the map did not change
203      */

204     public Object JavaDoc put(Object JavaDoc key, Object JavaDoc value) {
205         boolean result = false;
206         Collection JavaDoc coll = getCollection(key);
207         if (coll == null) {
208             coll = createCollection(1);
209             result = coll.add(value);
210             if (coll.size() > 0) {
211                 // only add if non-zero size to maintain class state
212
getMap().put(key, coll);
213                 result = false;
214             }
215         } else {
216             result = coll.add(value);
217         }
218         return (result ? value : null);
219     }
220
221     /**
222      * Override superclass to ensure that MultiMap instances are
223      * correctly handled.
224      * <p>
225      * If you call this method with a normal map, each entry is
226      * added using <code>put(Object,Object)</code>.
227      * If you call this method with a multi map, each entry is
228      * added using <code>putAll(Object,Collection)</code>.
229      *
230      * @param map the map to copy (either a normal or multi map)
231      */

232     public void putAll(Map JavaDoc map) {
233         if (map instanceof MultiMap) {
234             for (Iterator JavaDoc it = map.entrySet().iterator(); it.hasNext();) {
235                 Map.Entry JavaDoc entry = (Map.Entry JavaDoc) it.next();
236                 Collection JavaDoc coll = (Collection JavaDoc) entry.getValue();
237                 putAll(entry.getKey(), coll);
238             }
239         } else {
240             for (Iterator JavaDoc it = map.entrySet().iterator(); it.hasNext();) {
241                 Map.Entry JavaDoc entry = (Map.Entry JavaDoc) it.next();
242                 put(entry.getKey(), entry.getValue());
243             }
244         }
245     }
246
247     /**
248      * Gets a collection containing all the values in the map.
249      * <p>
250      * This returns a collection containing the combination of values from all keys.
251      *
252      * @return a collection view of the values contained in this map
253      */

254     public Collection JavaDoc values() {
255         Collection JavaDoc vs = values;
256         return (vs != null ? vs : (values = new Values()));
257     }
258
259     /**
260      * Checks whether the collection at the specified key contains the value.
261      *
262      * @param value the value to search for
263      * @return true if the map contains the value
264      */

265     public boolean containsValue(Object JavaDoc key, Object JavaDoc value) {
266         Collection JavaDoc coll = getCollection(key);
267         if (coll == null) {
268             return false;
269         }
270         return coll.contains(value);
271     }
272
273     /**
274      * Gets the collection mapped to the specified key.
275      * This method is a convenience method to typecast the result of <code>get(key)</code>.
276      *
277      * @param key the key to retrieve
278      * @return the collection mapped to the key, null if no mapping
279      */

280     public Collection JavaDoc getCollection(Object JavaDoc key) {
281         return (Collection JavaDoc) getMap().get(key);
282     }
283
284     /**
285      * Gets the size of the collection mapped to the specified key.
286      *
287      * @param key the key to get size for
288      * @return the size of the collection at the key, zero if key not in map
289      */

290     public int size(Object JavaDoc key) {
291         Collection JavaDoc coll = getCollection(key);
292         if (coll == null) {
293             return 0;
294         }
295         return coll.size();
296     }
297
298     /**
299      * Adds a collection of values to the collection associated with
300      * the specified key.
301      *
302      * @param key the key to store against
303      * @param values the values to add to the collection at the key, null ignored
304      * @return true if this map changed
305      */

306     public boolean putAll(Object JavaDoc key, Collection JavaDoc values) {
307         if (values == null || values.size() == 0) {
308             return false;
309         }
310         Collection JavaDoc coll = getCollection(key);
311         if (coll == null) {
312             coll = createCollection(values.size());
313             boolean result = coll.addAll(values);
314             if (coll.size() > 0) {
315                 // only add if non-zero size to maintain class state
316
getMap().put(key, coll);
317                 result = false;
318             }
319             return result;
320         } else {
321             return coll.addAll(values);
322         }
323     }
324
325     /**
326      * Gets an iterator for the collection mapped to the specified key.
327      *
328      * @param key the key to get an iterator for
329      * @return the iterator of the collection at the key, empty iterator if key not in map
330      */

331     public Iterator JavaDoc iterator(Object JavaDoc key) {
332         if (!containsKey(key)) {
333             return EmptyIterator.INSTANCE;
334         } else {
335             return new ValuesIterator(key);
336         }
337     }
338
339     /**
340      * Gets the total size of the map by counting all the values.
341      *
342      * @return the total size of the map counting all values
343      */

344     public int totalSize() {
345         int total = 0;
346         Collection JavaDoc values = getMap().values();
347         for (Iterator JavaDoc it = values.iterator(); it.hasNext();) {
348             Collection JavaDoc coll = (Collection JavaDoc) it.next();
349             total += coll.size();
350         }
351         return total;
352     }
353
354     /**
355      * Creates a new instance of the map value Collection container
356      * using the factory.
357      * <p>
358      * This method can be overridden to perform your own processing
359      * instead of using the factory.
360      *
361      * @param size the collection size that is about to be added
362      * @return the new collection
363      */

364     protected Collection JavaDoc createCollection(int size) {
365         return (Collection JavaDoc) collectionFactory.create();
366     }
367
368     //-----------------------------------------------------------------------
369
/**
370      * Inner class that provides the values view.
371      */

372     private class Values extends AbstractCollection JavaDoc {
373         public Iterator JavaDoc iterator() {
374             final IteratorChain chain = new IteratorChain();
375             for (Iterator JavaDoc it = keySet().iterator(); it.hasNext();) {
376                 chain.addIterator(new ValuesIterator(it.next()));
377             }
378             return chain;
379         }
380
381         public int size() {
382             return totalSize();
383         }
384
385         public void clear() {
386             MultiValueMap.this.clear();
387         }
388     }
389
390     /**
391      * Inner class that provides the values iterator.
392      */

393     private class ValuesIterator implements Iterator JavaDoc {
394         private final Object JavaDoc key;
395         private final Collection JavaDoc values;
396         private final Iterator JavaDoc iterator;
397
398         public ValuesIterator(Object JavaDoc key) {
399             this.key = key;
400             this.values = getCollection(key);
401             this.iterator = values.iterator();
402         }
403
404         public void remove() {
405             iterator.remove();
406             if (values.isEmpty()) {
407                 MultiValueMap.this.remove(key);
408             }
409         }
410
411         public boolean hasNext() {
412             return iterator.hasNext();
413         }
414
415         public Object JavaDoc next() {
416             return iterator.next();
417         }
418     }
419
420     /**
421      * Inner class that provides a simple reflection factory.
422      */

423     private static class ReflectionFactory implements Factory {
424         private final Class JavaDoc clazz;
425
426         public ReflectionFactory(Class JavaDoc clazz) {
427             this.clazz = clazz;
428         }
429
430         public Object JavaDoc create() {
431             try {
432                 return clazz.newInstance();
433             } catch (Exception JavaDoc ex) {
434                 throw new FunctorException("Cannot instantiate class: " + clazz, ex);
435             }
436         }
437     }
438
439 }
440
Popular Tags