From fc4bd9b4d0f05c0706f19e20c0fa6cfab852b719 Mon Sep 17 00:00:00 2001 From: Thomas Neidhart Date: Sun, 6 Apr 2014 19:58:37 +0000 Subject: [PATCH] [COLLECTIONS-508] Improved original contribution after feedback. Thanks to Dipanjan Laha. git-svn-id: https://svn.apache.org/repos/asf/commons/proper/collections/trunk@1585335 13f79535-47bb-0310-9956-ffa450edef68 --- .../commons/collections4/MultiValuedMap.java | 20 + .../commons/collections4/SetValuedMap.java | 71 ++ .../multimap/AbstractMultiValuedMap.java | 352 +++++++-- .../AbstractMultiValuedMapDecorator.java | 17 +- .../multimap/MultiValuedHashMap.java | 96 ++- .../multimap/UnmodifiableMultiValuedMap.java | 18 + .../collection/AbstractCollectionTest.java | 6 +- .../multimap/AbstractMultiValuedMapTest.java | 674 +++++++++++++++++- .../multimap/MultiValuedHashMapTest.java | 23 +- .../TransformedMultiValuedMapTest.java | 7 + .../UnmodifiableMultiValuedMapTest.java | 177 +++++ 11 files changed, 1368 insertions(+), 93 deletions(-) create mode 100644 src/main/java/org/apache/commons/collections4/SetValuedMap.java diff --git a/src/main/java/org/apache/commons/collections4/MultiValuedMap.java b/src/main/java/org/apache/commons/collections4/MultiValuedMap.java index 89c340e62..e7eef287d 100644 --- a/src/main/java/org/apache/commons/collections4/MultiValuedMap.java +++ b/src/main/java/org/apache/commons/collections4/MultiValuedMap.java @@ -284,4 +284,24 @@ public interface MultiValuedMap { */ Collection values(); + /** + * Returns a {@link Map} view of this MultiValuedMap with a Collection as + * its value. The Collection holds all the values mapped to that key. + * + * @return a Map view of the mappings in this MultiValuedMap + */ + Map> asMap(); + + // Iterators + + /** + * Obtains a MapIterator over the map. + *

