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/03/25 23:01:08 UTC

svn commit: r1581553 [1/2] - 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/multimap/ test/resources/data/test/

Author: tn
Date: Tue Mar 25 22:01:07 2014
New Revision: 1581553

URL: http://svn.apache.org/r1581553
Log:
[COLLECTIONS-508] Initial commit of new MultiValuedMap interface + implementations. Thanks to Dipanjan Laha.

Added:
    commons/proper/collections/trunk/src/main/java/org/apache/commons/collections4/ListValuedMap.java   (with props)
    commons/proper/collections/trunk/src/main/java/org/apache/commons/collections4/MultiValuedMap.java   (with props)
    commons/proper/collections/trunk/src/main/java/org/apache/commons/collections4/multimap/
    commons/proper/collections/trunk/src/main/java/org/apache/commons/collections4/multimap/AbstractMultiValuedMap.java   (with props)
    commons/proper/collections/trunk/src/main/java/org/apache/commons/collections4/multimap/AbstractMultiValuedMapDecorator.java   (with props)
    commons/proper/collections/trunk/src/main/java/org/apache/commons/collections4/multimap/MultiValuedHashMap.java   (with props)
    commons/proper/collections/trunk/src/main/java/org/apache/commons/collections4/multimap/TransformedMultiValuedMap.java   (with props)
    commons/proper/collections/trunk/src/main/java/org/apache/commons/collections4/multimap/UnmodifiableMultiValuedMap.java   (with props)
    commons/proper/collections/trunk/src/main/java/org/apache/commons/collections4/multimap/package-info.java   (with props)
    commons/proper/collections/trunk/src/test/java/org/apache/commons/collections4/multimap/
    commons/proper/collections/trunk/src/test/java/org/apache/commons/collections4/multimap/AbstractMultiValuedMapTest.java   (with props)
    commons/proper/collections/trunk/src/test/java/org/apache/commons/collections4/multimap/MultiValuedHashMapTest.java   (with props)
    commons/proper/collections/trunk/src/test/java/org/apache/commons/collections4/multimap/TransformedMultiValuedMapTest.java   (with props)
    commons/proper/collections/trunk/src/test/java/org/apache/commons/collections4/multimap/UnmodifiableMultiValuedMapTest.java   (with props)
    commons/proper/collections/trunk/src/test/resources/data/test/MultiValuedHashMap.emptyCollection.version4.1.obj   (with props)
    commons/proper/collections/trunk/src/test/resources/data/test/MultiValuedHashMap.fullCollection.version4.1.obj   (with props)
    commons/proper/collections/trunk/src/test/resources/data/test/TransformedMultiValuedMap.emptyCollection.version4.1.obj   (with props)
    commons/proper/collections/trunk/src/test/resources/data/test/TransformedMultiValuedMap.fullCollection.version4.1.obj   (with props)
    commons/proper/collections/trunk/src/test/resources/data/test/UnmodifiableMultiValuedMap.emptyCollection.version4.1.obj   (with props)
    commons/proper/collections/trunk/src/test/resources/data/test/UnmodifiableMultiValuedMap.fullCollection.version4.1.obj   (with props)

Added: commons/proper/collections/trunk/src/main/java/org/apache/commons/collections4/ListValuedMap.java
URL: http://svn.apache.org/viewvc/commons/proper/collections/trunk/src/main/java/org/apache/commons/collections4/ListValuedMap.java?rev=1581553&view=auto
==============================================================================
--- commons/proper/collections/trunk/src/main/java/org/apache/commons/collections4/ListValuedMap.java (added)
+++ commons/proper/collections/trunk/src/main/java/org/apache/commons/collections4/ListValuedMap.java Tue Mar 25 22:01:07 2014
@@ -0,0 +1,69 @@
+/*
+ * 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.List;
+
+/**
+ * Defines a map that holds a list of values against each key.
+ * <p>
+ * A <code>ListValuedMap</code> is a Map with slightly different semantics:
+ * <ul>
+ *   <li>Putting a value into the map will add the value to a Collection at that key.</li>
+ *   <li>Getting a value will return a Collection, holding all the values put to that key.</li>
+ * </ul>
+ *
+ * @since 4.1
+ * @version $Id$
+ */
+public interface ListValuedMap<K, V> extends MultiValuedMap<K, V> {
+
+    /**
+     * Gets the list 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>Collection</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
+     */
+    List<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>Collection</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
+     */
+    List<V> remove(Object key);
+
+}

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

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

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

