You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@commons.apache.org by tn...@apache.org on 2014/04/06 21:58:38 UTC

svn commit: r1585335 - in /commons/proper/collections/trunk/src: main/java/org/apache/commons/collections4/ main/java/org/apache/commons/collections4/multimap/ test/java/org/apache/commons/collections4/collection/ test/java/org/apache/commons/collectio...

Author: tn
Date: Sun Apr  6 19:58:37 2014
New Revision: 1585335

URL: http://svn.apache.org/r1585335
Log:
[COLLECTIONS-508] Improved original contribution after feedback. Thanks to Dipanjan Laha.

Added:
    commons/proper/collections/trunk/src/main/java/org/apache/commons/collections4/SetValuedMap.java   (with props)
Modified:
    commons/proper/collections/trunk/src/main/java/org/apache/commons/collections4/MultiValuedMap.java
    commons/proper/collections/trunk/src/main/java/org/apache/commons/collections4/multimap/AbstractMultiValuedMap.java
    commons/proper/collections/trunk/src/main/java/org/apache/commons/collections4/multimap/AbstractMultiValuedMapDecorator.java
    commons/proper/collections/trunk/src/main/java/org/apache/commons/collections4/multimap/MultiValuedHashMap.java
    commons/proper/collections/trunk/src/main/java/org/apache/commons/collections4/multimap/UnmodifiableMultiValuedMap.java
    commons/proper/collections/trunk/src/test/java/org/apache/commons/collections4/collection/AbstractCollectionTest.java
    commons/proper/collections/trunk/src/test/java/org/apache/commons/collections4/multimap/AbstractMultiValuedMapTest.java
    commons/proper/collections/trunk/src/test/java/org/apache/commons/collections4/multimap/MultiValuedHashMapTest.java
    commons/proper/collections/trunk/src/test/java/org/apache/commons/collections4/multimap/TransformedMultiValuedMapTest.java
    commons/proper/collections/trunk/src/test/java/org/apache/commons/collections4/multimap/UnmodifiableMultiValuedMapTest.java