+ * A map iterator is an efficient way of iterating over maps. There is no + * need to access the entries collection or use Map Entry objects. + * + * @return a map iterator + */ + MapIterator mapIterator(); + } diff --git a/src/main/java/org/apache/commons/collections4/SetValuedMap.java b/src/main/java/org/apache/commons/collections4/SetValuedMap.java new file mode 100644 index 000000000..de0806388 --- /dev/null +++ b/src/main/java/org/apache/commons/collections4/SetValuedMap.java @@ -0,0 +1,71 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.collections4; + +import java.util.Set; + +/** + * Defines a map that holds a set of values against each key. + *

+ * A SetValuedMap is a Map with slightly different semantics: + *

    + *
  • Putting a value into the map will add the value to a Set at + * that key.
  • + *
  • Getting a value will return a Set, holding all the values + * put to that key.
  • + *
+ * + * @since 4.1 + * @version $Id$ + */ +public interface SetValuedMap extends MultiValuedMap { + + /** + * Gets the set of values associated with the specified key. + *

+ * Implementations typically return null if no values have been + * mapped to the key, however the implementation may choose to return an + * empty collection. + *

+ * Implementations may choose to return a clone of the internal collection. + * + * @param key the key to retrieve + * @return the Set of values, implementations should return + * null for no mapping, but may return an empty + * collection + * @throws ClassCastException if the key is of an invalid type + * @throws NullPointerException if the key is null and null keys are invalid + */ + Set get(Object key); + + /** + * Removes all values associated with the specified key. + *

+ * Implementations typically return null from a subsequent + * get(Object), however they may choose to return an empty + * collection. + * + * @param key the key to remove values from + * @return the Set of values removed, implementations should + * return null for no mapping found, but may return an + * empty collection + * @throws UnsupportedOperationException if the map is unmodifiable + * @throws ClassCastException if the key is of an invalid type + * @throws NullPointerException if the key is null and null keys are invalid + */ + Set remove(Object key); +} diff --git a/src/main/java/org/apache/commons/collections4/multimap/AbstractMultiValuedMap.java b/src/main/java/org/apache/commons/collections4/multimap/AbstractMultiValuedMap.java index 2b09cd1b2..d240f1472 100644 --- a/src/main/java/org/apache/commons/collections4/multimap/AbstractMultiValuedMap.java +++ b/src/main/java/org/apache/commons/collections4/multimap/AbstractMultiValuedMap.java @@ -27,15 +27,20 @@ import java.util.Map.Entry; import java.util.Set; import org.apache.commons.collections4.Bag; +import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.collections4.Factory; +import org.apache.commons.collections4.IteratorUtils; +import org.apache.commons.collections4.MapIterator; import org.apache.commons.collections4.MultiValuedMap; import org.apache.commons.collections4.Transformer; import org.apache.commons.collections4.bag.HashBag; import org.apache.commons.collections4.functors.InstantiateFactory; -import org.apache.commons.collections4.iterators.EmptyIterator; +import org.apache.commons.collections4.iterators.EmptyMapIterator; import org.apache.commons.collections4.iterators.IteratorChain; import org.apache.commons.collections4.iterators.LazyIteratorChain; import org.apache.commons.collections4.iterators.TransformIterator; +import org.apache.commons.collections4.keyvalue.AbstractMapEntry; +import org.apache.commons.collections4.set.UnmodifiableSet; /** * Abstract implementation of the {@link MultiValuedMap} interface to simplify @@ -84,6 +89,30 @@ public class AbstractMultiValuedMap implements MultiValuedMap, Seria this.collectionFactory = new InstantiateFactory(collectionClazz); } + /** + * Constructor that wraps (not copies). + * + * @param the collection type + * @param map the map to wrap, must not be null + * @param initialCollectionCapacity the initial capacity of the collection + * @param collectionClazz the collection class + * @throws IllegalArgumentException if the map is null or if + * initialCollectionCapacity is negetive + */ + @SuppressWarnings("unchecked") + protected > AbstractMultiValuedMap(final Map map, + int initialCollectionCapacity, final Class collectionClazz) { + if (map == null) { + throw new IllegalArgumentException("Map must not be null"); + } + if (initialCollectionCapacity < 0) { + throw new IllegalArgumentException("Illegal Capacity: " + initialCollectionCapacity); + } + this.map = (Map>) map; + this.collectionFactory = new InstantiateFactory(collectionClazz, new Class[] { Integer.TYPE }, + new Object[] { new Integer(initialCollectionCapacity) }); + } + /** * Gets the map being wrapped. * @@ -119,7 +148,7 @@ public class AbstractMultiValuedMap implements MultiValuedMap, Seria * {@inheritDoc} */ public boolean containsMapping(Object key, Object value) { - final Collection col = get(key); + final Collection col = getMap().get(key); if (col == null) { return false; } @@ -134,15 +163,16 @@ public class AbstractMultiValuedMap implements MultiValuedMap, Seria } /** - * Gets the collection of values associated with the specified key. + * Gets the collection of values associated with the specified key. This + * would return an empty collection in case the mapping is not present * * @param key the key to retrieve - * @return the Collection of values, will return - * null for no mapping + * @return the Collection of values, will return an empty + * Collection for no mapping * @throws ClassCastException if the key is of an invalid type */ public Collection get(Object key) { - return getMap().get(key); + return new WrappedCollection(key); } /** @@ -174,7 +204,7 @@ public class AbstractMultiValuedMap implements MultiValuedMap, Seria */ public boolean removeMapping(K key, V item) { boolean result = false; - final Collection col = get(key); + final Collection col = getMap().get(key); if (col == null) { return false; } @@ -245,7 +275,7 @@ public class AbstractMultiValuedMap implements MultiValuedMap, Seria */ public V put(K key, V value) { boolean result = false; - Collection coll = get(key); + Collection coll = getMap().get(key); if (coll == null) { coll = createCollection(); coll.add(value); @@ -312,6 +342,13 @@ public class AbstractMultiValuedMap implements MultiValuedMap, Seria return keysBagView != null ? keysBagView : (keysBagView = new KeysBag()); } + /** + * {@inheritDoc} + */ + public Map> asMap() { + return getMap(); + } + /** * Adds Iterable values to the collection associated with the specified key. * @@ -326,7 +363,7 @@ public class AbstractMultiValuedMap implements MultiValuedMap, Seria } Iterator it = values.iterator(); boolean result = false; - Collection coll = get(key); + Collection coll = getMap().get(key); if (coll == null) { coll = createCollection(); // might produce a non-empty collection while (it.hasNext()) { @@ -351,31 +388,13 @@ public class AbstractMultiValuedMap implements MultiValuedMap, Seria } /** - * Gets an iterator for the collection mapped to the specified key. - * - * @param key the key to get an iterator for - * @return the iterator of the collection at the key, empty iterator if key - * not in map + * {@inheritDoc} */ - public Iterator iterator(final Object key) { - if (!containsKey(key)) { - return EmptyIterator. emptyIterator(); + public MapIterator mapIterator() { + if (size() == 0) { + return EmptyMapIterator.emptyMapIterator(); } - return new ValuesIterator(key); - } - - /** - * Gets the size of the collection mapped to the specified key. - * - * @param key the key to get size for - * @return the size of the collection at the key, zero if key not in map - */ - public int size(final Object key) { - final Collection coll = get(key); - if (coll == null) { - return 0; - } - return coll.size(); + return new MultiValuedMapIterator(); } @SuppressWarnings("rawtypes") @@ -432,6 +451,190 @@ public class AbstractMultiValuedMap implements MultiValuedMap, Seria // ----------------------------------------------------------------------- + /** + * Wrapped collection to handle add and remove on the collection returned by get(object) + */ + private class WrappedCollection implements Collection { + + private final Object key; + + public WrappedCollection(Object key) { + this.key = key; + } + + private Collection getMapping() { + return getMap().get(key); + } + + @SuppressWarnings("unchecked") + public boolean add(V value) { + final Collection col = getMapping(); + if (col == null) { + V addedVal = AbstractMultiValuedMap.this.put((K) key, value); + return addedVal != null ? true : false; + } + return col.add(value); + } + + @SuppressWarnings("unchecked") + public boolean addAll(Collection c) { + final Collection col = getMapping(); + if (col == null) { + return AbstractMultiValuedMap.this.putAll((K) key, c); + } + return col.addAll(c); + } + + public void clear() { + final Collection col = getMapping(); + if (col != null) { + col.clear(); + AbstractMultiValuedMap.this.remove(key); + } + } + + @SuppressWarnings("unchecked") + public Iterator iterator() { + final Collection col = getMapping(); + if (col == null) { + return (Iterator) IteratorUtils.EMPTY_ITERATOR; + } + return new ValuesIterator(key); + } + + public int size() { + final Collection col = getMapping(); + if (col == null) { + return 0; + } + return col.size(); + } + + public boolean contains(Object o) { + final Collection col = getMapping(); + if (col == null) { + return false; + } + return col.contains(o); + } + + public boolean containsAll(Collection o) { + final Collection col = getMapping(); + if (col == null) { + return false; + } + return col.containsAll(o); + } + + public boolean isEmpty() { + final Collection col = getMapping(); + if (col == null) { + return true; + } + return col.isEmpty(); + } + + public boolean remove(Object item) { + final Collection col = getMapping(); + if (col == null) { + return false; + } + + boolean result = col.remove(item); + if (col.isEmpty()) { + AbstractMultiValuedMap.this.remove(key); + } + return result; + } + + public boolean removeAll(Collection c) { + final Collection col = getMapping(); + if (col == null) { + return false; + } + + boolean result = col.removeAll(c); + if (col.isEmpty()) { + AbstractMultiValuedMap.this.remove(key); + } + return result; + } + + public boolean retainAll(Collection c) { + final Collection col = getMapping(); + if (col == null) { + return false; + } + + boolean result = col.retainAll(c); + if (col.isEmpty()) { + AbstractMultiValuedMap.this.remove(key); + } + return result; + } + + public Object[] toArray() { + final Collection col = getMapping(); + if (col == null) { + return CollectionUtils.EMPTY_COLLECTION.toArray(); + } + return col.toArray(); + } + + @SuppressWarnings("unchecked") + public T[] toArray(T[] a) { + final Collection col = getMapping(); + if (col == null) { + return (T[]) CollectionUtils.EMPTY_COLLECTION.toArray(a); + } + return col.toArray(a); + } + + @SuppressWarnings("rawtypes") + @Override + public boolean equals(Object other) { + final Collection col = getMapping(); + if (col == null) { + return CollectionUtils.EMPTY_COLLECTION.equals(other); + } + if (other == null) { + return false; + } + if(!(other instanceof Collection)){ + return false; + } + Collection otherCol = (Collection) other; + if (col.size() != otherCol.size()) { + return false; + } + for (Object value : col) { + if (!otherCol.contains(value)) { + return false; + } + } + return true; + } + + @Override + public int hashCode() { + final Collection col = getMapping(); + if (col == null) { + return CollectionUtils.EMPTY_COLLECTION.hashCode(); + } + return col.hashCode(); + } + + @Override + public String toString() { + final Collection col = getMapping(); + if (col == null) { + return CollectionUtils.EMPTY_COLLECTION.toString(); + } + return col.toString(); + } + + } + /** * Inner class that provides a Bag keys view */ @@ -519,7 +722,7 @@ public class AbstractMultiValuedMap implements MultiValuedMap, Seria } public Set uniqueSet() { - return keySet(); + return UnmodifiableSet.unmodifiableSet(keySet()); } public int size() { @@ -609,21 +812,9 @@ public class AbstractMultiValuedMap implements MultiValuedMap, Seria final Transformer> entryTransformer = new Transformer>() { public Entry transform(final V input) { - return new Entry() { - - public K getKey() { - return key; - } - - public V getValue() { - return input; - } - - public V setValue(V value) { - throw new UnsupportedOperationException(); - } - }; + return new MultiValuedMapEntry(key, input); } + }; return new TransformIterator>(new ValuesIterator(key), entryTransformer); } @@ -637,6 +828,71 @@ public class AbstractMultiValuedMap implements MultiValuedMap, Seria } + /** + * Inner class for MultiValuedMap Entries + */ + private class MultiValuedMapEntry extends AbstractMapEntry { + + public MultiValuedMapEntry(K key, V value) { + super(key, value); + } + + @Override + public V setValue(V value) { + throw new UnsupportedOperationException(); + } + + } + + /** + * Inner class for MapIterator + */ + private class MultiValuedMapIterator implements MapIterator { + + private final Iterator> it; + + private Entry current = null; + + public MultiValuedMapIterator() { + this.it = AbstractMultiValuedMap.this.entries().iterator(); + } + + public boolean hasNext() { + return it.hasNext(); + } + + public K next() { + current = it.next(); + return current.getKey(); + } + + public K getKey() { + if (current == null) { + throw new IllegalStateException(); + } + return current.getKey(); + } + + public V getValue() { + if (current == null) { + throw new IllegalStateException(); + } + return current.getValue(); + } + + public void remove() { + it.remove(); + } + + public V setValue(V value) { + if (current == null) { + throw new IllegalStateException(); + } + return current.setValue(value); + } + + } + /** * Inner class that provides the values view. */ @@ -671,7 +927,7 @@ public class AbstractMultiValuedMap implements MultiValuedMap, Seria public ValuesIterator(final Object key) { this.key = key; - this.values = get(key); + this.values = getMap().get(key); this.iterator = values.iterator(); } diff --git a/src/main/java/org/apache/commons/collections4/multimap/AbstractMultiValuedMapDecorator.java b/src/main/java/org/apache/commons/collections4/multimap/AbstractMultiValuedMapDecorator.java index bf54ec57b..d763b4026 100644 --- a/src/main/java/org/apache/commons/collections4/multimap/AbstractMultiValuedMapDecorator.java +++ b/src/main/java/org/apache/commons/collections4/multimap/AbstractMultiValuedMapDecorator.java @@ -23,14 +23,14 @@ import java.util.Map.Entry; import java.util.Set; import org.apache.commons.collections4.Bag; +import org.apache.commons.collections4.MapIterator; import org.apache.commons.collections4.MultiValuedMap; /** * Decorates another MultiValuedMap to provide additional behaviour. *

- * Each method call made on this MultiValuedMap is forwarded to the - * decorated MultiValuedMap. This class is used as a framework to - * build to extensions such as synchronized and unmodifiable behaviour. + * Each method call made on this MultiValuedMap is forwarded to the decorated MultiValuedMap. + * This class is used as a framework to build to extensions such as synchronized and unmodifiable behaviour. * * @param the type of key elements * @param the type of value elements @@ -55,7 +55,8 @@ public class AbstractMultiValuedMapDecorator */ protected AbstractMultiValuedMapDecorator(final MultiValuedMap map) { if (map == null) { - throw new IllegalArgumentException("MultiValuedMap must not be null"); + throw new IllegalArgumentException( + "MultiValuedMap must not be null"); } this.map = map; } @@ -120,6 +121,10 @@ public class AbstractMultiValuedMapDecorator return decorated().values(); } + public Map> asMap() { + return decorated().asMap(); + } + public boolean putAll(K key, Iterable values) { return decorated().putAll(key, values); } @@ -132,6 +137,10 @@ public class AbstractMultiValuedMapDecorator decorated().putAll(m); } + public MapIterator mapIterator() { + return decorated().mapIterator(); + } + @Override public boolean equals(final Object object) { if (object == this) { diff --git a/src/main/java/org/apache/commons/collections4/multimap/MultiValuedHashMap.java b/src/main/java/org/apache/commons/collections4/multimap/MultiValuedHashMap.java index c6ce934e6..c098503b1 100644 --- a/src/main/java/org/apache/commons/collections4/multimap/MultiValuedHashMap.java +++ b/src/main/java/org/apache/commons/collections4/multimap/MultiValuedHashMap.java @@ -50,6 +50,16 @@ public class MultiValuedHashMap extends AbstractMultiValuedMap imple /** Serialization Version */ private static final long serialVersionUID = -5845183518195365857L; + /** + * The initial capacity used when none specified in constructor. + */ + static final int DEFAULT_INITIAL_CAPACITY = 16; + + /** + * The load factor used when none specified in constructor. + */ + static final float DEFAULT_LOAD_FACTOR = 0.75f; + /** * Creates a MultiValuedHashMap which maps keys to collections of type * collectionClass. @@ -62,16 +72,57 @@ public class MultiValuedHashMap extends AbstractMultiValuedMap imple */ public static > MultiValuedMap multiValuedMap( final Class collectionClass) { - return new MultiValuedHashMap(collectionClass); + return new MultiValuedHashMap(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR, collectionClass); } /** - * Creates a MultiValueMap based on a HashMap which stores the - * multiple values in an ArrayList. + * Creates a MultiValueMap based on a HashMap with the default + * initial capacity (16) and the default load factor (0.75), which stores + * the multiple values in an ArrayList. */ @SuppressWarnings("unchecked") public MultiValuedHashMap() { - this(ArrayList.class); + this(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR, ArrayList.class); + } + + /** + * Creates a MultiValueMap based on a HashMap with the initial + * capacity and the default load factor (0.75), which stores the multiple + * values in an ArrayList. + * + * @param initialCapacity the initial capacity of the underlying hash map + */ + @SuppressWarnings("unchecked") + public MultiValuedHashMap(int initialCapacity) { + this(initialCapacity, DEFAULT_LOAD_FACTOR, ArrayList.class); + } + + /** + * Creates a MultiValueMap based on a HashMap with the initial + * capacity and the load factor, which stores the multiple values in an + * ArrayList. + * + * @param initialCapacity the initial capacity of the underlying hash map + * @param loadFactor the load factor of the underlying hash map + */ + @SuppressWarnings("unchecked") + public MultiValuedHashMap(int initialCapacity, float loadFactor) { + this(initialCapacity, loadFactor, ArrayList.class); + } + + /** + * Creates a MultiValueMap based on a HashMap with the initial + * capacity and the load factor, which stores the multiple values in an + * ArrayList with the initial collection capacity. + * + * @param initialCapacity the initial capacity of the underlying hash map + * @param loadFactor the load factor of the underlying hash map + * @param initialCollectionCapacity the initial capacity of the Collection + * of values + */ + @SuppressWarnings("unchecked") + public MultiValuedHashMap(int initialCapacity, float loadFactor, int initialCollectionCapacity) { + this(initialCapacity, loadFactor, initialCollectionCapacity, ArrayList.class); } /** @@ -81,7 +132,7 @@ public class MultiValuedHashMap extends AbstractMultiValuedMap imple */ @SuppressWarnings("unchecked") public MultiValuedHashMap(final MultiValuedMap map) { - this(ArrayList.class); + this(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR, ArrayList.class); super.putAll(map); } @@ -92,7 +143,7 @@ public class MultiValuedHashMap extends AbstractMultiValuedMap imple */ @SuppressWarnings("unchecked") public MultiValuedHashMap(final Map map) { - this(ArrayList.class); + this(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR, ArrayList.class); super.putAll(map); } @@ -100,12 +151,35 @@ public class MultiValuedHashMap extends AbstractMultiValuedMap imple * Creates a MultiValuedHashMap which creates the value collections using * the supplied collectionClazz. * - * @param the collection type - * @param collectionClazz the class of the Collection to use to - * create the value collections + * @param initialCapacity the initial capacity of the underlying + * HashMap + * @param loadFactor the load factor of the underlying HashMap + * @param the collection type + * @param collectionClazz the class of the Collection to use to + * create the value collections */ - protected > MultiValuedHashMap(final Class collectionClazz) { - super(new HashMap>(), collectionClazz); + protected > MultiValuedHashMap(int initialCapacity, float loadFactor, + final Class collectionClazz) { + super(new HashMap>(initialCapacity, loadFactor), collectionClazz); + } + + /** + * Creates a MultiValuedHashMap which creates the value collections using + * the supplied collectionClazz and the initial collection + * capacity . + * + * @param initialCapacity the initial capacity of the underlying + * HashMap + * @param loadFactor the load factor of the underlying HashMap + * @param initialCollectionCapacity the initial capacity of the + * Collection + * @param the collection type + * @param collectionClazz the class of the Collection to use to + * create the value collections + */ + protected > MultiValuedHashMap(int initialCapacity, float loadFactor, + int initialCollectionCapacity, final Class collectionClazz) { + super(new HashMap>(initialCapacity, loadFactor), initialCollectionCapacity, collectionClazz); } } diff --git a/src/main/java/org/apache/commons/collections4/multimap/UnmodifiableMultiValuedMap.java b/src/main/java/org/apache/commons/collections4/multimap/UnmodifiableMultiValuedMap.java index 1ef918732..2c53ee98f 100644 --- a/src/main/java/org/apache/commons/collections4/multimap/UnmodifiableMultiValuedMap.java +++ b/src/main/java/org/apache/commons/collections4/multimap/UnmodifiableMultiValuedMap.java @@ -22,10 +22,13 @@ import java.util.Map.Entry; import java.util.Set; import org.apache.commons.collections4.Bag; +import org.apache.commons.collections4.MapIterator; import org.apache.commons.collections4.MultiValuedMap; import org.apache.commons.collections4.Unmodifiable; import org.apache.commons.collections4.bag.UnmodifiableBag; import org.apache.commons.collections4.collection.UnmodifiableCollection; +import org.apache.commons.collections4.iterators.UnmodifiableMapIterator; +import org.apache.commons.collections4.map.UnmodifiableMap; import org.apache.commons.collections4.set.UnmodifiableSet; /** @@ -91,6 +94,11 @@ public class UnmodifiableMultiValuedMap throw new UnsupportedOperationException(); } + @Override + public Collection get(Object key) { + return UnmodifiableCollection.unmodifiableCollection(decorated().get(key)); + } + @Override public V put(K key, V value) { throw new UnsupportedOperationException(); @@ -116,6 +124,16 @@ public class UnmodifiableMultiValuedMap return UnmodifiableCollection.unmodifiableCollection(decorated().values()); } + @Override + public Map> asMap() { + return UnmodifiableMap.>unmodifiableMap(decorated().asMap()); + } + + @Override + public MapIterator mapIterator() { + return UnmodifiableMapIterator.unmodifiableMapIterator(decorated().mapIterator()); + } + @Override public boolean putAll(K key, Iterable values) { throw new UnsupportedOperationException(); diff --git a/src/test/java/org/apache/commons/collections4/collection/AbstractCollectionTest.java b/src/test/java/org/apache/commons/collections4/collection/AbstractCollectionTest.java index 443d2311a..ab1e10332 100644 --- a/src/test/java/org/apache/commons/collections4/collection/AbstractCollectionTest.java +++ b/src/test/java/org/apache/commons/collections4/collection/AbstractCollectionTest.java @@ -703,7 +703,7 @@ public abstract class AbstractCollectionTest extends AbstractObjectTest { // make sure calls to "containsAll" don't change anything verify(); - final int min = getFullElements().length < 2 ? 0 : 2; + final int min = getFullElements().length < 4 ? 0 : 2; final int max = getFullElements().length == 1 ? 1 : getFullElements().length <= 5 ? getFullElements().length - 1 : 5; col = Arrays.asList(getFullElements()).subList(min, max); @@ -931,7 +931,7 @@ public abstract class AbstractCollectionTest extends AbstractObjectTest { resetFull(); final int size = getCollection().size(); - final int min = getFullElements().length < 2 ? 0 : 2; + final int min = getFullElements().length < 4 ? 0 : 2; final int max = getFullElements().length == 1 ? 1 : getFullElements().length <= 5 ? getFullElements().length - 1 : 5; final Collection all = Arrays.asList(getFullElements()).subList(min, max); @@ -985,7 +985,7 @@ public abstract class AbstractCollectionTest extends AbstractObjectTest { if (getFullElements().length > 1) { resetFull(); size = getCollection().size(); - final int min = getFullElements().length < 2 ? 0 : 2; + final int min = getFullElements().length < 4 ? 0 : 2; final int max = getFullElements().length <= 5 ? getFullElements().length - 1 : 5; assertTrue("Collection should changed by partial retainAll", getCollection().retainAll(elements.subList(min, max))); diff --git a/src/test/java/org/apache/commons/collections4/multimap/AbstractMultiValuedMapTest.java b/src/test/java/org/apache/commons/collections4/multimap/AbstractMultiValuedMapTest.java index 771ea17e8..937bbcbd0 100644 --- a/src/test/java/org/apache/commons/collections4/multimap/AbstractMultiValuedMapTest.java +++ b/src/test/java/org/apache/commons/collections4/multimap/AbstractMultiValuedMapTest.java @@ -23,11 +23,20 @@ import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; import org.apache.commons.collections4.AbstractObjectTest; import org.apache.commons.collections4.Bag; +import org.apache.commons.collections4.BulkTest; +import org.apache.commons.collections4.MapIterator; import org.apache.commons.collections4.MultiValuedMap; +import org.apache.commons.collections4.bag.AbstractBagTest; +import org.apache.commons.collections4.bag.CollectionBag; import org.apache.commons.collections4.bag.HashBag; +import org.apache.commons.collections4.collection.AbstractCollectionTest; +import org.apache.commons.collections4.map.AbstractMapTest; +import org.apache.commons.collections4.set.AbstractSetTest; /** * Abstract test class for {@link MultiValuedMap} contract and methods. @@ -40,6 +49,12 @@ import org.apache.commons.collections4.bag.HashBag; */ public abstract class AbstractMultiValuedMapTest extends AbstractObjectTest { + /** Map created by reset(). */ + protected MultiValuedMap map; + + /** MultiValuedHashMap created by reset(). */ + protected MultiValuedMap confirmed; + public AbstractMultiValuedMapTest(String testName) { super(testName); } @@ -76,25 +91,108 @@ public abstract class AbstractMultiValuedMapTest extends AbstractObjectTes return true; } + /** + * Returns true if the maps produced by {@link #makeObject()} and + * {@link #makeFullMap()} supports null keys. + *

+ * Default implementation returns true. Override if your collection class + * does not support null keys. + */ + public boolean isAllowNullKey() { + return true; + } + + /** + * Returns the set of keys in the mappings used to test the map. This method + * must return an array with the same length as {@link #getSampleValues()} + * and all array elements must be different. The default implementation + * constructs a set of String keys, and includes a single null key if + * {@link #isAllowNullKey()} returns true. + */ + @SuppressWarnings("unchecked") + public K[] getSampleKeys() { + final Object[] result = new Object[] { + "one", "one", "two", "two", + "three", "three" + }; + return (K[]) result; + } + + /** + * Returns the set of values in the mappings used to test the map. This + * method must return an array with the same length as + * {@link #getSampleKeys()}. The default implementation constructs a set of + * String values + */ + @SuppressWarnings("unchecked") + public V[] getSampleValues() { + final Object[] result = new Object[] { + "uno", "un", "dos", "deux", + "tres", "trois" + }; + return (V[]) result; + } + protected MultiValuedMap makeFullMap() { final MultiValuedMap map = makeObject(); addSampleMappings(map); return map; } - @SuppressWarnings("unchecked") protected void addSampleMappings(MultiValuedMap map) { - map.put((K) "one", (V) "uno"); - map.put((K) "one", (V) "un"); - map.put((K) "two", (V) "dos"); - map.put((K) "two", (V) "deux"); - map.put((K) "three", (V) "tres"); - map.put((K) "three", (V) "trois"); + final K[] keys = getSampleKeys(); + final V[] values = getSampleValues(); + for (int i = 0; i < keys.length; i++) { + map.put(keys[i], values[i]); + } } - public void testNoMappingReturnsNull() { + /** + * Override to return a MultiValuedMap other than MultiValuedHashMap as the + * confirmed map. + * + * @return a MultiValuedMap that is known to be valid + */ + public MultiValuedMap makeConfirmedMap() { + return new MultiValuedHashMap(); + } + + public MultiValuedMap getConfirmed() { + return this.confirmed; + } + + public void setConfirmed(MultiValuedMap map) { + this.confirmed = map; + } + + public MultiValuedMap getMap() { + return this.map; + } + + /** + * Resets the {@link #map} and {@link #confirmed} fields to empty. + */ + public void resetEmpty() { + this.map = makeObject(); + this.confirmed = makeConfirmedMap(); + } + + /** + * Resets the {@link #map} and {@link #confirmed} fields to full. + */ + public void resetFull() { + this.map = makeFullMap(); + this.confirmed = makeConfirmedMap(); + final K[] k = getSampleKeys(); + final V[] v = getSampleValues(); + for (int i = 0; i < k.length; i++) { + confirmed.put(k[i], v[i]); + } + } + + public void testNoMappingReturnsEmptyCol() { final MultiValuedMap map = makeFullMap(); - assertNull(map.get("whatever")); + assertTrue(map.get("whatever").isEmpty()); } public void testMultipleValues() { @@ -114,6 +212,69 @@ public abstract class AbstractMultiValuedMapTest extends AbstractObjectTes assertTrue(map.get("three").contains("trois")); } + @SuppressWarnings("unchecked") + public void testAddMappingThroughGet(){ + if (!isAddSupported()) { + return; + } + resetEmpty(); + final MultiValuedMap map = getMap(); + Collection col1 = map.get("one"); + Collection col2 = map.get("one"); + assertTrue(col1.isEmpty()); + assertTrue(col2.isEmpty()); + assertEquals(0, map.size()); + col1.add((V) "uno"); + col2.add((V) "un"); + assertTrue(map.containsKey("one")); + assertTrue(map.containsMapping("one", "uno")); + assertTrue(map.containsMapping("one", "un")); + assertTrue(map.containsValue("uno")); + assertTrue(map.containsValue("un")); + assertTrue(col1.contains("un")); + assertTrue(col2.contains("uno")); + } + + public void testRemoveMappingThroughGet() { + if (!isRemoveSupported()) { + return; + } + resetFull(); + final MultiValuedMap map = getMap(); + Collection col = map.get("one"); + assertEquals(2, col.size()); + assertEquals(6, map.size()); + col.remove("uno"); + col.remove("un"); + assertFalse(map.containsKey("one")); + assertFalse(map.containsMapping("one", "uno")); + assertFalse(map.containsMapping("one", "un")); + assertFalse(map.containsValue("uno")); + assertFalse(map.containsValue("un")); + assertEquals(4, map.size()); + assertNull(map.remove("one")); + } + + public void testRemoveMappingThroughGetIterator() { + if (!isRemoveSupported()) { + return; + } + resetFull(); + final MultiValuedMap map = getMap(); + Iterator it = map.get("one").iterator(); + while (it.hasNext()) { + it.next(); + it.remove(); + } + assertFalse(map.containsKey("one")); + assertFalse(map.containsMapping("one", "uno")); + assertFalse(map.containsMapping("one", "un")); + assertFalse(map.containsValue("uno")); + assertFalse(map.containsValue("un")); + assertEquals(4, map.size()); + assertNull(map.remove("one")); + } + public void testContainsValue() { final MultiValuedMap map = makeFullMap(); assertTrue(map.containsValue("uno")); @@ -158,7 +319,7 @@ public abstract class AbstractMultiValuedMapTest extends AbstractObjectTes // assertEquals(expected, actual); // } - public void testRemoveAllViaIterator() { + public void testRemoveAllViaValuesIterator() { if (!isRemoveSupported()) { return; } @@ -167,10 +328,34 @@ public abstract class AbstractMultiValuedMapTest extends AbstractObjectTes i.next(); i.remove(); } - assertNull(map.get("one")); + assertTrue(map.get("one").isEmpty()); assertTrue(map.isEmpty()); } + public void testRemoveViaValuesRemove() { + if (!isRemoveSupported()) { + return; + } + final MultiValuedMap map = makeFullMap(); + Collection values = map.values(); + values.remove("uno"); + values.remove("un"); + assertFalse(map.containsKey("one")); + assertEquals(4, map.size()); + } + + /*public void testRemoveViaGetCollectionRemove() { + if (!isRemoveSupported()) { + return; + } + final MultiValuedMap map = makeFullMap(); + Collection values = map.get("one"); + values.remove("uno"); + values.remove("un"); + assertFalse(map.containsKey("one")); + assertEquals(4, map.size()); + }*/ + // public void testRemoveAllViaKeyedIterator() { // if (!isRemoveSupported()) { // return; @@ -210,7 +395,7 @@ public abstract class AbstractMultiValuedMapTest extends AbstractObjectTes i.next(); i.remove(); } - assertNull(map.get("one")); + assertTrue(map.get("one").isEmpty()); assertEquals(0, map.size()); } @@ -459,11 +644,93 @@ public abstract class AbstractMultiValuedMapTest extends AbstractObjectTes assertTrue(keyBag.containsAll(col)); } -// public void testMapEqulas() { -// MultiValuedMap map1 = makeFullMap(); -// MultiValuedMap map2 = makeFullMap(); -// assertEquals(true, map1.equals(map2)); -// } + public void testAsMapGet() { + resetEmpty(); + Map> mapCol = getMap().asMap(); + assertNull(mapCol.get("one")); + assertEquals(0, mapCol.size()); + + resetFull(); + mapCol = getMap().asMap(); + Collection col = mapCol.get("one"); + assertNotNull(col); + assertTrue(col.contains("un")); + assertTrue(col.contains("uno")); + } + + @SuppressWarnings("unchecked") + public void testAsMapPut() { + if (!isAddSupported()) { + return; + } + resetEmpty(); + Map> mapCol = getMap().asMap(); + Collection col = (Collection) Arrays.asList("un", "uno"); + mapCol.put((K) "one", col); + assertEquals(2, getMap().size()); + assertTrue(getMap().containsKey("one")); + assertTrue(getMap().containsValue("un")); + assertTrue(getMap().containsValue("uno")); + + resetFull(); + mapCol = getMap().asMap(); + col = mapCol.get("one"); + col.add((V) "one"); + assertEquals(7, getMap().size()); + assertTrue(getMap().containsValue("one")); + assertTrue(getMap().containsValue("un")); + assertTrue(getMap().containsValue("uno")); + } + + public void testAsMapRemove() { + if (!isRemoveSupported()) { + return; + } + resetFull(); + Map> mapCol = getMap().asMap(); + mapCol.remove("one"); + assertFalse(getMap().containsKey("one")); + assertEquals(4, getMap().size()); + } + + public void testMapIterator() { + resetEmpty(); + MapIterator mapIt = getMap().mapIterator(); + assertFalse(mapIt.hasNext()); + + resetFull(); + mapIt = getMap().mapIterator(); + while (mapIt.hasNext()) { + K key = mapIt.next(); + V value = mapIt.getValue(); + assertTrue(getMap().containsMapping(key, value)); + } + } + + public void testMapIteratorRemove() { + if (!isRemoveSupported()) { + return; + } + resetFull(); + MapIterator mapIt = getMap().mapIterator(); + while (mapIt.hasNext()) { + mapIt.next(); + mapIt.remove(); + } + assertTrue(getMap().isEmpty()); + } + + @SuppressWarnings("unchecked") + public void testMapIteratorUnsupportedSet() { + resetFull(); + MapIterator mapIt = getMap().mapIterator(); + mapIt.next(); + try { + mapIt.setValue((V) "some value"); + fail(); + } catch (UnsupportedOperationException e) { + } + } // ----------------------------------------------------------------------- // Manual serialization testing as this class cannot easily @@ -472,13 +739,15 @@ public abstract class AbstractMultiValuedMapTest extends AbstractObjectTes public void testEmptyMapCompatibility() throws Exception { final MultiValuedMap map = makeObject(); - final MultiValuedMap map2 = (MultiValuedMap) readExternalFormFromDisk(getCanonicalEmptyCollectionName(map)); + final MultiValuedMap map2 = + (MultiValuedMap) readExternalFormFromDisk(getCanonicalEmptyCollectionName(map)); assertEquals("Map is empty", 0, map2.size()); } public void testFullMapCompatibility() throws Exception { final MultiValuedMap map = (MultiValuedMap) makeFullMap(); - final MultiValuedMap map2 = (MultiValuedMap) readExternalFormFromDisk(getCanonicalFullCollectionName(map)); + final MultiValuedMap map2 = + (MultiValuedMap) readExternalFormFromDisk(getCanonicalFullCollectionName(map)); assertEquals("Map is the right size", map.size(), map2.size()); for (final Object key : map.keySet()) { assertEquals("Map had inequal elements", map.get(key), map2.get(key)); @@ -491,4 +760,371 @@ public abstract class AbstractMultiValuedMapTest extends AbstractObjectTes } } + // Bulk Tests + /** + * Bulk test {@link MultiValuedMap#entries()}. This method runs through all + * of the tests in {@link AbstractCollectionTest}. After modification + * operations, {@link #verify()} is invoked to ensure that the map and the + * other collection views are still valid. + * + * @return a {@link AbstractCollectionTest} instance for testing the map's + * values collection + */ + public BulkTest bulkTestMultiValuedMapEntries() { + return new TestMultiValuedMapEntries(); + } + + public class TestMultiValuedMapEntries extends AbstractCollectionTest> { + public TestMultiValuedMapEntries() { + super(""); + } + + @SuppressWarnings("unchecked") + @Override + public Entry[] getFullElements() { + return makeFullMap().entries().toArray(new Entry[0]); + } + + @Override + public Collection> makeObject() { + return AbstractMultiValuedMapTest.this.makeObject().entries(); + } + + @Override + public Collection> makeFullCollection() { + return AbstractMultiValuedMapTest.this.makeFullMap().entries(); + } + + @Override + public boolean isNullSupported() { + return AbstractMultiValuedMapTest.this.isAllowNullKey(); + } + + @Override + public boolean isAddSupported() { + // Add not supported in entries view + return false; + } + + @Override + public boolean isRemoveSupported() { + return AbstractMultiValuedMapTest.this.isRemoveSupported(); + } + + @Override + public boolean isTestSerialization() { + return false; + } + + @Override + public void resetFull() { + AbstractMultiValuedMapTest.this.resetFull(); + setCollection(AbstractMultiValuedMapTest.this.getMap().entries()); + TestMultiValuedMapEntries.this.setConfirmed(AbstractMultiValuedMapTest.this.getConfirmed().entries()); + } + + @Override + public void resetEmpty() { + AbstractMultiValuedMapTest.this.resetEmpty(); + setCollection(AbstractMultiValuedMapTest.this.getMap().entries()); + TestMultiValuedMapEntries.this.setConfirmed(AbstractMultiValuedMapTest.this.getConfirmed().entries()); + } + + @Override + public Collection> makeConfirmedCollection() { + // never gets called, reset methods are overridden + return null; + } + + @Override + public Collection> makeConfirmedFullCollection() { + // never gets called, reset methods are overridden + return null; + } + + } + + /** + * Bulk test {@link MultiValuedMap#keySet()}. This method runs through all + * of the tests in {@link AbstractSetTest}. After modification operations, + * {@link #verify()} is invoked to ensure that the map and the other + * collection views are still valid. + * + * @return a {@link AbstractSetTest} instance for testing the map's key set + */ + public BulkTest bulkTestMultiValuedMapKeySet() { + return new TestMultiValuedMapKeySet(); + } + + public class TestMultiValuedMapKeySet extends AbstractSetTest { + public TestMultiValuedMapKeySet() { + super(""); + } + + @SuppressWarnings("unchecked") + @Override + public K[] getFullElements() { + return (K[]) AbstractMultiValuedMapTest.this.makeFullMap().keySet().toArray(); + } + + @Override + public Set makeObject() { + return AbstractMultiValuedMapTest.this.makeObject().keySet(); + } + + @Override + public Set makeFullCollection() { + return AbstractMultiValuedMapTest.this.makeFullMap().keySet(); + } + + @Override + public boolean isNullSupported() { + return AbstractMultiValuedMapTest.this.isAllowNullKey(); + } + + @Override + public boolean isAddSupported() { + return false; + } + + @Override + public boolean isRemoveSupported() { + return AbstractMultiValuedMapTest.this.isRemoveSupported(); + } + + @Override + public boolean isTestSerialization() { + return false; + } + + } + + /** + * Bulk test {@link MultiValuedMap#values()}. This method runs through all + * of the tests in {@link AbstractCollectionTest}. After modification + * operations, {@link #verify()} is invoked to ensure that the map and the + * other collection views are still valid. + * + * @return a {@link AbstractCollectionTest} instance for testing the map's + * values collection + */ + public BulkTest bulkTestMultiValuedMapValues() { + return new TestMultiValuedMapValues(); + } + + public class TestMultiValuedMapValues extends AbstractCollectionTest { + public TestMultiValuedMapValues() { + super(""); + } + + @Override + public V[] getFullElements() { + return getSampleValues(); + } + + @Override + public Collection makeObject() { + return AbstractMultiValuedMapTest.this.makeObject().values(); + } + + @Override + public Collection makeFullCollection() { + return AbstractMultiValuedMapTest.this.makeFullMap().values(); + } + + @Override + public boolean isNullSupported() { + return AbstractMultiValuedMapTest.this.isAllowNullKey(); + } + + @Override + public boolean isAddSupported() { + return false; + } + + @Override + public boolean isRemoveSupported() { + return AbstractMultiValuedMapTest.this.isRemoveSupported(); + } + + @Override + public boolean isTestSerialization() { + return false; + } + + @Override + public void resetFull() { + AbstractMultiValuedMapTest.this.resetFull(); + setCollection(AbstractMultiValuedMapTest.this.getMap().values()); + TestMultiValuedMapValues.this.setConfirmed(AbstractMultiValuedMapTest.this.getConfirmed().values()); + } + + @Override + public void resetEmpty() { + AbstractMultiValuedMapTest.this.resetEmpty(); + setCollection(AbstractMultiValuedMapTest.this.getMap().values()); + TestMultiValuedMapValues.this.setConfirmed(AbstractMultiValuedMapTest.this.getConfirmed().values()); + } + + @Override + public Collection makeConfirmedCollection() { + // never gets called, reset methods are overridden + return null; + } + + @Override + public Collection makeConfirmedFullCollection() { + // never gets called, reset methods are overridden + return null; + } + + } + + /** + * Bulk test {@link MultiValuedMap#keys()}. This method runs through all of + * the tests in {@link AbstractBagTest}. After modification operations, + * {@link #verify()} is invoked to ensure that the map and the other + * collection views are still valid. + * + * @return a {@link AbstractBagTest} instance for testing the map's values + * collection + */ + public BulkTest bulkTestMultiValuedMapKeys() { + return new TestMultiValuedMapKeys(); + } + + public class TestMultiValuedMapKeys extends AbstractBagTest { + + public TestMultiValuedMapKeys() { + super(""); + } + + @Override + public K[] getFullElements() { + return getSampleKeys(); + } + + @Override + public Bag makeObject() { + return AbstractMultiValuedMapTest.this.makeObject().keys(); + } + + @Override + public Bag makeFullCollection() { + return AbstractMultiValuedMapTest.this.makeFullMap().keys(); + } + + @Override + public boolean isNullSupported() { + return AbstractMultiValuedMapTest.this.isAllowNullKey(); + } + + @Override + public boolean isAddSupported() { + return false; + } + + @Override + public boolean isRemoveSupported() { + return false; + } + + @Override + public boolean isTestSerialization() { + return false; + } + + @Override + public void resetFull() { + AbstractMultiValuedMapTest.this.resetFull(); + // wrapping with CollectionBag as otherwise the Collection tests + // would fail + setCollection(CollectionBag.collectionBag(AbstractMultiValuedMapTest.this.getMap().keys())); + TestMultiValuedMapKeys.this.setConfirmed(AbstractMultiValuedMapTest.this.getConfirmed().keys()); + } + + @Override + public void resetEmpty() { + AbstractMultiValuedMapTest.this.resetEmpty(); + setCollection(CollectionBag.collectionBag(AbstractMultiValuedMapTest.this.getMap().keys())); + TestMultiValuedMapKeys.this.setConfirmed(AbstractMultiValuedMapTest.this.getConfirmed().keys()); + } + + } + + public BulkTest bulkTestAsMap() { + return new TestMultiValuedMapAsMap(); + } + + public class TestMultiValuedMapAsMap extends AbstractMapTest> { + + public TestMultiValuedMapAsMap() { + super(""); + } + + @Override + public Map> makeObject() { + return AbstractMultiValuedMapTest.this.makeObject().asMap(); + } + + public Map> makeFullMap() { + return AbstractMultiValuedMapTest.this.makeFullMap().asMap(); + } + + @SuppressWarnings("unchecked") + public K[] getSampleKeys() { + K[] samplekeys = AbstractMultiValuedMapTest.this.getSampleKeys(); + Object[] finalKeys = new Object[3]; + for (int i = 0; i < 3; i++) { + finalKeys[i] = samplekeys[i * 2]; + } + return (K[]) finalKeys; + } + + @SuppressWarnings("unchecked") + public Collection[] getSampleValues() { + V[] sampleValues = AbstractMultiValuedMapTest.this.getSampleValues(); + Collection[] colArr = new Collection[3]; + for(int i = 0; i < 3; i++) { + colArr[i] = Arrays.asList(sampleValues[i*2], sampleValues[i*2 + 1]); + } + return colArr; + } + + @SuppressWarnings("unchecked") + public Collection[] getNewSampleValues() { + Object[] sampleValues = { "ein", "ek", "zwei", "duey", "drei", "teen" }; + Collection[] colArr = new Collection[3]; + for (int i = 0; i < 3; i++) { + colArr[i] = Arrays.asList((V) sampleValues[i * 2], (V) sampleValues[i * 2 + 1]); + } + return colArr; + } + + @Override + public boolean isAllowNullKey() { + return AbstractMultiValuedMapTest.this.isAllowNullKey(); + } + + @Override + public boolean isPutAddSupported() { + return AbstractMultiValuedMapTest.this.isAddSupported(); + } + + @Override + public boolean isPutChangeSupported() { + return AbstractMultiValuedMapTest.this.isAddSupported(); + } + + @Override + public boolean isRemoveSupported() { + return AbstractMultiValuedMapTest.this.isRemoveSupported(); + } + + @Override + public boolean isTestSerialization() { + return false; + } + + } } diff --git a/src/test/java/org/apache/commons/collections4/multimap/MultiValuedHashMapTest.java b/src/test/java/org/apache/commons/collections4/multimap/MultiValuedHashMapTest.java index 81cfd9503..ede566faa 100644 --- a/src/test/java/org/apache/commons/collections4/multimap/MultiValuedHashMapTest.java +++ b/src/test/java/org/apache/commons/collections4/multimap/MultiValuedHashMapTest.java @@ -17,10 +17,11 @@ package org.apache.commons.collections4.multimap; import java.util.ArrayList; -import java.util.Collection; import java.util.HashSet; -import java.util.LinkedList; +import junit.framework.Test; + +import org.apache.commons.collections4.BulkTest; import org.apache.commons.collections4.MultiValuedMap; /** @@ -35,13 +36,17 @@ public class MultiValuedHashMapTest extends AbstractMultiValuedMapTest makeObject() { final MultiValuedMap m = new MultiValuedHashMap(); return m; } - private > MultiValuedHashMap createTestMap(final Class collectionClass) { + /*private > MultiValuedHashMap createTestMap(final Class collectionClass) { final MultiValuedHashMap map = (MultiValuedHashMap) MultiValuedHashMap. multiValuedMap(collectionClass); addSampleMappings(map); @@ -52,26 +57,28 @@ public class MultiValuedHashMapTest extends AbstractMultiValuedMapTest map = createTestMap(LinkedList.class); assertTrue(map.get("one") instanceof LinkedList); - } + }*/ @SuppressWarnings("unchecked") public void testPutWithList() { - final MultiValuedHashMap test = (MultiValuedHashMap) MultiValuedHashMap.multiValuedMap(ArrayList.class); + final MultiValuedHashMap test = + (MultiValuedHashMap) MultiValuedHashMap.multiValuedMap(ArrayList.class); assertEquals("a", test.put((K) "A", (V) "a")); assertEquals("b", test.put((K) "A", (V) "b")); assertEquals(1, test.keySet().size()); - assertEquals(2, test.size("A")); + assertEquals(2, test.get("A").size()); assertEquals(2, test.size()); } @SuppressWarnings("unchecked") public void testPutWithSet() { - final MultiValuedHashMap test = (MultiValuedHashMap) MultiValuedHashMap.multiValuedMap(HashSet.class); + final MultiValuedHashMap test = + (MultiValuedHashMap) MultiValuedHashMap.multiValuedMap(HashSet.class); assertEquals("a", test.put((K) "A", (V) "a")); assertEquals("b", test.put((K) "A", (V) "b")); assertEquals(null, test.put((K) "A", (V) "a")); assertEquals(1, test.keySet().size()); - assertEquals(2, test.size("A")); + assertEquals(2, test.get("A").size()); assertEquals(2, test.size()); } diff --git a/src/test/java/org/apache/commons/collections4/multimap/TransformedMultiValuedMapTest.java b/src/test/java/org/apache/commons/collections4/multimap/TransformedMultiValuedMapTest.java index 46a8a7cb8..38f777372 100644 --- a/src/test/java/org/apache/commons/collections4/multimap/TransformedMultiValuedMapTest.java +++ b/src/test/java/org/apache/commons/collections4/multimap/TransformedMultiValuedMapTest.java @@ -16,6 +16,9 @@ */ package org.apache.commons.collections4.multimap; +import junit.framework.Test; + +import org.apache.commons.collections4.BulkTest; import org.apache.commons.collections4.MultiValuedMap; import org.apache.commons.collections4.Transformer; import org.apache.commons.collections4.TransformerUtils; @@ -34,6 +37,10 @@ public class TransformedMultiValuedMapTest extends AbstractMultiValuedMapT super(testName); } + public static Test suite() { + return BulkTest.makeSuite(TransformedMultiValuedMapTest.class); + } + @Override public MultiValuedMap makeObject() { return TransformedMultiValuedMap.transformingMap(new MultiValuedHashMap(), diff --git a/src/test/java/org/apache/commons/collections4/multimap/UnmodifiableMultiValuedMapTest.java b/src/test/java/org/apache/commons/collections4/multimap/UnmodifiableMultiValuedMapTest.java index eec85c5fe..84f9ae842 100644 --- a/src/test/java/org/apache/commons/collections4/multimap/UnmodifiableMultiValuedMapTest.java +++ b/src/test/java/org/apache/commons/collections4/multimap/UnmodifiableMultiValuedMapTest.java @@ -16,6 +16,18 @@ */ package org.apache.commons.collections4.multimap; +import java.util.Arrays; +import java.util.Collection; +import java.util.Iterator; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; + +import junit.framework.Test; + +import org.apache.commons.collections4.Bag; +import org.apache.commons.collections4.BulkTest; +import org.apache.commons.collections4.MapIterator; import org.apache.commons.collections4.MultiValuedMap; import org.apache.commons.collections4.Unmodifiable; @@ -31,6 +43,10 @@ public class UnmodifiableMultiValuedMapTest extends AbstractMultiValuedMap super(testName); } + public static Test suite() { + return BulkTest.makeSuite(UnmodifiableMultiValuedMapTest.class); + } + public boolean isAddSupported() { return false; } @@ -78,6 +94,167 @@ public class UnmodifiableMultiValuedMapTest extends AbstractMultiValuedMap } } + @SuppressWarnings("unchecked") + public void testUnmodifiableEntries() { + resetFull(); + Collection> entries = getMap().entries(); + try { + entries.clear(); + fail(); + } catch (UnsupportedOperationException e) { + } + + Iterator> it = entries.iterator(); + Entry entry = it.next(); + try { + it.remove(); + fail(); + } catch (UnsupportedOperationException e) { + } + + try { + entry.setValue((V) "three"); + fail(); + } catch (UnsupportedOperationException e) { + } + } + + @SuppressWarnings("unchecked") + public void testUnmodifiableMapIterator() { + resetFull(); + MapIterator mapIt = getMap().mapIterator(); + try { + mapIt.remove(); + fail(); + } catch (UnsupportedOperationException e) { + } + + try { + mapIt.setValue((V) "three"); + fail(); + } catch (UnsupportedOperationException e) { + } + } + + @SuppressWarnings("unchecked") + public void testUnmodifiableKeySet() { + resetFull(); + Set keySet = getMap().keySet(); + try { + keySet.add((K) "four"); + fail(); + } catch (UnsupportedOperationException e) { + } + + try { + keySet.remove("four"); + fail(); + } catch (UnsupportedOperationException e) { + } + + try { + keySet.clear(); + fail(); + } catch (UnsupportedOperationException e) { + } + + Iterator it = keySet.iterator(); + try { + it.remove(); + fail(); + } catch (UnsupportedOperationException e) { + } + } + + @SuppressWarnings("unchecked") + public void testUnmodifiableValues() { + resetFull(); + Collection values = getMap().values(); + try { + values.add((V) "four"); + fail(); + } catch (UnsupportedOperationException e) { + } + + try { + values.remove("four"); + fail(); + } catch (UnsupportedOperationException e) { + } + + try { + values.clear(); + fail(); + } catch (UnsupportedOperationException e) { + } + + Iterator it = values.iterator(); + try { + it.remove(); + fail(); + } catch (UnsupportedOperationException e) { + } + } + + @SuppressWarnings("unchecked") + public void testUnmodifiableAsMap() { + resetFull(); + Map> mapCol = getMap().asMap(); + try { + mapCol.put((K) "four", (Collection) Arrays.asList("four")); + fail(); + } catch (UnsupportedOperationException e) { + } + + try { + mapCol.remove("four"); + fail(); + } catch (UnsupportedOperationException e) { + } + + try { + mapCol.clear(); + fail(); + } catch (UnsupportedOperationException e) { + } + + try { + mapCol.clear(); + fail(); + } catch (UnsupportedOperationException e) { + } + } + + @SuppressWarnings("unchecked") + public void testUnmodifiableKeys() { + resetFull(); + Bag keys = getMap().keys(); + try { + keys.add((K) "four"); + fail(); + } catch (UnsupportedOperationException e) { + } + + try { + keys.remove("four"); + fail(); + } catch (UnsupportedOperationException e) { + } + + try { + keys.clear(); + fail(); + } catch (UnsupportedOperationException e) { + } + + Iterator it = keys.iterator(); + try { + it.remove(); + fail(); + } catch (UnsupportedOperationException e) { + } + } + // public void testCreate() throws Exception { // writeExternalFormToDisk((java.io.Serializable) makeObject(), // "src/test/resources/data/test/UnmodifiableMultiValuedMap.emptyCollection.version4.1.obj");