From 9072508ad03b97fe1c34b97b48ccd44360a98b93 Mon Sep 17 00:00:00 2001 From: Stephen Colebourne Date: Thu, 19 May 2005 23:25:46 +0000 Subject: [PATCH] Fix put methods, optimize clear method, fix formatting bug 29440 git-svn-id: https://svn.apache.org/repos/asf/jakarta/commons/proper/collections/trunk@171009 13f79535-47bb-0310-9956-ffa450edef68 --- .../collections/map/MultiValueMap.java | 226 +++++++++++------- .../collections/map/TestMultiValueMap.java | 6 +- 2 files changed, 139 insertions(+), 93 deletions(-) diff --git a/src/java/org/apache/commons/collections/map/MultiValueMap.java b/src/java/org/apache/commons/collections/map/MultiValueMap.java index 4066973d4..37a2327be 100644 --- a/src/java/org/apache/commons/collections/map/MultiValueMap.java +++ b/src/java/org/apache/commons/collections/map/MultiValueMap.java @@ -15,110 +15,121 @@ */ package org.apache.commons.collections.map; +import java.util.AbstractCollection; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; +import java.util.Set; + import org.apache.commons.collections.Factory; import org.apache.commons.collections.MultiMap; import org.apache.commons.collections.iterators.EmptyIterator; import org.apache.commons.collections.iterators.IteratorChain; -import java.util.AbstractCollection; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Iterator; -import java.util.Map; -import java.util.Set; - /** * A MultiValueMap decorates another map, allowing it to have - * more than one value for a key. The values of the map will be - * Collection objects. The types of which can be specified using - * either a Class object or a Factory which creates Collection - * objects. + * more than one value for a key. + *

+ * A MultiMap is a Map with slightly different semantics. + * Putting a value into the map will add the value to a Collection at that key. + * Getting a value will return a Collection, holding all the values put to that key. + *

+ * This implementation is a decorator, allowing any Map implementation + * to be used as the base. + *