Modified: commons/proper/collections/trunk/src/main/java/org/apache/commons/collections4/MultiValuedMap.java
URL: http://svn.apache.org/viewvc/commons/proper/collections/trunk/src/main/java/org/apache/commons/collections4/MultiValuedMap.java?rev=1585335&r1=1585334&r2=1585335&view=diff
==============================================================================
--- commons/proper/collections/trunk/src/main/java/org/apache/commons/collections4/MultiValuedMap.java (original)
+++ commons/proper/collections/trunk/src/main/java/org/apache/commons/collections4/MultiValuedMap.java Sun Apr  6 19:58:37 2014
@@ -284,4 +284,24 @@ public interface MultiValuedMap<K, V> {
      */
     Collection<V> 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<K, Collection<V>> asMap();
+
+    // Iterators
+
+    /**
+     * Obtains a <code>MapIterator</code> over the map.
+     * <p>
+     * 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<K, V> mapIterator();
+
 }

Added: commons/proper/collections/trunk/src/main/java/org/apache/commons/collections4/SetValuedMap.java
URL: http://svn.apache.org/viewvc/commons/proper/collections/trunk/src/main/java/org/apache/commons/collections4/SetValuedMap.java?rev=1585335&view=auto
==============================================================================
--- commons/proper/collections/trunk/src/main/java/org/apache/commons/collections4/SetValuedMap.java (added)
+++ commons/proper/collections/trunk/src/main/java/org/apache/commons/collections4/SetValuedMap.java Sun Apr  6 19:58:37 2014
@@ -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.
+ * <p>
+ * A <code>SetValuedMap</code> is a Map with slightly different semantics:
+ * <ul>
+ * <li>Putting a value into the map will add the value to a <code>Set</code> at
+ * that key.</li>
+ * <li>Getting a value will return a <code>Set</code>, holding all the values
+ * put to that key.</li>
+ * </ul>
+ *
+ * @since 4.1
+ * @version $Id$
+ */
+public interface SetValuedMap<K, V> extends MultiValuedMap<K, V> {
+
+    /**
+     * Gets the set of values associated with the specified key.
+     * <p>
+     * Implementations typically return <code>null</code> if no values have been
+     * mapped to the key, however the implementation may choose to return an
+     * empty collection.
+     * <p>
+     * Implementations may choose to return a clone of the internal collection.
+     *
+     * @param key the key to retrieve
+     * @return the <code>Set</code> of values, implementations should return
+     *         <code>null</code> 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<V> get(Object key);
+
+    /**
+     * Removes all values associated with the specified key.
+     * <p>
+     * Implementations typically return <code>null</code> from a subsequent
+     * <code>get(Object)</code>, however they may choose to return an empty
+     * collection.
+     *
+     * @param key the key to remove values from
+     * @return the <code>Set</code> of values removed, implementations should
+     *         return <code>null</code> 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<V> remove(Object key);
+}

Propchange: commons/proper/collections/trunk/src/main/java/org/apache/commons/collections4/SetValuedMap.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: commons/proper/collections/trunk/src/main/java/org/apache/commons/collections4/SetValuedMap.java
------------------------------------------------------------------------------
    svn:keywords = Id Revision HeadURL

Propchange: commons/proper/collections/trunk/src/main/java/org/apache/commons/collections4/SetValuedMap.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Modified: commons/proper/collections/trunk/src/main/java/org/apache/commons/collections4/multimap/AbstractMultiValuedMap.java
URL: http://svn.apache.org/viewvc/commons/proper/collections/trunk/src/main/java/org/apache/commons/collections4/multimap/AbstractMultiValuedMap.java?rev=1585335&r1=1585334&r2=1585335&view=diff
==============================================================================
--- commons/proper/collections/trunk/src/main/java/org/apache/commons/collections4/multimap/AbstractMultiValuedMap.java (original)
+++ commons/proper/collections/trunk/src/main/java/org/apache/commons/collections4/multimap/AbstractMultiValuedMap.java Sun Apr  6 19:58:37 2014
@@ -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
@@ -85,6 +90,30 @@ public class AbstractMultiValuedMap<K, V
     }
 
     /**
+     * Constructor that wraps (not copies).
+     *
+     * @param <C> 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 <C extends Collection<V>> AbstractMultiValuedMap(final Map<K, ? super C> map,
+            int initialCollectionCapacity, final Class<C> collectionClazz) {
+        if (map == null) {
+            throw new IllegalArgumentException("Map must not be null");
+        }
+        if (initialCollectionCapacity < 0) {
+            throw new IllegalArgumentException("Illegal Capacity: " + initialCollectionCapacity);
+        }
+        this.map = (Map<K, Collection<V>>) map;
+        this.collectionFactory = new InstantiateFactory<C>(collectionClazz, new Class[] { Integer.TYPE },
+                new Object[] { new Integer(initialCollectionCapacity) });
+    }
+
+    /**
      * Gets the map being wrapped.
      *
      * @return the wrapped map
@@ -119,7 +148,7 @@ public class AbstractMultiValuedMap<K, V
      * {@inheritDoc}
      */
     public boolean containsMapping(Object key, Object value) {
-        final Collection<V> col = get(key);
+        final Collection<V> col = getMap().get(key);
         if (col == null) {
             return false;
         }
@@ -134,15 +163,16 @@ public class AbstractMultiValuedMap<K, V
     }
 
     /**
-     * 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 <code>Collection</code> of values, will return
-     *         <code>null</code> for no mapping
+     * @return the <code>Collection</code> of values, will return an empty
+     *         <code>Collection</code> for no mapping
      * @throws ClassCastException if the key is of an invalid type
      */
     public Collection<V> get(Object key) {
-        return getMap().get(key);
+        return new WrappedCollection(key);
     }
 
     /**
@@ -174,7 +204,7 @@ public class AbstractMultiValuedMap<K, V
      */
     public boolean removeMapping(K key, V item) {
         boolean result = false;
-        final Collection<V> col = get(key);
+        final Collection<V> col = getMap().get(key);
         if (col == null) {
             return false;
         }
@@ -245,7 +275,7 @@ public class AbstractMultiValuedMap<K, V
      */
     public V put(K key, V value) {
         boolean result = false;
-        Collection<V> coll = get(key);
+        Collection<V> coll = getMap().get(key);
         if (coll == null) {
             coll = createCollection();
             coll.add(value);
@@ -313,6 +343,13 @@ public class AbstractMultiValuedMap<K, V
     }
 
     /**
+     * {@inheritDoc}
+     */
+    public Map<K, Collection<V>> asMap() {
+        return getMap();
+    }
+
+    /**
      * Adds Iterable values to the collection associated with the specified key.
      *
      * @param key the key to store against
@@ -326,7 +363,7 @@ public class AbstractMultiValuedMap<K, V
         }
         Iterator<? extends V> it = values.iterator();
         boolean result = false;
-        Collection<V> coll = get(key);
+        Collection<V> 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<K, V
     }
 
     /**
-     * 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
-     */
-    public Iterator<V> iterator(final Object key) {
-        if (!containsKey(key)) {
-            return EmptyIterator.<V> emptyIterator();
-        }
-        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
+     * {@inheritDoc}
      */
-    public int size(final Object key) {
-        final Collection<V> coll = get(key);
-        if (coll == null) {
-            return 0;
+    public MapIterator<K, V> mapIterator() {
+        if (size() == 0) {
+            return EmptyMapIterator.<K, V>emptyMapIterator();
         }
-        return coll.size();
+        return new MultiValuedMapIterator();
     }
 
     @SuppressWarnings("rawtypes")
@@ -433,6 +452,190 @@ public class AbstractMultiValuedMap<K, V
     // -----------------------------------------------------------------------
 
     /**
+     * Wrapped collection to handle add and remove on the collection returned by get(object)
+     */
+    private class WrappedCollection implements Collection<V> {
+
+        private final Object key;
+
+        public WrappedCollection(Object key) {
+            this.key = key;
+        }
+
+        private Collection<V> getMapping() {
+            return getMap().get(key);
+        }
+
+        @SuppressWarnings("unchecked")
+        public boolean add(V value) {
+            final Collection<V> 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<? extends V> c) {
+            final Collection<V> col = getMapping();
+            if (col == null) {
+                return AbstractMultiValuedMap.this.putAll((K) key, c);
+            }
+            return col.addAll(c);
+        }
+
+        public void clear() {
+            final Collection<V> col = getMapping();
+            if (col != null) {
+                col.clear();
+                AbstractMultiValuedMap.this.remove(key);
+            }
+        }
+
+        @SuppressWarnings("unchecked")
+        public Iterator<V> iterator() {
+            final Collection<V> col = getMapping();
+            if (col == null) {
+                return (Iterator<V>) IteratorUtils.EMPTY_ITERATOR;
+            }
+            return new ValuesIterator(key);
+        }
+
+        public int size() {
+            final Collection<V> col = getMapping();
+            if (col == null) {
+                return 0;
+            }
+            return col.size();
+        }
+
+        public boolean contains(Object o) {
+            final Collection<V> col = getMapping();
+            if (col == null) {
+                return false;
+            }
+            return col.contains(o);
+        }
+
+        public boolean containsAll(Collection<?> o) {
+            final Collection<V> col = getMapping();
+            if (col == null) {
+                return false;
+            }
+            return col.containsAll(o);
+        }
+
+        public boolean isEmpty() {
+            final Collection<V> col = getMapping();
+            if (col == null) {
+                return true;
+            }
+            return col.isEmpty();
+        }
+
+        public boolean remove(Object item) {
+            final Collection<V> 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<V> 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<V> 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<V> col = getMapping();
+            if (col == null) {
+                return CollectionUtils.EMPTY_COLLECTION.toArray();
+            }
+            return col.toArray();
+        }
+
+        @SuppressWarnings("unchecked")
+        public <T> T[] toArray(T[] a) {
+            final Collection<V> 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<V> 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<V> col = getMapping();
+            if (col == null) {
+                return CollectionUtils.EMPTY_COLLECTION.hashCode();
+            }
+            return col.hashCode();
+        }
+
+        @Override
+        public String toString() {
+            final Collection<V> col = getMapping();
+            if (col == null) {
+                return CollectionUtils.EMPTY_COLLECTION.toString();
+            }
+            return col.toString();
+        }
+
+    }
+
+    /**
      * Inner class that provides a Bag<K> keys view
      */
     private class KeysBag implements Bag<K> {
@@ -519,7 +722,7 @@ public class AbstractMultiValuedMap<K, V
         }
 
         public Set<K> uniqueSet() {
-            return keySet();
+            return UnmodifiableSet.<K>unmodifiableSet(keySet());
         }
 
         public int size() {
@@ -609,21 +812,9 @@ public class AbstractMultiValuedMap<K, V
                     final Transformer<V, Entry<K, V>> entryTransformer = new Transformer<V, Entry<K, V>>() {
 
                         public Entry<K, V> transform(final V input) {
-                            return new Entry<K, V>() {
-
-                                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<V, Entry<K, V>>(new ValuesIterator(key), entryTransformer);
                 }
@@ -638,6 +829,71 @@ public class AbstractMultiValuedMap<K, V
     }
 
     /**
+     * Inner class for MultiValuedMap Entries
+     */
+    private class MultiValuedMapEntry extends AbstractMapEntry<K, V> {
+
+        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<K, V> {
+
+        private final Iterator<Entry<K, V>> it;
+
+        private Entry<K, V> 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.
      */
     private class Values extends AbstractCollection<V> {
@@ -671,7 +927,7 @@ public class AbstractMultiValuedMap<K, V
 
         public ValuesIterator(final Object key) {
             this.key = key;
-            this.values = get(key);
+            this.values = getMap().get(key);
             this.iterator = values.iterator();
         }
 

Modified: commons/proper/collections/trunk/src/main/java/org/apache/commons/collections4/multimap/AbstractMultiValuedMapDecorator.java
URL: http://svn.apache.org/viewvc/commons/proper/collections/trunk/src/main/java/org/apache/commons/collections4/multimap/AbstractMultiValuedMapDecorator.java?rev=1585335&r1=1585334&r2=1585335&view=diff
==============================================================================
--- commons/proper/collections/trunk/src/main/java/org/apache/commons/collections4/multimap/AbstractMultiValuedMapDecorator.java (original)
+++ commons/proper/collections/trunk/src/main/java/org/apache/commons/collections4/multimap/AbstractMultiValuedMapDecorator.java Sun Apr  6 19:58:37 2014
@@ -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 <code>MultiValuedMap</code> to provide additional behaviour.
  * <p>
- * Each method call made on this <code>MultiValuedMap</code> is forwarded to the
- * decorated <code>MultiValuedMap</code>. This class is used as a framework to
- * build to extensions such as synchronized and unmodifiable behaviour.
+ * Each method call made on this <code>MultiValuedMap</code> is forwarded to the decorated <code>MultiValuedMap</code>.
+ * This class is used as a framework to build to extensions such as synchronized and unmodifiable behaviour.
  *
  * @param <K> the type of key elements
  * @param <V> the type of value elements
@@ -55,7 +55,8 @@ public class AbstractMultiValuedMapDecor
      */
     protected AbstractMultiValuedMapDecorator(final MultiValuedMap<K, V> 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 AbstractMultiValuedMapDecor
         return decorated().values();
     }
 
+    public Map<K, Collection<V>> asMap() {
+        return decorated().asMap();
+    }
+
     public boolean putAll(K key, Iterable<? extends V> values) {
         return decorated().putAll(key, values);
     }
@@ -132,6 +137,10 @@ public class AbstractMultiValuedMapDecor
         decorated().putAll(m);
     }
 
+    public MapIterator<K, V> mapIterator() {
+        return decorated().mapIterator();
+    }
+
     @Override
     public boolean equals(final Object object) {
         if (object == this) {

Modified: commons/proper/collections/trunk/src/main/java/org/apache/commons/collections4/multimap/MultiValuedHashMap.java
URL: http://svn.apache.org/viewvc/commons/proper/collections/trunk/src/main/java/org/apache/commons/collections4/multimap/MultiValuedHashMap.java?rev=1585335&r1=1585334&r2=1585335&view=diff
==============================================================================
--- commons/proper/collections/trunk/src/main/java/org/apache/commons/collections4/multimap/MultiValuedHashMap.java (original)
+++ commons/proper/collections/trunk/src/main/java/org/apache/commons/collections4/multimap/MultiValuedHashMap.java Sun Apr  6 19:58:37 2014
@@ -51,6 +51,16 @@ public class MultiValuedHashMap<K, V> ex
     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
      * <code>collectionClass</code>.
      *
@@ -62,16 +72,57 @@ public class MultiValuedHashMap<K, V> ex
      */
     public static <K, V, C extends Collection<V>> MultiValuedMap<K, V> multiValuedMap(
             final Class<C> collectionClass) {
-        return new MultiValuedHashMap<K, V>(collectionClass);
+        return new MultiValuedHashMap<K, V>(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR, collectionClass);
     }
 
     /**
-     * Creates a MultiValueMap based on a <code>HashMap</code> which stores the
-     * multiple values in an <code>ArrayList</code>.
+     * Creates a MultiValueMap based on a <code>HashMap</code> with the default
+     * initial capacity (16) and the default load factor (0.75), which stores
+     * the multiple values in an <code>ArrayList</code>.
      */
     @SuppressWarnings("unchecked")
     public MultiValuedHashMap() {
-        this(ArrayList.class);
+        this(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR, ArrayList.class);
+    }
+
+    /**
+     * Creates a MultiValueMap based on a <code>HashMap</code> with the initial
+     * capacity and the default load factor (0.75), which stores the multiple
+     * values in an <code>ArrayList</code>.
+     *
+     * @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 <code>HashMap</code> with the initial
+     * capacity and the load factor, which stores the multiple values in an
+     * <code>ArrayList</code>.
+     *
+     * @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 <code>HashMap</code> with the initial
+     * capacity and the load factor, which stores the multiple values in an
+     * <code>ArrayList</code> 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<K, V> ex
      */
     @SuppressWarnings("unchecked")
     public MultiValuedHashMap(final MultiValuedMap<? extends K, ? extends V> map) {
-        this(ArrayList.class);
+        this(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR, ArrayList.class);
         super.putAll(map);
     }
 
@@ -92,7 +143,7 @@ public class MultiValuedHashMap<K, V> ex
      */
     @SuppressWarnings("unchecked")
     public MultiValuedHashMap(final Map<? extends K, ? extends V> map) {
-        this(ArrayList.class);
+        this(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR, ArrayList.class);
         super.putAll(map);
     }
 
@@ -100,12 +151,35 @@ public class MultiValuedHashMap<K, V> ex
      * Creates a MultiValuedHashMap which creates the value collections using
      * the supplied <code>collectionClazz</code>.
      *
-     * @param <C>  the collection type
-     * @param collectionClazz  the class of the <code>Collection</code> to use to
-     *   create the value collections
+     * @param initialCapacity the initial capacity of the underlying
+     *        <code>HashMap</code>
+     * @param loadFactor the load factor of the underlying <code>HashMap</code>
+     * @param <C> the collection type
+     * @param collectionClazz the class of the <code>Collection</code> to use to
+     *        create the value collections
+     */
+    protected <C extends Collection<V>> MultiValuedHashMap(int initialCapacity, float loadFactor,
+            final Class<C> collectionClazz) {
+        super(new HashMap<K, Collection<V>>(initialCapacity, loadFactor), collectionClazz);
+    }
+
+    /**
+     * Creates a MultiValuedHashMap which creates the value collections using
+     * the supplied <code>collectionClazz</code> and the initial collection
+     * capacity .
+     *
+     * @param initialCapacity the initial capacity of the underlying
+     *        <code>HashMap</code>
+     * @param loadFactor the load factor of the underlying <code>HashMap</code>
+     * @param initialCollectionCapacity the initial capacity of the
+     *        <code>Collection</code>
+     * @param <C> the collection type
+     * @param collectionClazz the class of the <code>Collection</code> to use to
+     *        create the value collections
      */
-    protected <C extends Collection<V>> MultiValuedHashMap(final Class<C> collectionClazz) {
-        super(new HashMap<K, Collection<V>>(), collectionClazz);
+    protected <C extends Collection<V>> MultiValuedHashMap(int initialCapacity, float loadFactor,
+            int initialCollectionCapacity, final Class<C> collectionClazz) {
+        super(new HashMap<K, Collection<V>>(initialCapacity, loadFactor), initialCollectionCapacity, collectionClazz);
     }
 
 }

Modified: commons/proper/collections/trunk/src/main/java/org/apache/commons/collections4/multimap/UnmodifiableMultiValuedMap.java
URL: http://svn.apache.org/viewvc/commons/proper/collections/trunk/src/main/java/org/apache/commons/collections4/multimap/UnmodifiableMultiValuedMap.java?rev=1585335&r1=1585334&r2=1585335&view=diff
==============================================================================
--- commons/proper/collections/trunk/src/main/java/org/apache/commons/collections4/multimap/UnmodifiableMultiValuedMap.java (original)
+++ commons/proper/collections/trunk/src/main/java/org/apache/commons/collections4/multimap/UnmodifiableMultiValuedMap.java Sun Apr  6 19:58:37 2014
@@ -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;
 
 /**
@@ -92,6 +95,11 @@ public class UnmodifiableMultiValuedMap<
     }
 
     @Override
+    public Collection<V> get(Object key) {
+        return UnmodifiableCollection.<V>unmodifiableCollection(decorated().get(key));
+    }
+
+    @Override
     public V put(K key, V value) {
         throw new UnsupportedOperationException();
     }
@@ -117,6 +125,16 @@ public class UnmodifiableMultiValuedMap<
     }
 
     @Override
+    public Map<K, Collection<V>> asMap() {
+        return UnmodifiableMap.<K, Collection<V>>unmodifiableMap(decorated().asMap());
+    }
+
+    @Override
+    public MapIterator<K, V> mapIterator() {
+        return UnmodifiableMapIterator.<K, V>unmodifiableMapIterator(decorated().mapIterator());
+    }
+
+    @Override
     public boolean putAll(K key, Iterable<? extends V> values) {
         throw new UnsupportedOperationException();
     }

Modified: commons/proper/collections/trunk/src/test/java/org/apache/commons/collections4/collection/AbstractCollectionTest.java
URL: http://svn.apache.org/viewvc/commons/proper/collections/trunk/src/test/java/org/apache/commons/collections4/collection/AbstractCollectionTest.java?rev=1585335&r1=1585334&r2=1585335&view=diff
==============================================================================
--- commons/proper/collections/trunk/src/test/java/org/apache/commons/collections4/collection/AbstractCollectionTest.java (original)
+++ commons/proper/collections/trunk/src/test/java/org/apache/commons/collections4/collection/AbstractCollectionTest.java Sun Apr  6 19:58:37 2014
@@ -703,7 +703,7 @@ public abstract class AbstractCollection
         // 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 AbstractCollection
 
         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<E> all = Arrays.asList(getFullElements()).subList(min, max);
@@ -985,7 +985,7 @@ public abstract class AbstractCollection
         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)));

Modified: commons/proper/collections/trunk/src/test/java/org/apache/commons/collections4/multimap/AbstractMultiValuedMapTest.java
URL: http://svn.apache.org/viewvc/commons/proper/collections/trunk/src/test/java/org/apache/commons/collections4/multimap/AbstractMultiValuedMapTest.java?rev=1585335&r1=1585334&r2=1585335&view=diff
==============================================================================
--- commons/proper/collections/trunk/src/test/java/org/apache/commons/collections4/multimap/AbstractMultiValuedMapTest.java (original)
+++ commons/proper/collections/trunk/src/test/java/org/apache/commons/collections4/multimap/AbstractMultiValuedMapTest.java Sun Apr  6 19:58:37 2014
@@ -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.b
  */
 public abstract class AbstractMultiValuedMapTest<K, V> extends AbstractObjectTest {
 
+    /** Map created by reset(). */
+    protected MultiValuedMap<K, V> map;
+
+    /** MultiValuedHashMap created by reset(). */
+    protected MultiValuedMap<K, V> confirmed;
+
     public AbstractMultiValuedMapTest(String testName) {
         super(testName);
     }
@@ -76,25 +91,108 @@ public abstract class AbstractMultiValue
         return true;
     }
 
+    /**
+     * Returns true if the maps produced by {@link #makeObject()} and
+     * {@link #makeFullMap()} supports null keys.
+     * <p>
+     * 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 <code>true</code>.
+     */
+    @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<K, V> makeFullMap() {
         final MultiValuedMap<K, V> map = makeObject();
         addSampleMappings(map);
         return map;
     }
 
-    @SuppressWarnings("unchecked")
     protected void addSampleMappings(MultiValuedMap<? super K, ? super V> 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<K, V> makeConfirmedMap() {
+        return new MultiValuedHashMap<K, V>();
+    }
+
+    public MultiValuedMap<K, V> getConfirmed() {
+        return this.confirmed;
+    }
+
+    public void setConfirmed(MultiValuedMap<K, V> map) {
+        this.confirmed = map;
+    }
+
+    public MultiValuedMap<K, V> 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<K, V> map = makeFullMap();
-        assertNull(map.get("whatever"));
+        assertTrue(map.get("whatever").isEmpty());
     }
 
     public void testMultipleValues() {
@@ -114,6 +212,69 @@ public abstract class AbstractMultiValue
         assertTrue(map.get("three").contains("trois"));
     }
 
+    @SuppressWarnings("unchecked")
+    public void testAddMappingThroughGet(){
+        if (!isAddSupported()) {
+            return;
+        }
+        resetEmpty();
+        final MultiValuedMap<K, V> map =  getMap();
+        Collection<V> col1 = map.get("one");
+        Collection<V> 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<K, V> map = getMap();
+        Collection<V> 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<K, V> map = getMap();
+        Iterator<V> 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<K, V> map = makeFullMap();
         assertTrue(map.containsValue("uno"));
@@ -158,7 +319,7 @@ public abstract class AbstractMultiValue
 //        assertEquals(expected, actual);
 //    }
 
-    public void testRemoveAllViaIterator() {
+    public void testRemoveAllViaValuesIterator() {
         if (!isRemoveSupported()) {
             return;
         }
@@ -167,10 +328,34 @@ public abstract class AbstractMultiValue
             i.next();
             i.remove();
         }
-        assertNull(map.get("one"));
+        assertTrue(map.get("one").isEmpty());
         assertTrue(map.isEmpty());
     }
 
+    public void testRemoveViaValuesRemove() {
+        if (!isRemoveSupported()) {
+            return;
+        }
+        final MultiValuedMap<K, V> map = makeFullMap();
+        Collection<V> 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<K, V> map = makeFullMap();
+        Collection<V> 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 AbstractMultiValue
             i.next();
             i.remove();
         }
-        assertNull(map.get("one"));
+        assertTrue(map.get("one").isEmpty());
         assertEquals(0, map.size());
     }
 
@@ -459,11 +644,93 @@ public abstract class AbstractMultiValue
         assertTrue(keyBag.containsAll(col));
     }
 
-//    public void testMapEqulas() {
-//        MultiValuedMap<K, V> map1 = makeFullMap();
-//        MultiValuedMap<K, V> map2 = makeFullMap();
-//        assertEquals(true, map1.equals(map2));
-//    }
+    public void testAsMapGet() {
+        resetEmpty();
+        Map<K, Collection<V>> mapCol = getMap().asMap();
+        assertNull(mapCol.get("one"));
+        assertEquals(0, mapCol.size());
+
+        resetFull();
+        mapCol = getMap().asMap();
+        Collection<V> col = mapCol.get("one");
+        assertNotNull(col);
+        assertTrue(col.contains("un"));
+        assertTrue(col.contains("uno"));
+    }
+
+    @SuppressWarnings("unchecked")
+    public void testAsMapPut() {
+        if (!isAddSupported()) {
+            return;
+        }
+        resetEmpty();
+        Map<K, Collection<V>> mapCol = getMap().asMap();
+        Collection<V> col = (Collection<V>) 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<K, Collection<V>> mapCol = getMap().asMap();
+        mapCol.remove("one");
+        assertFalse(getMap().containsKey("one"));
+        assertEquals(4, getMap().size());
+    }
+
+    public void testMapIterator() {
+        resetEmpty();
+        MapIterator<K, V> 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<K, V> mapIt = getMap().mapIterator();
+        while (mapIt.hasNext()) {
+            mapIt.next();
+            mapIt.remove();
+        }
+        assertTrue(getMap().isEmpty());
+    }
+
+    @SuppressWarnings("unchecked")
+    public void testMapIteratorUnsupportedSet() {
+        resetFull();
+        MapIterator<K, V> 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 AbstractMultiValue
 
     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 AbstractMultiValue
         }
     }
 
+    // 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<Entry<K, V>> {
+        public TestMultiValuedMapEntries() {
+            super("");
+        }
+
+        @SuppressWarnings("unchecked")
+        @Override
+        public Entry<K, V>[] getFullElements() {
+            return makeFullMap().entries().toArray(new Entry[0]);
+        }
+
+        @Override
+        public Collection<Entry<K, V>> makeObject() {
+            return AbstractMultiValuedMapTest.this.makeObject().entries();
+        }
+
+        @Override
+        public Collection<Entry<K, V>> 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<Entry<K, V>> makeConfirmedCollection() {
+            // never gets called, reset methods are overridden
+            return null;
+        }
+
+        @Override
+        public Collection<Entry<K, V>> 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<K> {
+        public TestMultiValuedMapKeySet() {
+            super("");
+        }
+
+        @SuppressWarnings("unchecked")
+        @Override
+        public K[] getFullElements() {
+            return (K[]) AbstractMultiValuedMapTest.this.makeFullMap().keySet().toArray();
+        }
+
+        @Override
+        public Set<K> makeObject() {
+            return AbstractMultiValuedMapTest.this.makeObject().keySet();
+        }
+
+        @Override
+        public Set<K> 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<V> {
+        public TestMultiValuedMapValues() {
+            super("");
+        }
+
+        @Override
+        public V[] getFullElements() {
+            return getSampleValues();
+        }
+
+        @Override
+        public Collection<V> makeObject() {
+            return AbstractMultiValuedMapTest.this.makeObject().values();
+        }
+
+        @Override
+        public Collection<V> 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<V> makeConfirmedCollection() {
+            // never gets called, reset methods are overridden
+            return null;
+        }
+
+        @Override
+        public Collection<V> 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<K> {
+
+        public TestMultiValuedMapKeys() {
+            super("");
+        }
+
+        @Override
+        public K[] getFullElements() {
+            return getSampleKeys();
+        }
+
+        @Override
+        public Bag<K> makeObject() {
+            return AbstractMultiValuedMapTest.this.makeObject().keys();
+        }
+
+        @Override
+        public Bag<K> 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.<K>collectionBag(AbstractMultiValuedMapTest.this.getMap().keys()));
+            TestMultiValuedMapKeys.this.setConfirmed(AbstractMultiValuedMapTest.this.getConfirmed().keys());
+        }
+
+        @Override
+        public void resetEmpty() {
+            AbstractMultiValuedMapTest.this.resetEmpty();
+            setCollection(CollectionBag.<K>collectionBag(AbstractMultiValuedMapTest.this.getMap().keys()));
+            TestMultiValuedMapKeys.this.setConfirmed(AbstractMultiValuedMapTest.this.getConfirmed().keys());
+        }
+
+    }
+
+    public BulkTest bulkTestAsMap() {
+        return new TestMultiValuedMapAsMap();
+    }
+
+    public class TestMultiValuedMapAsMap extends AbstractMapTest<K, Collection<V>> {
+
+        public TestMultiValuedMapAsMap() {
+            super("");
+        }
+
+        @Override
+        public Map<K, Collection<V>> makeObject() {
+            return AbstractMultiValuedMapTest.this.makeObject().asMap();
+        }
+
+        public Map<K, Collection<V>> 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<V>[] getSampleValues() {
+            V[] sampleValues = AbstractMultiValuedMapTest.this.getSampleValues();
+            Collection<V>[] 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<V>[] getNewSampleValues() {
+            Object[] sampleValues = { "ein", "ek", "zwei", "duey", "drei", "teen" };
+            Collection<V>[] 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;
+        }
+
+    }
 }

Modified: commons/proper/collections/trunk/src/test/java/org/apache/commons/collections4/multimap/MultiValuedHashMapTest.java
URL: http://svn.apache.org/viewvc/commons/proper/collections/trunk/src/test/java/org/apache/commons/collections4/multimap/MultiValuedHashMapTest.java?rev=1585335&r1=1585334&r2=1585335&view=diff
==============================================================================
--- commons/proper/collections/trunk/src/test/java/org/apache/commons/collections4/multimap/MultiValuedHashMapTest.java (original)
+++ commons/proper/collections/trunk/src/test/java/org/apache/commons/collections4/multimap/MultiValuedHashMapTest.java Sun Apr  6 19:58:37 2014
@@ -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<K, V
         super(testName);
     }
 
+    public static Test suite() {
+        return BulkTest.makeSuite(MultiValuedHashMapTest.class);
+    }
+
     @Override
     public MultiValuedMap<K, V> makeObject() {
         final MultiValuedMap<K, V> m = new MultiValuedHashMap<K, V>();
         return m;
     }
 
-    private <C extends Collection<V>> MultiValuedHashMap<K, V> createTestMap(final Class<C> collectionClass) {
+    /*private <C extends Collection<V>> MultiValuedHashMap<K, V> createTestMap(final Class<C> collectionClass) {
         final MultiValuedHashMap<K, V> map =
                 (MultiValuedHashMap<K, V>) MultiValuedHashMap.<K, V, C> multiValuedMap(collectionClass);
         addSampleMappings(map);
@@ -52,26 +57,28 @@ public class MultiValuedHashMapTest<K, V
     public void testValueCollectionType() {
         final MultiValuedHashMap<K, V> map = createTestMap(LinkedList.class);
         assertTrue(map.get("one") instanceof LinkedList);
-    }
+    }*/
 
     @SuppressWarnings("unchecked")
     public void testPutWithList() {
-        final MultiValuedHashMap<K, V> test = (MultiValuedHashMap<K, V>) MultiValuedHashMap.multiValuedMap(ArrayList.class);
+        final MultiValuedHashMap<K, V> test =
+                (MultiValuedHashMap<K, V>) 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<K, V> test = (MultiValuedHashMap<K, V>) MultiValuedHashMap.multiValuedMap(HashSet.class);
+        final MultiValuedHashMap<K, V> test =
+                (MultiValuedHashMap<K, V>) 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());
     }
 

Modified: commons/proper/collections/trunk/src/test/java/org/apache/commons/collections4/multimap/TransformedMultiValuedMapTest.java
URL: http://svn.apache.org/viewvc/commons/proper/collections/trunk/src/test/java/org/apache/commons/collections4/multimap/TransformedMultiValuedMapTest.java?rev=1585335&r1=1585334&r2=1585335&view=diff
==============================================================================
--- commons/proper/collections/trunk/src/test/java/org/apache/commons/collections4/multimap/TransformedMultiValuedMapTest.java (original)
+++ commons/proper/collections/trunk/src/test/java/org/apache/commons/collections4/multimap/TransformedMultiValuedMapTest.java Sun Apr  6 19:58:37 2014
@@ -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 TransformedMultiValuedMapTe
         super(testName);
     }
 
+    public static Test suite() {
+        return BulkTest.makeSuite(TransformedMultiValuedMapTest.class);
+    }
+
     @Override
     public MultiValuedMap<K, V> makeObject() {
         return TransformedMultiValuedMap.transformingMap(new MultiValuedHashMap<K, V>(),

Modified: commons/proper/collections/trunk/src/test/java/org/apache/commons/collections4/multimap/UnmodifiableMultiValuedMapTest.java
URL: http://svn.apache.org/viewvc/commons/proper/collections/trunk/src/test/java/org/apache/commons/collections4/multimap/UnmodifiableMultiValuedMapTest.java?rev=1585335&r1=1585334&r2=1585335&view=diff
==============================================================================
--- commons/proper/collections/trunk/src/test/java/org/apache/commons/collections4/multimap/UnmodifiableMultiValuedMapTest.java (original)
+++ commons/proper/collections/trunk/src/test/java/org/apache/commons/collections4/multimap/UnmodifiableMultiValuedMapTest.java Sun Apr  6 19:58:37 2014
@@ -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 UnmodifiableMultiValuedMapT
         super(testName);
     }
 
+    public static Test suite() {
+        return BulkTest.makeSuite(UnmodifiableMultiValuedMapTest.class);
+    }
+    
     public boolean isAddSupported() {
         return false;
     }
@@ -78,6 +94,167 @@ public class UnmodifiableMultiValuedMapT
         }
     }
 
+    @SuppressWarnings("unchecked")
+    public void testUnmodifiableEntries() {
+        resetFull();
+        Collection<Entry<K, V>> entries = getMap().entries();
+        try {
+            entries.clear();
+            fail();
+        } catch (UnsupportedOperationException e) {
+        }
+
+        Iterator<Entry<K, V>> it = entries.iterator();
+        Entry<K, V> 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<K, V> 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<K> 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<K> it = keySet.iterator();
+        try {
+            it.remove();
+            fail();
+        } catch (UnsupportedOperationException e) {
+        }
+    }
+
+    @SuppressWarnings("unchecked")
+    public void testUnmodifiableValues() {
+        resetFull();
+        Collection<V> 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<V> it = values.iterator();
+        try {
+            it.remove();
+            fail();
+        } catch (UnsupportedOperationException e) {
+        }
+    }
+
+    @SuppressWarnings("unchecked")
+    public void testUnmodifiableAsMap() {
+        resetFull();
+        Map<K, Collection<V>> mapCol = getMap().asMap();
+        try {
+            mapCol.put((K) "four", (Collection<V>) 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<K> 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<K> 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");