Added: 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=1581553&view=auto
==============================================================================
--- commons/proper/collections/trunk/src/main/java/org/apache/commons/collections4/MultiValuedMap.java (added)
+++ commons/proper/collections/trunk/src/main/java/org/apache/commons/collections4/MultiValuedMap.java Tue Mar 25 22:01:07 2014
@@ -0,0 +1,287 @@
+/*
+ * 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.Collection;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+
+/**
+ * Defines a map that holds a collection of values against each key.
+ * <p>
+ * A <code>MultiValuedMap</code> is a Map with slightly different semantics:
+ * <ul>
+ *   <li>Putting a value into the map will add the value to a Collection at that key.</li>
+ *   <li>Getting a value will return a Collection, holding all the values put to that key.</li>
+ * </ul>
+ * <p>
+ * For example:
+ * <pre>
+ * MultiValuedMap&lt;K, String&gt; map = new MultiValuedHashMap&lt;K, String&gt;();
+ * map.put(key, &quot;A&quot;);
+ * map.put(key, &quot;B&quot;);
+ * map.put(key, &quot;C&quot;);
+ * Collection&lt;String&gt; coll = map.get(key);
+ * </pre>
+ * <p>
+ * <code>coll</code> will be a collection containing "A", "B", "C".
+ * <p>
+ *
+ * @since 4.1
+ * @version $Id$
+ */
+public interface MultiValuedMap<K, V> {
+    // Query operations
+
+    /**
+     * Gets the total size of the map.
+     * <p>
+     * Implementations would return the total size of the map which is the count
+     * of the values from all keys.
+     *
+     * @return the total size of the map
+     */
+    int size();
+
+    /**
+     * Returns <tt>true</tt> if this map contains no key-value mappings.
+     *
+     * @return <tt>true</tt> if this map contains no key-value mappings
+     */
+    boolean isEmpty();
+
+    /**
+     * Returns <tt>true</tt> if this map contains a mapping for the specified
+     * key. More formally, returns <tt>true</tt> if and only if this map
+     * contains a mapping for a key <tt>k</tt> such that
+     * <tt>(key==null ? k==null : key.equals(k))</tt>. (There can be at most one
+     * such mapping.)
+     *
+     * @param key key whose presence in this map is to be tested
+     * @return <tt>true</tt> if this map contains a mapping for the specified key
+     * @throws ClassCastException if the key is of an inappropriate type for this map (optional)
+     * @throws NullPointerException if the specified key is null and this map
+     *         does not permit null keys (optional)
+     */
+    boolean containsKey(Object key);
+
+    /**
+     * Checks whether the map contains at least one mapping for the specified value.
+     *
+     * @param value the value to search for
+     * @return true if the map contains the value
+     * @throws ClassCastException if the value is of an invalid type
+     * @throws NullPointerException if the value is null and null value are invalid
+     */
+    boolean containsValue(Object value);
+
+    /**
+     * Checks whether the map contains a mapping for the specified key and value.
+     *
+     * @param key the key to search for
+     * @param value the value to search for
+     * @return true if the map contains the value
+     */
+    boolean containsMapping(Object key, Object value);
+
+    /**
+     * Gets the collection of values associated with the specified key.
+     * <p>
+     * Implementations are free to declare that they return
+     * <code>Collection</code> subclasses such as <code>List</code> or
+     * <code>Set</code>.
+     * <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>Collection</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
+     */
+    Collection<V> get(Object key);
+
+    // Modification operations
+
+    /**
+     * Adds the value to the collection associated with the specified key.
+     * <p>
+     * Unlike a normal <code>Map</code> the previous value is not replaced.
+     * Instead the new value is added to the collection stored against the key.
+     * The collection may be a <code>List</code>, <code>Set</code> or other
+     * collection dependent on implementation.
+     *
+     * @param key the key to store against
+     * @param value the value to add to the collection at the key
+     * @return typically the value added if the map changed and null if the map
+     *         did not change
+     * @throws UnsupportedOperationException if the map is unmodifiable
+     * @throws ClassCastException if the key or value is of an invalid type
+     * @throws NullPointerException if the key or value is null and null is invalid
+     * @throws IllegalArgumentException if the key or value is invalid
+     */
+    V put(K key, V value);
+
+    /**
+     * Adds Iterable values to the collection associated with the specified key.
+     *
+     * @param key the key to store against
+     * @param values the values to add to the collection at the key, null ignored
+     * @return true if this map changed
+     */
+    boolean putAll(K key, Iterable<? extends V> values);
+
+    /**
+     * Copies all of the mappings from the specified map to this map (optional
+     * operation). The effect of this call is equivalent to that of calling
+     * {@link #put(Object,Object) put(k, v)} on this map once for each mapping
+     * from key <tt>k</tt> to value <tt>v</tt> in the specified map. The
+     * behavior of this operation is undefined if the specified map is modified
+     * while the operation is in progress.
+     *
+     * @param m mappings to be stored in this map
+     * @throws UnsupportedOperationException if the <tt>putAll</tt> operation is
+     *         not supported by this map
+     * @throws ClassCastException if the class of a key or value in the
+     *         specified map prevents it from being stored in this map
+     * @throws NullPointerException if the specified map is null, or if this map
+     *         does not permit null keys or values, and the specified map
+     *         contains null keys or values
+     * @throws IllegalArgumentException if some property of a key or value in
+     *         the specified map prevents it from being stored in this map
+     */
+    void putAll(Map<? extends K, ? extends V> m);
+
+    /**
+     * Copies all of the mappings from the specified MultiValuedMap to this map
+     * (optional operation). The effect of this call is equivalent to that of
+     * calling {@link #put(Object,Object) put(k, v)} on this map once for each
+     * mapping from key <tt>k</tt> to value <tt>v</tt> in the specified map. The
+     * behavior of this operation is undefined if the specified map is modified
+     * while the operation is in progress.
+     *
+     * @param m mappings to be stored in this map
+     * @throws UnsupportedOperationException if the <tt>putAll</tt> operation is
+     *         not supported by this map
+     * @throws ClassCastException if the class of a key or value in the
+     *         specified map prevents it from being stored in this map
+     * @throws NullPointerException if the specified map is null, or if this map
+     *         does not permit null keys or values, and the specified map
+     *         contains null keys or values
+     * @throws IllegalArgumentException if some property of a key or value in
+     *         the specified map prevents it from being stored in this map
+     */
+    void putAll(MultiValuedMap<? extends K, ? extends V> m);
+
+    /**
+     * 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>Collection</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
+     */
+    Collection<V> remove(Object key);
+
+    /**
+     * Removes a key-value mapping from the map.
+     * <p>
+     * The item is removed from the collection mapped to the specified key.
+     * Other values attached to that key are unaffected.
+     * <p>
+     * If the last value for a key is removed, 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 from
+     * @param item the item to remove
+     * @return {@code true} if the mapping was removed, {@code false} otherwise
+     * @throws UnsupportedOperationException if the map is unmodifiable
+     * @throws ClassCastException if the key or value is of an invalid type
+     * @throws NullPointerException if the key or value is null and null is
+     *         invalid
+     */
+    boolean removeMapping(K key, V item);
+
+    /**
+     * Removes all of the mappings from this map (optional operation).
+     * The map will be empty after this call returns.
+     *
+     * @throws UnsupportedOperationException if the map is unmodifiable
+     */
+    void clear();
+
+    // Views
+
+    /**
+     * Returns a {@link Collection} view of the mappings contained in this map.
+     * The collection is backed by the map, so changes to the map are reflected
+     * in this, and vice-versa.
+     *
+     * @return a set view of the mappings contained in this map
+     */
+    Collection<Entry<K, V>> entries();
+
+    /**
+     * Returns a {@link Bag} view of the key mapping contained in this map.
+     * <p>
+     * Implementations typically return a Bag of keys with its values count as
+     * the count of the Bag. This bag is backed by the map, so any changes in
+     * the map is reflected here.
+     *
+     * @return a bag view of the key mapping contained in this map
+     */
+    Bag<K> keys();
+
+    /**
+     * Returns a {@link Set} view of the keys contained in this map. The set is
+     * backed by the map, so changes to the map are reflected in the set, and
+     * vice-versa. If the map is modified while an iteration over the set is in
+     * progress (except through the iterator's own <tt>remove</tt> operation),
+     * the results of the iteration are undefined. The set supports element
+     * removal, which removes the corresponding mapping from the map, via the
+     * <tt>Iterator.remove</tt>, <tt>Set.remove</tt>, <tt>removeAll</tt>,
+     * <tt>retainAll</tt>, and <tt>clear</tt> operations. It does not support
+     * the <tt>add</tt> or <tt>addAll</tt> operations.
+     *
+     * @return a set view of the keys contained in this map
+     */
+    Set<K> keySet();
+
+    /**
+     * Gets a collection containing all the values in the map.
+     * <p>
+     * Implementations typically return a collection containing the combination
+     * of values from all keys.
+     *
+     * @return a collection view of the values contained in this map
+     */
+    Collection<V> values();
+
+}

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

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

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