+ * In addition, this implementation allows the type of collection used + * for the values to be controlled. By default, an ArrayList + * is used, however a Class to instantiate may be specified, + * or a factory that returns a Collection instance. * - * @author James Carman + * @author James Carman + * @author Christopher Berry + * @author James Strachan + * @author Steve Downey + * @author Stephen Colebourne + * @author Julien Buret + * @author Serhiy Yevtushenko + * @version $Revision: $ $Date: $ * @since Commons Collections 3.2 */ public class MultiValueMap extends AbstractMapDecorator implements MultiMap { + + /** The factory for creating value collections. */ private final Factory collectionFactory; - private Collection values; + /** The cached values. */ + private transient Collection values; /** * Creates a map which wraps the given map and * maps keys to ArrayLists. * - * @param map the map to wrap + * @param map the map to wrap */ - public static Map decorate(Map map) { - return new MultiValueMap(map); + public static MultiValueMap decorate(Map map) { + return new MultiValueMap(map, new ReflectionFactory(ArrayList.class)); } /** * Creates a map which decorates the given map and * maps keys to collections of type collectionClass. * - * @param map the map to wrap - * @param collectionClass the type of the collection class + * @param map the map to wrap + * @param collectionClass the type of the collection class */ - public static Map decorate(Map map, Class collectionClass) { - return new MultiValueMap(map, collectionClass); + public static MultiValueMap decorate(Map map, Class collectionClass) { + return new MultiValueMap(map, new ReflectionFactory(collectionClass)); } /** * Creates a map which decorates the given map and * creates the value collections using the supplied collectionFactory. * - * @param map the map to decorate - * @param collectionFactory the collection factory (must return a Collection object). + * @param map the map to decorate + * @param collectionFactory the collection factory (must return a Collection object). */ - public static Map decorate(Map map, Factory collectionFactory) { + public static MultiValueMap decorate(Map map, Factory collectionFactory) { return new MultiValueMap(map, collectionFactory); } + //----------------------------------------------------------------------- /** - * Creates a MultiValueMap which wraps the given map and - * maps keys to ArrayLists. - * - * @param map the map to wrap + * Creates a MultiValueMap based on a HashMap and + * storing the multiple values in an ArrayList. */ - protected MultiValueMap(Map map) { - this(map, ArrayList.class); - } - - /** - * Creates a MultiValueMap which decorates the given map and - * maps keys to collections of type collectionClass. - * - * @param map the map to wrap - * @param collectionClass the type of the collection class - */ - protected MultiValueMap(Map map, Class collectionClass) { - this(map, new ReflectionFactory(collectionClass)); + public MultiValueMap() { + this(new HashMap(), new ReflectionFactory(ArrayList.class)); } /** * Creates a MultiValueMap which decorates the given map and * creates the value collections using the supplied collectionFactory. * - * @param map the map to decorate - * @param collectionFactory the collection factory (must return a Collection object). + * @param map the map to decorate + * @param collectionFactory the collection factory which must return a Collection instance */ protected MultiValueMap(Map map, Factory collectionFactory) { super(map); + if (collectionFactory == null) { + throw new IllegalArgumentException("The factory must not be null"); + } this.collectionFactory = collectionFactory; } + //----------------------------------------------------------------------- /** * Clear the map. - *

- * This clears each collection in the map, and so may be slow. */ public void clear() { - Set pairs = getMap().entrySet(); - Iterator pairsIterator = pairs.iterator(); - while(pairsIterator.hasNext()) { - Map.Entry keyValuePair = (Map.Entry) pairsIterator.next(); - Collection coll = (Collection) keyValuePair.getValue(); - coll.clear(); - } + // If you believe that you have GC issues here, try uncommenting this code +// Set pairs = getMap().entrySet(); +// Iterator pairsIterator = pairs.iterator(); +// while (pairsIterator.hasNext()) { +// Map.Entry keyValuePair = (Map.Entry) pairsIterator.next(); +// Collection coll = (Collection) keyValuePair.getValue(); +// coll.clear(); +// } getMap().clear(); } @@ -137,14 +148,14 @@ public class MultiValueMap extends AbstractMapDecorator implements MultiMap { */ public Object remove(Object key, Object value) { Collection valuesForKey = getCollection(key); - if(valuesForKey == null) { + if (valuesForKey == null) { return null; } boolean removed = valuesForKey.remove(value); - if(removed == false) { + if (removed == false) { return null; } - if(valuesForKey.isEmpty()) { + if (valuesForKey.isEmpty()) { remove(key); } return value; @@ -160,14 +171,14 @@ public class MultiValueMap extends AbstractMapDecorator implements MultiMap { */ public boolean containsValue(Object value) { Set pairs = getMap().entrySet(); - if(pairs == null) { + if (pairs == null) { return false; } Iterator pairsIterator = pairs.iterator(); - while(pairsIterator.hasNext()) { + while (pairsIterator.hasNext()) { Map.Entry keyValuePair = (Map.Entry) pairsIterator.next(); Collection coll = (Collection) keyValuePair.getValue(); - if(coll.contains(value)) { + if (coll.contains(value)) { return true; } } @@ -175,26 +186,30 @@ public class MultiValueMap extends AbstractMapDecorator implements MultiMap { } /** - * Removes a specific value from map. + * Adds the value to the collection associated with the specified key. *

- * The item is removed from the collection mapped to the specified key. - * Other values attached to that key are unaffected. - *

- * If the last value for a key is removed, null will be returned - * from a subsequant get(key). + * Unlike a normal Map the previous value is not replaced. + * Instead the new value is added to the collection stored against the key. * - * @param key the key to remove from - * @param value the value to remove - * @return the value removed (which was passed in), null if nothing removed + * @param key the key to store against + * @param value the value to add to the collection at the key + * @return the value added if the map changed and null if the map did not change */ public Object put(Object key, Object value) { - Collection c = getCollection(key); - if(c == null) { - c = (Collection) collectionFactory.create(); - getMap().put(key, c); + boolean result = false; + Collection coll = getCollection(key); + if (coll == null) { + coll = createCollection(1); + result = coll.add(value); + if (coll.size() > 0) { + // only add if non-zero size to maintain class state + getMap().put(key, coll); + result = false; + } + } else { + result = coll.add(value); } - boolean results = c.add(value); - return (results ? value : null); + return (result ? value : null); } /** @@ -217,7 +232,7 @@ public class MultiValueMap extends AbstractMapDecorator implements MultiMap { */ public boolean containsValue(Object key, Object value) { Collection coll = getCollection(key); - if(coll == null) { + if (coll == null) { return false; } return coll.contains(value); @@ -242,29 +257,37 @@ public class MultiValueMap extends AbstractMapDecorator implements MultiMap { */ public int size(Object key) { Collection coll = getCollection(key); - if(coll == null) { + if (coll == null) { return 0; } return coll.size(); } /** - * Adds a collection of values to the collection associated with the specified key. + * Adds a collection of values to the collection associated with + * the specified key. * * @param key the key to store against * @param values the values to add to the collection at the key, null ignored * @return true if this map changed */ public boolean putAll(Object key, Collection values) { - if(values == null || values.size() == 0) { + if (values == null || values.size() == 0) { return false; } Collection coll = getCollection(key); - if(coll == null) { - coll = (Collection) collectionFactory.create(); - getMap().put(key, coll); + if (coll == null) { + coll = createCollection(values.size()); + boolean result = coll.addAll(values); + if (coll.size() > 0) { + // only add if non-zero size to maintain class state + getMap().put(key, coll); + result = false; + } + return result; + } else { + return coll.addAll(values); } - return coll.addAll(values); } /** @@ -274,10 +297,9 @@ public class MultiValueMap extends AbstractMapDecorator implements MultiMap { * @return the iterator of the collection at the key, empty iterator if key not in map */ public Iterator iterator(Object key) { - if(!containsKey(key)) { + if (!containsKey(key)) { return EmptyIterator.INSTANCE; - } - else { + } else { return new ValuesIterator(key); } } @@ -290,18 +312,36 @@ public class MultiValueMap extends AbstractMapDecorator implements MultiMap { public int totalSize() { int total = 0; Collection values = getMap().values(); - for(Iterator it = values.iterator(); it.hasNext();) { + for (Iterator it = values.iterator(); it.hasNext();) { Collection coll = (Collection) it.next(); total += coll.size(); } return total; } + /** + * Creates a new instance of the map value Collection container + * using the factory. + *

+ * This method can be overridden to perform your own processing + * instead of using the factory. + * + * @param size the collection size that is about to be added + * @return the new collection + */ + protected Collection createCollection(int size) { + return (Collection) collectionFactory.create(); + } + + //----------------------------------------------------------------------- + /** + * Inner class that provides the values view. + */ private class Values extends AbstractCollection { public Iterator iterator() { final IteratorChain chain = new IteratorChain(); - for(Iterator i = keySet().iterator(); i.hasNext();) { - chain.addIterator(new ValuesIterator(i.next())); + for (Iterator it = keySet().iterator(); it.hasNext();) { + chain.addIterator(new ValuesIterator(it.next())); } return chain; } @@ -315,6 +355,9 @@ public class MultiValueMap extends AbstractMapDecorator implements MultiMap { } } + /** + * Inner class that provides the values iterator. + */ private class ValuesIterator implements Iterator { private final Object key; private final Collection values; @@ -328,7 +371,7 @@ public class MultiValueMap extends AbstractMapDecorator implements MultiMap { public void remove() { iterator.remove(); - if(values.isEmpty()) { + if (values.isEmpty()) { MultiValueMap.this.remove(key); } } @@ -342,6 +385,9 @@ public class MultiValueMap extends AbstractMapDecorator implements MultiMap { } } + /** + * Inner class that provides a simple reflection factory. + */ private static class ReflectionFactory implements Factory { private final Class clazz; @@ -352,10 +398,10 @@ public class MultiValueMap extends AbstractMapDecorator implements MultiMap { public Object create() { try { return clazz.newInstance(); - } - catch(Exception e) { - throw new RuntimeException("Cannot instantiate class " + clazz + ".", e); + } catch (Exception ex) { + throw new RuntimeException("Cannot instantiate class: " + clazz, ex); } } } + } diff --git a/src/test/org/apache/commons/collections/map/TestMultiValueMap.java b/src/test/org/apache/commons/collections/map/TestMultiValueMap.java index 99bdd8a56..3fe42240c 100644 --- a/src/test/org/apache/commons/collections/map/TestMultiValueMap.java +++ b/src/test/org/apache/commons/collections/map/TestMultiValueMap.java @@ -1,5 +1,5 @@ /* - * Copyright 2001-2005 The Apache Software Foundation + * Copyright 2005 The Apache Software Foundation * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -28,7 +28,7 @@ import java.util.Collection; import org.apache.commons.collections.IteratorUtils; /** - * TestMultiValueMap + * TestMultiValueMap. * * @author James Carman * @since Commons Collections 3.2 @@ -93,7 +93,7 @@ public class TestMultiValueMap extends TestCase { } private MultiValueMap createTestMap(Class collectionClass) { - final MultiValueMap map = new MultiValueMap(new HashMap(), collectionClass); + final MultiValueMap map = MultiValueMap.decorate(new HashMap(), collectionClass); map.put("one", "uno"); map.put("one", "un"); map.put("two", "dos");