001: /*
002: * Copyright 2001-2005 The Apache Software Foundation
003: *
004: * Licensed under the Apache License, Version 2.0 (the "License");
005: * you may not use this file except in compliance with the License.
006: * You may obtain a copy of the License at
007: *
008: * http://www.apache.org/licenses/LICENSE-2.0
009: *
010: * Unless required by applicable law or agreed to in writing, software
011: * distributed under the License is distributed on an "AS IS" BASIS,
012: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013: * See the License for the specific language governing permissions and
014: * limitations under the License.
015: */
016: package org.apache.commons.collections.map;
017:
018: import java.util.AbstractCollection;
019: import java.util.ArrayList;
020: import java.util.Collection;
021: import java.util.HashMap;
022: import java.util.Iterator;
023: import java.util.Map;
024: import java.util.Set;
025:
026: import org.apache.commons.collections.Factory;
027: import org.apache.commons.collections.FunctorException;
028: import org.apache.commons.collections.MultiMap;
029: import org.apache.commons.collections.iterators.EmptyIterator;
030: import org.apache.commons.collections.iterators.IteratorChain;
031:
032: /**
033: * A MultiValueMap decorates another map, allowing it to have
034: * more than one value for a key.
035: * <p>
036: * A <code>MultiMap</code> is a Map with slightly different semantics.
037: * Putting a value into the map will add the value to a Collection at that key.
038: * Getting a value will return a Collection, holding all the values put to that key.
039: * <p>
040: * This implementation is a decorator, allowing any Map implementation
041: * to be used as the base.
042: * <p>
043: * In addition, this implementation allows the type of collection used
044: * for the values to be controlled. By default, an <code>ArrayList</code>
045: * is used, however a <code>Class</code> to instantiate may be specified,
046: * or a factory that returns a <code>Collection</code> instance.
047: * <p>
048: * <strong>Note that MultiValueMap is not synchronized and is not thread-safe.</strong>
049: * If you wish to use this map from multiple threads concurrently, you must use
050: * appropriate synchronization. This class may throw exceptions when accessed
051: * by concurrent threads without synchronization.
052: *
053: * @author James Carman
054: * @author Christopher Berry
055: * @author James Strachan
056: * @author Steve Downey
057: * @author Stephen Colebourne
058: * @author Julien Buret
059: * @author Serhiy Yevtushenko
060: * @version $Revision: 348007 $ $Date: 2005-11-21 22:52:57 +0000 (Mon, 21 Nov 2005) $
061: * @since Commons Collections 3.2
062: */
063: public class MultiValueMap extends AbstractMapDecorator implements
064: MultiMap {
065:
066: /** The factory for creating value collections. */
067: private final Factory collectionFactory;
068: /** The cached values. */
069: private transient Collection values;
070:
071: /**
072: * Creates a map which wraps the given map and
073: * maps keys to ArrayLists.
074: *
075: * @param map the map to wrap
076: */
077: public static MultiValueMap decorate(Map map) {
078: return new MultiValueMap(map, new ReflectionFactory(
079: ArrayList.class));
080: }
081:
082: /**
083: * Creates a map which decorates the given <code>map</code> and
084: * maps keys to collections of type <code>collectionClass</code>.
085: *
086: * @param map the map to wrap
087: * @param collectionClass the type of the collection class
088: */
089: public static MultiValueMap decorate(Map map, Class collectionClass) {
090: return new MultiValueMap(map, new ReflectionFactory(
091: collectionClass));
092: }
093:
094: /**
095: * Creates a map which decorates the given <code>map</code> and
096: * creates the value collections using the supplied <code>collectionFactory</code>.
097: *
098: * @param map the map to decorate
099: * @param collectionFactory the collection factory (must return a Collection object).
100: */
101: public static MultiValueMap decorate(Map map,
102: Factory collectionFactory) {
103: return new MultiValueMap(map, collectionFactory);
104: }
105:
106: //-----------------------------------------------------------------------
107: /**
108: * Creates a MultiValueMap based on a <code>HashMap</code> and
109: * storing the multiple values in an <code>ArrayList</code>.
110: */
111: public MultiValueMap() {
112: this (new HashMap(), new ReflectionFactory(ArrayList.class));
113: }
114:
115: /**
116: * Creates a MultiValueMap which decorates the given <code>map</code> and
117: * creates the value collections using the supplied <code>collectionFactory</code>.
118: *
119: * @param map the map to decorate
120: * @param collectionFactory the collection factory which must return a Collection instance
121: */
122: protected MultiValueMap(Map map, Factory collectionFactory) {
123: super (map);
124: if (collectionFactory == null) {
125: throw new IllegalArgumentException(
126: "The factory must not be null");
127: }
128: this .collectionFactory = collectionFactory;
129: }
130:
131: //-----------------------------------------------------------------------
132: /**
133: * Clear the map.
134: */
135: public void clear() {
136: // If you believe that you have GC issues here, try uncommenting this code
137: // Set pairs = getMap().entrySet();
138: // Iterator pairsIterator = pairs.iterator();
139: // while (pairsIterator.hasNext()) {
140: // Map.Entry keyValuePair = (Map.Entry) pairsIterator.next();
141: // Collection coll = (Collection) keyValuePair.getValue();
142: // coll.clear();
143: // }
144: getMap().clear();
145: }
146:
147: /**
148: * Removes a specific value from map.
149: * <p>
150: * The item is removed from the collection mapped to the specified key.
151: * Other values attached to that key are unaffected.
152: * <p>
153: * If the last value for a key is removed, <code>null</code> will be returned
154: * from a subsequant <code>get(key)</code>.
155: *
156: * @param key the key to remove from
157: * @param value the value to remove
158: * @return the value removed (which was passed in), null if nothing removed
159: */
160: public Object remove(Object key, Object value) {
161: Collection valuesForKey = getCollection(key);
162: if (valuesForKey == null) {
163: return null;
164: }
165: boolean removed = valuesForKey.remove(value);
166: if (removed == false) {
167: return null;
168: }
169: if (valuesForKey.isEmpty()) {
170: remove(key);
171: }
172: return value;
173: }
174:
175: /**
176: * Checks whether the map contains the value specified.
177: * <p>
178: * This checks all collections against all keys for the value, and thus could be slow.
179: *
180: * @param value the value to search for
181: * @return true if the map contains the value
182: */
183: public boolean containsValue(Object value) {
184: Set pairs = getMap().entrySet();
185: if (pairs == null) {
186: return false;
187: }
188: Iterator pairsIterator = pairs.iterator();
189: while (pairsIterator.hasNext()) {
190: Map.Entry keyValuePair = (Map.Entry) pairsIterator.next();
191: Collection coll = (Collection) keyValuePair.getValue();
192: if (coll.contains(value)) {
193: return true;
194: }
195: }
196: return false;
197: }
198:
199: /**
200: * Adds the value to the collection associated with the specified key.
201: * <p>
202: * Unlike a normal <code>Map</code> the previous value is not replaced.
203: * Instead the new value is added to the collection stored against the key.
204: *
205: * @param key the key to store against
206: * @param value the value to add to the collection at the key
207: * @return the value added if the map changed and null if the map did not change
208: */
209: public Object put(Object key, Object value) {
210: boolean result = false;
211: Collection coll = getCollection(key);
212: if (coll == null) {
213: coll = createCollection(1);
214: result = coll.add(value);
215: if (coll.size() > 0) {
216: // only add if non-zero size to maintain class state
217: getMap().put(key, coll);
218: result = false;
219: }
220: } else {
221: result = coll.add(value);
222: }
223: return (result ? value : null);
224: }
225:
226: /**
227: * Override superclass to ensure that MultiMap instances are
228: * correctly handled.
229: * <p>
230: * If you call this method with a normal map, each entry is
231: * added using <code>put(Object,Object)</code>.
232: * If you call this method with a multi map, each entry is
233: * added using <code>putAll(Object,Collection)</code>.
234: *
235: * @param map the map to copy (either a normal or multi map)
236: */
237: public void putAll(Map map) {
238: if (map instanceof MultiMap) {
239: for (Iterator it = map.entrySet().iterator(); it.hasNext();) {
240: Map.Entry entry = (Map.Entry) it.next();
241: Collection coll = (Collection) entry.getValue();
242: putAll(entry.getKey(), coll);
243: }
244: } else {
245: for (Iterator it = map.entrySet().iterator(); it.hasNext();) {
246: Map.Entry entry = (Map.Entry) it.next();
247: put(entry.getKey(), entry.getValue());
248: }
249: }
250: }
251:
252: /**
253: * Gets a collection containing all the values in the map.
254: * <p>
255: * This returns a collection containing the combination of values from all keys.
256: *
257: * @return a collection view of the values contained in this map
258: */
259: public Collection values() {
260: Collection vs = values;
261: return (vs != null ? vs : (values = new Values()));
262: }
263:
264: /**
265: * Checks whether the collection at the specified key contains the value.
266: *
267: * @param value the value to search for
268: * @return true if the map contains the value
269: */
270: public boolean containsValue(Object key, Object value) {
271: Collection coll = getCollection(key);
272: if (coll == null) {
273: return false;
274: }
275: return coll.contains(value);
276: }
277:
278: /**
279: * Gets the collection mapped to the specified key.
280: * This method is a convenience method to typecast the result of <code>get(key)</code>.
281: *
282: * @param key the key to retrieve
283: * @return the collection mapped to the key, null if no mapping
284: */
285: public Collection getCollection(Object key) {
286: return (Collection) getMap().get(key);
287: }
288:
289: /**
290: * Gets the size of the collection mapped to the specified key.
291: *
292: * @param key the key to get size for
293: * @return the size of the collection at the key, zero if key not in map
294: */
295: public int size(Object key) {
296: Collection coll = getCollection(key);
297: if (coll == null) {
298: return 0;
299: }
300: return coll.size();
301: }
302:
303: /**
304: * Adds a collection of values to the collection associated with
305: * the specified key.
306: *
307: * @param key the key to store against
308: * @param values the values to add to the collection at the key, null ignored
309: * @return true if this map changed
310: */
311: public boolean putAll(Object key, Collection values) {
312: if (values == null || values.size() == 0) {
313: return false;
314: }
315: Collection coll = getCollection(key);
316: if (coll == null) {
317: coll = createCollection(values.size());
318: boolean result = coll.addAll(values);
319: if (coll.size() > 0) {
320: // only add if non-zero size to maintain class state
321: getMap().put(key, coll);
322: result = false;
323: }
324: return result;
325: } else {
326: return coll.addAll(values);
327: }
328: }
329:
330: /**
331: * Gets an iterator for the collection mapped to the specified key.
332: *
333: * @param key the key to get an iterator for
334: * @return the iterator of the collection at the key, empty iterator if key not in map
335: */
336: public Iterator iterator(Object key) {
337: if (!containsKey(key)) {
338: return EmptyIterator.INSTANCE;
339: } else {
340: return new ValuesIterator(key);
341: }
342: }
343:
344: /**
345: * Gets the total size of the map by counting all the values.
346: *
347: * @return the total size of the map counting all values
348: */
349: public int totalSize() {
350: int total = 0;
351: Collection values = getMap().values();
352: for (Iterator it = values.iterator(); it.hasNext();) {
353: Collection coll = (Collection) it.next();
354: total += coll.size();
355: }
356: return total;
357: }
358:
359: /**
360: * Creates a new instance of the map value Collection container
361: * using the factory.
362: * <p>
363: * This method can be overridden to perform your own processing
364: * instead of using the factory.
365: *
366: * @param size the collection size that is about to be added
367: * @return the new collection
368: */
369: protected Collection createCollection(int size) {
370: return (Collection) collectionFactory.create();
371: }
372:
373: //-----------------------------------------------------------------------
374: /**
375: * Inner class that provides the values view.
376: */
377: private class Values extends AbstractCollection {
378: public Iterator iterator() {
379: final IteratorChain chain = new IteratorChain();
380: for (Iterator it = keySet().iterator(); it.hasNext();) {
381: chain.addIterator(new ValuesIterator(it.next()));
382: }
383: return chain;
384: }
385:
386: public int size() {
387: return totalSize();
388: }
389:
390: public void clear() {
391: MultiValueMap.this .clear();
392: }
393: }
394:
395: /**
396: * Inner class that provides the values iterator.
397: */
398: private class ValuesIterator implements Iterator {
399: private final Object key;
400: private final Collection values;
401: private final Iterator iterator;
402:
403: public ValuesIterator(Object key) {
404: this .key = key;
405: this .values = getCollection(key);
406: this .iterator = values.iterator();
407: }
408:
409: public void remove() {
410: iterator.remove();
411: if (values.isEmpty()) {
412: MultiValueMap.this .remove(key);
413: }
414: }
415:
416: public boolean hasNext() {
417: return iterator.hasNext();
418: }
419:
420: public Object next() {
421: return iterator.next();
422: }
423: }
424:
425: /**
426: * Inner class that provides a simple reflection factory.
427: */
428: private static class ReflectionFactory implements Factory {
429: private final Class clazz;
430:
431: public ReflectionFactory(Class clazz) {
432: this .clazz = clazz;
433: }
434:
435: public Object create() {
436: try {
437: return clazz.newInstance();
438: } catch (Exception ex) {
439: throw new FunctorException("Cannot instantiate class: "
440: + clazz, ex);
441: }
442: }
443: }
444:
445: }
|