Added: 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=1581553&view=auto
==============================================================================
--- commons/proper/collections/trunk/src/main/java/org/apache/commons/collections4/multimap/AbstractMultiValuedMap.java (added)
+++ commons/proper/collections/trunk/src/main/java/org/apache/commons/collections4/multimap/AbstractMultiValuedMap.java Tue Mar 25 22:01:07 2014
@@ -0,0 +1,694 @@
+/*
+ * 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.multimap;
+
+import java.io.Serializable;
+import java.lang.reflect.Array;
+import java.util.AbstractCollection;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+
+import org.apache.commons.collections4.Bag;
+import org.apache.commons.collections4.Factory;
+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.IteratorChain;
+import org.apache.commons.collections4.iterators.LazyIteratorChain;
+import org.apache.commons.collections4.iterators.TransformIterator;
+
+/**
+ * Abstract implementation of the {@link MultiValuedMap} interface to simplify
+ * the creation of subclass implementations.
+ * <p>
+ * Subclasses specify a Map implementation to use as the internal storage.
+ *
+ * @since 4.1
+ * @version $Id$
+ */
+public class AbstractMultiValuedMap<K, V> implements MultiValuedMap<K, V>, Serializable {
+
+    /** Serialization Version */
+    private static final long serialVersionUID = 7994988366330224277L;
+
+    /** The factory for creating value collections. */
+    private final Factory<? extends Collection<V>> collectionFactory;
+
+    /** The values view */
+    private transient Collection<V> valuesView;
+
+    /** The EntryValues view */
+    private transient EntryValues entryValuesView;
+
+    /** The KeyBag view */
+    private transient KeysBag keysBagView;
+
+    /** The map used to store the data */
+    private final Map<K, Collection<V>> map;
+
+    /**
+     * Constructor that wraps (not copies).
+     *
+     * @param <C>  the collection type
+     * @param map  the map to wrap, must not be null
+     * @param collectionClazz  the collection class
+     * @throws IllegalArgumentException if the map is null
+     */
+    @SuppressWarnings("unchecked")
+    protected <C extends Collection<V>> AbstractMultiValuedMap(final Map<K, ? super C> map,
+                                                               final Class<C> collectionClazz) {
+        if (map == null) {
+            throw new IllegalArgumentException("Map must not be null");
+        }
+        this.map = (Map<K, Collection<V>>) map;
+        this.collectionFactory = new InstantiateFactory<C>(collectionClazz);
+    }
+
+    /**
+     * Gets the map being wrapped.
+     *
+     * @return the wrapped map
+     */
+    protected Map<K, Collection<V>> getMap() {
+        return map;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public boolean containsKey(Object key) {
+        return getMap().containsKey(key);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public boolean containsValue(final Object value) {
+        final Set<Map.Entry<K, Collection<V>>> pairs = getMap().entrySet();
+        if (pairs != null) {
+            for (final Map.Entry<K, Collection<V>> entry : pairs) {
+                if (entry.getValue().contains(value)) {
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public boolean containsMapping(Object key, Object value) {
+        final Collection<V> col = get(key);
+        if (col == null) {
+            return false;
+        }
+        return col.contains(value);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public Collection<Entry<K, V>> entries() {
+        return entryValuesView != null ? entryValuesView : (entryValuesView = new EntryValues());
+    }
+
+    /**
+     * Gets the collection of values associated with the specified key.
+     *
+     * @param key the key to retrieve
+     * @return the <code>Collection</code> of values, will return
+     *         <code>null</code> for no mapping
+     * @throws ClassCastException if the key is of an invalid type
+     */
+    public Collection<V> get(Object key) {
+        return getMap().get(key);
+    }
+
+    /**
+     * Removes all values associated with the specified key.
+     * <p>
+     * A subsequent <code>get(Object)</code> would return null collection.
+     *
+     * @param key the key to remove values from
+     * @return the <code>Collection</code> of values removed, will return
+     *         <code>null</code> for no mapping found.
+     * @throws ClassCastException if the key is of an invalid type
+     */
+    public Collection<V> remove(Object key) {
+        return getMap().remove(key);
+    }
+
+    /**
+     * Removes a specific value from map.
+     * <p>
+     * The item is removed from the collection mapped to the specified key.
+     * Other values attached to that key are unaffected.
+     * <p>
+     * If the last value for a key is removed, <code>null</code> would be
+     * returned from a subsequent <code>get(Object)</code>.
+     *
+     * @param key the key to remove from
+     * @param item the item to remove
+     * @return {@code true} if the mapping was removed, {@code false} otherwise
+     */
+    public boolean removeMapping(K key, V item) {
+        boolean result = false;
+        final Collection<V> col = get(key);
+        if (col == null) {
+            return false;
+        }
+        result = col.remove(item);
+        if (!result) {
+            return false;
+        }
+        if (col.isEmpty()) {
+            remove(key);
+        }
+        return true;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public boolean isEmpty() {
+        return getMap().isEmpty();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public Set<K> keySet() {
+        return getMap().keySet();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public int size() {
+        int size = 0;
+        for (Collection<V> col : getMap().values()) {
+            size += col.size();
+        }
+        return size;
+    }
+
+    /**
+     * Gets a collection containing all the values in the map.
+     * <p>
+     * Returns a collection containing all the values from all keys.
+     *
+     * @return a collection view of the values contained in this map
+     */
+    public Collection<V> values() {
+        final Collection<V> vs = valuesView;
+        return vs != null ? vs : (valuesView = new Values());
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void clear() {
+        getMap().clear();
+    }
+
+    /**
+     * Adds the value to the collection associated with the specified key.
+     * <p>
+     * Unlike a normal <code>Map</code> the previous value is not replaced.
+     * Instead the new value is added to the collection stored against the key.
+     *
+     * @param key the key to store against
+     * @param value the value to add to the collection at the key
+     * @return the value added if the map changed and null if the map did not
+     *         change
+     */
+    public V put(K key, V value) {
+        boolean result = false;
+        Collection<V> coll = get(key);
+        if (coll == null) {
+            coll = createCollection();
+            coll.add(value);
+            if (coll.size() > 0) {
+                // only add if non-zero size to maintain class state
+                getMap().put(key, coll);
+                result = true; // map definitely changed
+            }
+        } else {
+            result = coll.add(value);
+        }
+        return result ? value : null;
+    }
+
+    /**
+     * Copies all of the mappings from the specified map to this map. The effect
+     * of this call is equivalent to that of calling {@link #put(Object,Object)
+     * put(k, v)} on this map once for each mapping from key <tt>k</tt> to value
+     * <tt>v</tt> in the specified map. The behavior of this operation is
+     * undefined if the specified map is modified while the operation is in
+     * progress.
+     *
+     * @param map mappings to be stored in this map
+     */
+    public void putAll(final Map<? extends K, ? extends V> map) {
+        if (map != null) {
+            for (final Map.Entry<? extends K, ? extends V> entry : map.entrySet()) {
+                put((K) entry.getKey(), (V) entry.getValue());
+            }
+        }
+    }
+
+    /**
+     * Copies all of the mappings from the specified MultiValuedMap to this map.
+     * The effect of this call is equivalent to that of calling
+     * {@link #put(Object,Object) put(k, v)} on this map once for each mapping
+     * from key <tt>k</tt> to value <tt>v</tt> in the specified map. The
+     * behavior of this operation is undefined if the specified map is modified
+     * while the operation is in progress.
+     *
+     * @param map mappings to be stored in this map
+     */
+    @SuppressWarnings("unchecked")
+    public void putAll(MultiValuedMap<? extends K, ? extends V> map) {
+        if (map != null) {
+            for (final K key : map.keySet()) {
+                putAll(key, (Collection<V>) map.get(key));
+            }
+        }
+    }
+
+    /**
+     * Returns a {@link Bag} view of the key mapping contained in this map.
+     * <p>
+     * Returns a Bag of keys with its values count as the count of the Bag. This
+     * bag is backed by the map, so any changes in the map is reflected here.
+     * Any method which modifies this bag like <tt>add</tt>, <tt>remove</tt>,
+     * <tt>Iterator.remove</tt> etc throws
+     * <code>UnsupportedOperationException</code>
+     *
+     * @return a bag view of the key mapping contained in this map
+     */
+    public Bag<K> keys() {
+        return keysBagView != null ? keysBagView : (keysBagView = new KeysBag());
+    }
+
+    /**
+     * Adds Iterable values to the collection associated with the specified key.
+     *
+     * @param key the key to store against
+     * @param values the values to add to the collection at the key, null
+     *        ignored
+     * @return true if this map changed
+     */
+    public boolean putAll(final K key, final Iterable<? extends V> values) {
+        if (values == null || values.iterator() == null || !values.iterator().hasNext()) {
+            return false;
+        }
+        Iterator<? extends V> it = values.iterator();
+        boolean result = false;
+        Collection<V> coll = get(key);
+        if (coll == null) {
+            coll = createCollection(); // might produce a non-empty collection
+            while (it.hasNext()) {
+                coll.add(it.next());
+            }
+            if (coll.size() > 0) {
+                // only add if non-zero size to maintain class state
+                getMap().put(key, coll);
+                result = true; // map definitely changed
+            }
+        } else {
+            while (it.hasNext()) {
+                boolean tmpResult = coll.add(it.next());
+                if (!result && tmpResult) {
+                    // If any one of the values have been added, the map has
+                    // changed
+                    result = true;
+                }
+            }
+        }
+        return result;
+    }
+
+    /**
+     * 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
+     */
+    public int size(final Object key) {
+        final Collection<V> coll = get(key);
+        if (coll == null) {
+            return 0;
+        }
+        return coll.size();
+    }
+
+    @SuppressWarnings("rawtypes")
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj) {
+            return true;
+        }
+        if (obj == null) {
+            return false;
+        }
+        if (obj instanceof MultiValuedMap == false) {
+            return false;
+        }
+        MultiValuedMap<?, ?> other = (MultiValuedMap<?, ?>) obj;
+        if (other.size() != size()) {
+            return false;
+        }
+        Iterator it = keySet().iterator();
+        while (it.hasNext()) {
+            Object key = it.next();
+            Collection<?> col = get(key);
+            Collection<?> otherCol = other.get(key);
+            if (otherCol == null) {
+                return false;
+            }
+            if (col.size() != otherCol.size()) {
+                return false;
+            }
+            for (Object value : col) {
+                if (!otherCol.contains(value)) {
+                    return false;
+                }
+            }
+        }
+        return true;
+    }
+
+    @Override
+    public int hashCode() {
+        return getMap().hashCode();
+    }
+
+    @Override
+    public String toString() {
+        return getMap().toString();
+    }
+
+    // -----------------------------------------------------------------------
+
+    protected Collection<V> createCollection() {
+        return collectionFactory.create();
+    }
+
+    // -----------------------------------------------------------------------
+
+    /**
+     * Inner class that provides a Bag<K> keys view
+     */
+    private class KeysBag implements Bag<K> {
+
+        public boolean addAll(Collection<? extends K> c) {
+            throw new UnsupportedOperationException();
+        }
+
+        public void clear() {
+            throw new UnsupportedOperationException();
+        }
+
+        public boolean contains(Object o) {
+            return getMap().containsKey(o);
+        }
+
+        public boolean isEmpty() {
+            return getMap().isEmpty();
+        }
+
+        public Object[] toArray() {
+            final Object[] result = new Object[size()];
+            int i = 0;
+            final Iterator<K> it = getMap().keySet().iterator();
+            while (it.hasNext()) {
+                final K current = it.next();
+                for (int index = getCount(current); index > 0; index--) {
+                    result[i++] = current;
+                }
+            }
+            return result;
+        }
+
+        public <T> T[] toArray(T[] array) {
+            final int size = size();
+            if (array.length < size) {
+                @SuppressWarnings("unchecked")
+                // safe as both are of type T
+                final T[] unchecked = (T[]) Array.newInstance(array.getClass().getComponentType(), size);
+                array = unchecked;
+            }
+
+            int i = 0;
+            final Iterator<K> it = getMap().keySet().iterator();
+            while (it.hasNext()) {
+                final K current = it.next();
+                for (int index = getCount(current); index > 0; index--) {
+                    // unsafe, will throw ArrayStoreException if types are not
+                    // compatible, see javadoc
+                    @SuppressWarnings("unchecked")
+                    final T unchecked = (T) current;
+                    array[i++] = unchecked;
+                }
+            }
+            while (i < array.length) {
+                array[i++] = null;
+            }
+            return array;
+        }
+
+        public int getCount(Object object) {
+            int count = 0;
+            Collection<V> col = AbstractMultiValuedMap.this.getMap().get(object);
+            if (col != null) {
+                count = col.size();
+            }
+            return count;
+        }
+
+        public boolean add(K object) {
+            throw new UnsupportedOperationException();
+        }
+
+        public boolean add(K object, int nCopies) {
+            throw new UnsupportedOperationException();
+        }
+
+        public boolean remove(Object object) {
+            throw new UnsupportedOperationException();
+        }
+
+        public boolean remove(Object object, int nCopies) {
+            throw new UnsupportedOperationException();
+        }
+
+        public Set<K> uniqueSet() {
+            return keySet();
+        }
+
+        public int size() {
+            return AbstractMultiValuedMap.this.size();
+        }
+
+        public boolean containsAll(Collection<?> coll) {
+            if (coll instanceof Bag) {
+                return containsAll((Bag<?>) coll);
+            }
+            return containsAll(new HashBag<Object>(coll));
+        }
+
+        private boolean containsAll(final Bag<?> other) {
+            final Iterator<?> it = other.uniqueSet().iterator();
+            while (it.hasNext()) {
+                final Object current = it.next();
+                if (getCount(current) < other.getCount(current)) {
+                    return false;
+                }
+            }
+            return true;
+        }
+
+        public boolean removeAll(Collection<?> coll) {
+            throw new UnsupportedOperationException();
+        }
+
+        public boolean retainAll(Collection<?> coll) {
+            throw new UnsupportedOperationException();
+        }
+
+        public Iterator<K> iterator() {
+            return new LazyIteratorChain<K>() {
+
+                final Iterator<K> keyIterator = getMap().keySet().iterator();
+
+                @Override
+                protected Iterator<? extends K> nextIterator(int count) {
+                    if (!keyIterator.hasNext()) {
+                        return null;
+                    }
+                    final K key = keyIterator.next();
+                    final Iterator<V> colIterator = getMap().get(key).iterator();
+                    Iterator<K> nextIt = new Iterator<K>() {
+
+                        public boolean hasNext() {
+                            return colIterator.hasNext();
+                        }
+
+                        public K next() {
+                            colIterator.next();// Increment the iterator
+                            // The earlier statement would throw
+                            // NoSuchElementException anyway in case it ends
+                            return key;
+                        }
+
+                        public void remove() {
+                            throw new UnsupportedOperationException();
+                        }
+                    };
+                    return nextIt;
+                }
+            };
+        }
+
+    }
+
+    /**
+     * Inner class that provides the Entry<K, V> view
+     */
+    private class EntryValues extends AbstractCollection<Entry<K, V>> {
+
+        @Override
+        public Iterator<Entry<K, V>> iterator() {
+            return new LazyIteratorChain<Entry<K, V>>() {
+
+                final Collection<K> keysCol = new ArrayList<K>(getMap().keySet());
+                final Iterator<K> keyIterator = keysCol.iterator();
+
+                @Override
+                protected Iterator<? extends Entry<K, V>> nextIterator(int count) {
+                    if (!keyIterator.hasNext()) {
+                        return null;
+                    }
+                    final K key = keyIterator.next();
+                    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 TransformIterator<V, Entry<K, V>>(new ValuesIterator(key), entryTransformer);
+                }
+            };
+        }
+
+        @Override
+        public int size() {
+            return AbstractMultiValuedMap.this.size();
+        }
+
+    }
+
+    /**
+     * Inner class that provides the values view.
+     */
+    private class Values extends AbstractCollection<V> {
+        @Override
+        public Iterator<V> iterator() {
+            final IteratorChain<V> chain = new IteratorChain<V>();
+            for (final K k : keySet()) {
+                chain.addIterator(new ValuesIterator(k));
+            }
+            return chain;
+        }
+
+        @Override
+        public int size() {
+            return AbstractMultiValuedMap.this.size();
+        }
+
+        @Override
+        public void clear() {
+            AbstractMultiValuedMap.this.clear();
+        }
+    }
+
+    /**
+     * Inner class that provides the values iterator.
+     */
+    private class ValuesIterator implements Iterator<V> {
+        private final Object key;
+        private final Collection<V> values;
+        private final Iterator<V> iterator;
+
+        public ValuesIterator(final Object key) {
+            this.key = key;
+            this.values = get(key);
+            this.iterator = values.iterator();
+        }
+
+        public void remove() {
+            iterator.remove();
+            if (values.isEmpty()) {
+                AbstractMultiValuedMap.this.remove(key);
+            }
+        }
+
+        public boolean hasNext() {
+            return iterator.hasNext();
+        }
+
+        public V next() {
+            return iterator.next();
+        }
+    }
+
+}

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

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

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

Added: 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=1581553&view=auto
==============================================================================
--- commons/proper/collections/trunk/src/main/java/org/apache/commons/collections4/multimap/AbstractMultiValuedMapDecorator.java (added)
+++ commons/proper/collections/trunk/src/main/java/org/apache/commons/collections4/multimap/AbstractMultiValuedMapDecorator.java Tue Mar 25 22:01:07 2014
@@ -0,0 +1,153 @@
+/*
+ * 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.multimap;
+
+import java.io.Serializable;
+import java.util.Collection;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+
+import org.apache.commons.collections4.Bag;
+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.
+ *
+ * @param <K> the type of key elements
+ * @param <V> the type of value elements
+ *
+ * @since 4.1
+ * @version $Id$
+ */
+public class AbstractMultiValuedMapDecorator<K, V>
+        implements MultiValuedMap<K, V>, Serializable {
+
+    /** Serialization version */
+    private static final long serialVersionUID = -9184930955231260637L;
+
+    /** MultiValuedMap to decorate */
+    private final MultiValuedMap<K, V> map;
+
+    /**
+     * Constructor that wraps (not copies).
+     *
+     * @param map the map to decorate, must not be null
+     * @throws IllegalArgumentException if the map is null
+     */
+    protected AbstractMultiValuedMapDecorator(final MultiValuedMap<K, V> map) {
+        if (map == null) {
+            throw new IllegalArgumentException("MultiValuedMap must not be null");
+        }
+        this.map = map;
+    }
+
+    protected MultiValuedMap<K, V> decorated() {
+        return map;
+    }
+
+    public int size() {
+        return decorated().size();
+    }
+
+    public boolean isEmpty() {
+        return decorated().isEmpty();
+    }
+
+    public boolean containsKey(Object key) {
+        return decorated().containsKey(key);
+    }
+
+    public boolean containsValue(Object value) {
+        return decorated().containsValue(value);
+    }
+
+    public boolean containsMapping(Object key, Object value) {
+        return decorated().containsMapping(key, value);
+    }
+
+    public Collection<V> get(Object key) {
+        return decorated().get(key);
+    }
+
+    public Collection<V> remove(Object key) {
+        return decorated().remove(key);
+    }
+
+    public boolean removeMapping(K key, V item) {
+        return decorated().removeMapping(key, item);
+    }
+
+    public void clear() {
+        decorated().clear();
+    }
+
+    public V put(K key, V value) {
+        return decorated().put(key, value);
+    }
+
+    public Set<K> keySet() {
+        return decorated().keySet();
+    }
+
+    public Collection<Entry<K, V>> entries() {
+        return decorated().entries();
+    }
+
+    public Bag<K> keys() {
+        return decorated().keys();
+    }
+
+    public Collection<V> values() {
+        return decorated().values();
+    }
+
+    public boolean putAll(K key, Iterable<? extends V> values) {
+        return decorated().putAll(key, values);
+    }
+
+    public void putAll(Map<? extends K, ? extends V> m) {
+        decorated().putAll(m);
+    }
+
+    public void putAll(MultiValuedMap<? extends K, ? extends V> m) {
+        decorated().putAll(m);
+    }
+
+    @Override
+    public boolean equals(final Object object) {
+        if (object == this) {
+            return true;
+        }
+        return decorated().equals(object);
+    }
+
+    @Override
+    public int hashCode() {
+        return decorated().hashCode();
+    }
+
+    @Override
+    public String toString() {
+        return decorated().toString();
+    }
+
+}

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

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

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

Added: 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=1581553&view=auto
==============================================================================
--- commons/proper/collections/trunk/src/main/java/org/apache/commons/collections4/multimap/MultiValuedHashMap.java (added)
+++ commons/proper/collections/trunk/src/main/java/org/apache/commons/collections4/multimap/MultiValuedHashMap.java Tue Mar 25 22:01:07 2014
@@ -0,0 +1,111 @@
+/*
+ * 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.multimap;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.commons.collections4.MultiValuedMap;
+
+/**
+ * Implements a {@link MultiValuedMap}, using a {@link HashMap} to provide data
+ * storage. This is the standard implementation of a MultiValuedMap
+ * <p>
+ * A <code>MultiValuedMap</code> is a Map with slightly different semantics.
+ * Putting a value into the map will add the value to a Collection at that key.
+ * Getting a value will return a Collection, holding all the values put to that
+ * key
+ * <p>
+ * In addition, this implementation allows the type of collection used for the
+ * values to be controlled. By default, an <code>ArrayList</code> is used,
+ * however a <code>Class<? extends Collection></code> to instantiate the value
+ * collection may be specified.
+ * <p>
+ * <strong>Note that MultiValuedHashMap is not synchronized and is not
+ * thread-safe.</strong> If you wish to use this map from multiple threads
+ * concurrently, you must use appropriate synchronization. This class may throw
+ * exceptions when accessed by concurrent threads without synchronization.
+ *
+ * @since 4.1
+ * @version $Id$
+ */
+public class MultiValuedHashMap<K, V> extends AbstractMultiValuedMap<K, V> implements MultiValuedMap<K, V> {
+
+    /** Serialization Version */
+    private static final long serialVersionUID = -5845183518195365857L;
+
+    /**
+     * Creates a MultiValuedHashMap which maps keys to collections of type
+     * <code>collectionClass</code>.
+     *
+     * @param <K> the key type
+     * @param <V> the value type
+     * @param <C> the collection class type
+     * @param collectionClass the type of the collection class
+     * @return a new MultiValuedMap
+     */
+    public static <K, V, C extends Collection<V>> MultiValuedMap<K, V> multiValuedMap(
+            final Class<C> collectionClass) {
+        return new MultiValuedHashMap<K, V>(collectionClass);
+    }
+
+    /**
+     * Creates a MultiValueMap based on a <code>HashMap</code> which stores the
+     * multiple values in an <code>ArrayList</code>.
+     */
+    @SuppressWarnings("unchecked")
+    public MultiValuedHashMap() {
+        this(ArrayList.class);
+    }
+
+    /**
+     * Creates a MultiValuedHashMap copying all the mappings of the given map.
+     *
+     * @param map a <code>MultiValuedMap</code> to copy into this map
+     */
+    @SuppressWarnings("unchecked")
+    public MultiValuedHashMap(final MultiValuedMap<? extends K, ? extends V> map) {
+        this(ArrayList.class);
+        super.putAll(map);
+    }
+
+    /**
+     * Creates a MultiValuedHashMap copying all the mappings of the given map.
+     *
+     * @param map a <code>Map</code> to copy into this map
+     */
+    @SuppressWarnings("unchecked")
+    public MultiValuedHashMap(final Map<? extends K, ? extends V> map) {
+        this(ArrayList.class);
+        super.putAll(map);
+    }
+
+    /**
+     * 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
+     */
+    protected <C extends Collection<V>> MultiValuedHashMap(final Class<C> collectionClazz) {
+        super(new HashMap<K, Collection<V>>(), collectionClazz);
+    }
+
+}

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

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

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

Added: commons/proper/collections/trunk/src/main/java/org/apache/commons/collections4/multimap/TransformedMultiValuedMap.java
URL: http://svn.apache.org/viewvc/commons/proper/collections/trunk/src/main/java/org/apache/commons/collections4/multimap/TransformedMultiValuedMap.java?rev=1581553&view=auto
==============================================================================
--- commons/proper/collections/trunk/src/main/java/org/apache/commons/collections4/multimap/TransformedMultiValuedMap.java (added)
+++ commons/proper/collections/trunk/src/main/java/org/apache/commons/collections4/multimap/TransformedMultiValuedMap.java Tue Mar 25 22:01:07 2014
@@ -0,0 +1,236 @@
+/*
+ * 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.multimap;
+
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.commons.collections4.MultiValuedMap;
+import org.apache.commons.collections4.Transformer;
+import org.apache.commons.collections4.map.LinkedMap;
+
+/**
+ * Decorates another <code>MultiValuedMap</code> to transform objects that are added.
+ * <p>
+ * This class affects the MultiValuedMap put methods. Thus objects must be
+ * removed or searched for using their transformed form. For example, if the
+ * transformation converts Strings to Integers, you must use the Integer form to
+ * remove objects.
+ * <p>
+ * <strong>Note that TransformedMultiValuedMap is not synchronized and is not thread-safe.</strong>
+ *
+ * @since 4.1
+ * @version $Id$
+ */
+public class TransformedMultiValuedMap<K, V> extends AbstractMultiValuedMapDecorator<K, V> {
+
+    /** Serialization Version */
+    private static final long serialVersionUID = -1254147899086470720L;
+
+    private final Transformer<? super K, ? extends K> keyTransformer;
+
+    private final Transformer<? super V, ? extends V> valueTransformer;
+
+    /**
+     * Factory method to create a transforming MultiValuedMap.
+     * <p>
+     * If there are any elements already in the map being decorated, they are
+     * NOT transformed. Contrast this with
+     * {@link #transformedMap(MultiValuedMap, Transformer, Transformer)}.
+     *
+     * @param <K> the key type
+     * @param <V> the value type
+     * @param map the MultiValuedMap to decorate, must not be null
+     * @param keyTransformer the transformer to use for key conversion, null
+     *        means no transformation
+     * @param valueTransformer the transformer to use for value conversion, null
+     *        means no transformation
+     * @return a new transformed MultiValuedMap
+     * @throws IllegalArgumentException if map is null
+     */
+    public static <K, V> TransformedMultiValuedMap<K, V> transformingMap(final MultiValuedMap<K, V> map,
+            final Transformer<? super K, ? extends K> keyTransformer,
+            final Transformer<? super V, ? extends V> valueTransformer) {
+        return new TransformedMultiValuedMap<K, V>(map, keyTransformer, valueTransformer);
+    }
+
+    /**
+     * Factory method to create a transforming MultiValuedMap that will
+     * transform existing contents of the specified map.
+     * <p>
+     * If there are any elements already in the map being decorated, they will
+     * be transformed by this method. Contrast this with
+     * {@link #transformingMap(MultiValuedMap, Transformer, Transformer)}.
+     *
+     * @param <K> the key type
+     * @param <V> the value type
+     * @param map the MultiValuedMap to decorate, must not be null
+     * @param keyTransformer the transformer to use for key conversion, null
+     *        means no transformation
+     * @param valueTransformer the transformer to use for value conversion, null
+     *        means no transformation
+     * @return a new transformed MultiValuedMap
+     * @throws IllegalArgumentException if map is null
+     */
+    public static <K, V> TransformedMultiValuedMap<K, V> transformedMap(final MultiValuedMap<K, V> map,
+            final Transformer<? super K, ? extends K> keyTransformer,
+            final Transformer<? super V, ? extends V> valueTransformer) {
+        final TransformedMultiValuedMap<K, V> decorated =
+                new TransformedMultiValuedMap<K, V>(map, keyTransformer, valueTransformer);
+        if (map.size() > 0) {
+            MultiValuedMap<K, V> transformed = decorated.transformMultiValuedMap(map);
+            decorated.clear();
+            // to avoid double transform
+            decorated.decorated().putAll(transformed);
+        }
+        return decorated;
+    }
+
+    // -----------------------------------------------------------------------
+    /**
+     * Constructor that wraps (not copies).
+     * <p>
+     * If there are any elements already in the collection being decorated, they
+     * are NOT transformed.
+     *
+     * @param map the MultiValuedMap to decorate, must not be null
+     * @param keyTransformer the transformer to use for key conversion, null
+     *        means no conversion
+     * @param valueTransformer the transformer to use for value conversion, null
+     *        means no conversion
+     * @throws IllegalArgumentException if map is null
+     */
+    protected TransformedMultiValuedMap(MultiValuedMap<K, V> map,
+            Transformer<? super K, ? extends K> keyTransformer, Transformer<? super V, ? extends V> valueTransformer) {
+        super(map);
+        this.keyTransformer = keyTransformer;
+        this.valueTransformer = valueTransformer;
+    }
+
+    /**
+     * Transforms a key.
+     * <p>
+     * The transformer itself may throw an exception if necessary.
+     *
+     * @param object the object to transform
+     * @return the transformed object
+     */
+    protected K transformKey(final K object) {
+        if (keyTransformer == null) {
+            return object;
+        }
+        return keyTransformer.transform(object);
+    }
+
+    /**
+     * Transforms a value.
+     * <p>
+     * The transformer itself may throw an exception if necessary.
+     *
+     * @param object the object to transform
+     * @return the transformed object
+     */
+    protected V transformValue(final V object) {
+        if (valueTransformer == null) {
+            return object;
+        }
+        return valueTransformer.transform(object);
+    }
+
+    /**
+     * Transforms a map.
+     * <p>
+     * The transformer itself may throw an exception if necessary.
+     *
+     * @param map the map to transform
+     * @return the transformed object
+     */
+    @SuppressWarnings("unchecked")
+    protected Map<K, V> transformMap(final Map<? extends K, ? extends V> map) {
+        if (map.isEmpty()) {
+            return (Map<K, V>) map;
+        }
+        final Map<K, V> result = new LinkedMap<K, V>(map.size());
+
+        for (final Map.Entry<? extends K, ? extends V> entry : map.entrySet()) {
+            result.put(transformKey(entry.getKey()), transformValue(entry.getValue()));
+        }
+        return result;
+    }
+
+    /**
+     * Transforms a MultiValuedMap.
+     * <p>
+     * The transformer itself may throw an exception if necessary.
+     *
+     * @param map the MultiValuedMap to transform
+     * @return the transformed object
+     */
+    @SuppressWarnings("unchecked")
+    protected MultiValuedMap<K, V> transformMultiValuedMap(final MultiValuedMap<? extends K, ? extends V> map) {
+        if (map.isEmpty()) {
+            return (MultiValuedMap<K, V>) map;
+        }
+        final MultiValuedMap<K, V> result = new MultiValuedHashMap<K, V>();
+
+        for (final Map.Entry<? extends K, ? extends V> entry : map.entries()) {
+            result.put(transformKey(entry.getKey()), transformValue(entry.getValue()));
+        }
+        return result;
+    }
+
+    @Override
+    public V put(K key, V value) {
+        K transformedKey = transformKey(key);
+        V transformedValue = transformValue(value);
+        return decorated().put(transformedKey, transformedValue);
+    }
+
+    @Override
+    @SuppressWarnings("unchecked")
+    public boolean putAll(K key, Iterable<? extends V> values) {
+        if (values == null || values.iterator() == null || !values.iterator().hasNext()) {
+            return false;
+        }
+        K transformedKey = transformKey(key);
+        List<V> transformedValues = new LinkedList<V>();
+        Iterator<V> it = (Iterator<V>) values.iterator();
+        while (it.hasNext()) {
+            transformedValues.add(transformValue(it.next()));
+        }
+        return decorated().putAll(transformedKey, transformedValues);
+    }
+
+    @Override
+    public void putAll(Map<? extends K, ? extends V> m) {
+        if (m == null) {
+            return;
+        }
+        decorated().putAll(transformMap(m));
+    }
+
+    @Override
+    public void putAll(MultiValuedMap<? extends K, ? extends V> m) {
+        if (m == null) {
+            return;
+        }
+        decorated().putAll(transformMultiValuedMap(m));
+    }
+
+}

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

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

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

Added: 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=1581553&view=auto
==============================================================================
--- commons/proper/collections/trunk/src/main/java/org/apache/commons/collections4/multimap/UnmodifiableMultiValuedMap.java (added)
+++ commons/proper/collections/trunk/src/main/java/org/apache/commons/collections4/multimap/UnmodifiableMultiValuedMap.java Tue Mar 25 22:01:07 2014
@@ -0,0 +1,134 @@
+/*
+ * 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.multimap;
+
+import java.util.Collection;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+
+import org.apache.commons.collections4.Bag;
+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.set.UnmodifiableSet;
+
+/**
+ * Decorates another {@link MultiValuedMap} to ensure it can't be altered.
+ * <p>
+ * Attempts to modify it will result in an UnsupportedOperationException.
+ *
+ * @param <K> the type of key elements
+ * @param <V> the type of value elements
+ *
+ * @since 4.1
+ * @version $Id$
+ */
+public class UnmodifiableMultiValuedMap<K, V>
+        extends AbstractMultiValuedMapDecorator<K, V> implements Unmodifiable {
+
+    /** Serialization version */
+    private static final long serialVersionUID = 1418669828214151566L;
+
+    /**
+     * Factory method to create an unmodifiable MultiValuedMap.
+     * <p>
+     * If the map passed in is already unmodifiable, it is returned.
+     *
+     * @param <K> the type of key elements
+     * @param <V> the type of value elements
+     * @param map the map to decorate, must not be null
+     * @return an unmodifiable MultiValuedMap
+     * @throws IllegalArgumentException if map is null
+     */
+    @SuppressWarnings("unchecked")
+    public static <K, V> UnmodifiableMultiValuedMap<K, V>
+            unmodifiableMultiValuedMap(MultiValuedMap<? extends K, ? extends V> map) {
+        if (map instanceof Unmodifiable) {
+            return (UnmodifiableMultiValuedMap<K, V>) map;
+        }
+        return new UnmodifiableMultiValuedMap<K, V>(map);
+    }
+
+    /**
+     * Constructor that wraps (not copies).
+     *
+     * @param map the MultiValuedMap to decorate, must not be null
+     * @throws IllegalArgumentException if the map is null
+     */
+    @SuppressWarnings("unchecked")
+    private UnmodifiableMultiValuedMap(final MultiValuedMap<? extends K, ? extends V> map) {
+        super((MultiValuedMap<K, V>) map);
+    }
+
+    @Override
+    public Collection<V> remove(Object key) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public boolean removeMapping(K key, V item) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void clear() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public V put(K key, V value) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public Set<K> keySet() {
+        return UnmodifiableSet.<K>unmodifiableSet(decorated().keySet());
+    }
+
+    @Override
+    public Collection<Entry<K, V>> entries() {
+        return UnmodifiableCollection.<Entry<K, V>>unmodifiableCollection(decorated().entries());
+    }
+
+    @Override
+    public Bag<K> keys() {
+        return UnmodifiableBag.<K>unmodifiableBag(decorated().keys());
+    }
+
+    @Override
+    public Collection<V> values() {
+        return UnmodifiableCollection.<V>unmodifiableCollection(decorated().values());
+    }
+
+    @Override
+    public boolean putAll(K key, Iterable<? extends V> values) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void putAll(Map<? extends K, ? extends V> m) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void putAll(MultiValuedMap<? extends K, ? extends V> m) {
+        throw new UnsupportedOperationException();
+    }
+
+}

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

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

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

Added: commons/proper/collections/trunk/src/main/java/org/apache/commons/collections4/multimap/package-info.java
URL: http://svn.apache.org/viewvc/commons/proper/collections/trunk/src/main/java/org/apache/commons/collections4/multimap/package-info.java?rev=1581553&view=auto
==============================================================================
--- commons/proper/collections/trunk/src/main/java/org/apache/commons/collections4/multimap/package-info.java (added)
+++ commons/proper/collections/trunk/src/main/java/org/apache/commons/collections4/multimap/package-info.java Tue Mar 25 22:01:07 2014
@@ -0,0 +1,34 @@
+/*
+ * 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.
+ */
+/**
+ * This package contains implementations of the {@link org.apache.commons.collections4.MultiValuedMap} interfaces.
+ * A MultiValuedMap holds a collection of values against each key.
+ * <p>
+ * The following implementations are provided in the package:
+ * <ul>
+ *   <li>MultiValuedHashMap - implementation that uses a HashMap to store the data
+ * </ul>
+ * <p>
+ * The following decorators are provided in the package:
+ * <ul>
+ *   <li>Transformed - transforms elements added to the MultiValuedMap
+ *   <li>Unmodifiable - ensures the collection cannot be altered
+ * </ul>
+ *
+ * @version $Id$
+ */
+package org.apache.commons.collections4.multimap;

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

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

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