MultiValueMap.java

  1. /*
  2.  * Licensed to the Apache Software Foundation (ASF) under one or more
  3.  * contributor license agreements.  See the NOTICE file distributed with
  4.  * this work for additional information regarding copyright ownership.
  5.  * The ASF licenses this file to You under the Apache License, Version 2.0
  6.  * (the "License"); you may not use this file except in compliance with
  7.  * the License.  You may obtain a copy of the License at
  8.  *
  9.  *      http://www.apache.org/licenses/LICENSE-2.0
  10.  *
  11.  * Unless required by applicable law or agreed to in writing, software
  12.  * distributed under the License is distributed on an "AS IS" BASIS,
  13.  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14.  * See the License for the specific language governing permissions and
  15.  * limitations under the License.
  16.  */
  17. package org.apache.commons.collections4.map;

  18. import java.io.IOException;
  19. import java.io.ObjectInputStream;
  20. import java.io.ObjectOutputStream;
  21. import java.io.Serializable;
  22. import java.util.AbstractCollection;
  23. import java.util.ArrayList;
  24. import java.util.Collection;
  25. import java.util.HashMap;
  26. import java.util.Iterator;
  27. import java.util.Map;
  28. import java.util.Set;

  29. import org.apache.commons.collections4.CollectionUtils;
  30. import org.apache.commons.collections4.Factory;
  31. import org.apache.commons.collections4.FunctorException;
  32. import org.apache.commons.collections4.MultiMap;
  33. import org.apache.commons.collections4.Transformer;
  34. import org.apache.commons.collections4.iterators.EmptyIterator;
  35. import org.apache.commons.collections4.iterators.IteratorChain;
  36. import org.apache.commons.collections4.iterators.LazyIteratorChain;
  37. import org.apache.commons.collections4.iterators.TransformIterator;

  38. /**
  39.  * A MultiValueMap decorates another map, allowing it to have
  40.  * more than one value for a key.
  41.  * <p>
  42.  * A {@code MultiMap} is a Map with slightly different semantics.
  43.  * Putting a value into the map will add the value to a Collection at that key.
  44.  * Getting a value will return a Collection, holding all the values put to that key.
  45.  * </p>
  46.  * <p>
  47.  * This implementation is a decorator, allowing any Map implementation
  48.  * to be used as the base.
  49.  * </p>
  50.  * <p>
  51.  * In addition, this implementation allows the type of collection used
  52.  * for the values to be controlled. By default, an {@code ArrayList}
  53.  * is used, however a {@code Class} to instantiate may be specified,
  54.  * or a factory that returns a {@code Collection} instance.
  55.  * </p>
  56.  * <p>
  57.  * <strong>Note that MultiValueMap is not synchronized and is not thread-safe.</strong>
  58.  * If you wish to use this map from multiple threads concurrently, you must use
  59.  * appropriate synchronization. This class may throw exceptions when accessed
  60.  * by concurrent threads without synchronization.
  61.  * </p>
  62.  *
  63.  * @param <K> the type of the keys in this map
  64.  * @param <V> the type of the values in this map
  65.  * @since 3.2
  66.  * @deprecated since 4.1, use {@link org.apache.commons.collections4.MultiValuedMap MultiValuedMap} instead
  67.  */
  68. @Deprecated
  69. public class MultiValueMap<K, V> extends AbstractMapDecorator<K, Object> implements MultiMap<K, V>, Serializable {

  70.     /**
  71.      * Inner class that provides a simple reflection factory.
  72.      *
  73.      * @param <T> the type of results supplied by this supplier.
  74.      */
  75.     private static final class ReflectionFactory<T extends Collection<?>> implements Factory<T>, Serializable {

  76.         /** Serialization version */
  77.         private static final long serialVersionUID = 2986114157496788874L;

  78.         private final Class<T> clazz;

  79.         ReflectionFactory(final Class<T> clazz) {
  80.             this.clazz = clazz;
  81.         }

  82.         @Override
  83.         public T create() {
  84.             try {
  85.                 return clazz.getDeclaredConstructor().newInstance();
  86.             } catch (final Exception ex) {
  87.                 throw new FunctorException("Cannot instantiate class: " + clazz, ex);
  88.             }
  89.         }

  90.         /**
  91.          * Deserializes an instance from an ObjectInputStream.
  92.          *
  93.          * @param in The source ObjectInputStream.
  94.          * @throws IOException            Any of the usual Input/Output related exceptions.
  95.          * @throws ClassNotFoundException A class of a serialized object cannot be found.
  96.          */
  97.         private void readObject(final ObjectInputStream is) throws IOException, ClassNotFoundException {
  98.             is.defaultReadObject();
  99.             // ensure that the de-serialized class is a Collection, COLLECTIONS-580
  100.             if (clazz != null && !Collection.class.isAssignableFrom(clazz)) {
  101.                 throw new UnsupportedOperationException();
  102.             }
  103.         }
  104.     }

  105.     /**
  106.      * Inner class that provides the values view.
  107.      */
  108.     private final class Values extends AbstractCollection<V> {
  109.         @Override
  110.         public void clear() {
  111.             MultiValueMap.this.clear();
  112.         }

  113.         @Override
  114.         public Iterator<V> iterator() {
  115.             final IteratorChain<V> chain = new IteratorChain<>();
  116.             for (final K k : keySet()) {
  117.                 chain.addIterator(new ValuesIterator(k));
  118.             }
  119.             return chain;
  120.         }

  121.         @Override
  122.         public int size() {
  123.             return totalSize();
  124.         }
  125.     }
  126.     /**
  127.      * Inner class that provides the values iterator.
  128.      */
  129.     private final class ValuesIterator implements Iterator<V> {
  130.         private final Object key;
  131.         private final Collection<V> values;
  132.         private final Iterator<V> iterator;

  133.         ValuesIterator(final Object key) {
  134.             this.key = key;
  135.             this.values = getCollection(key);
  136.             this.iterator = values.iterator();
  137.         }

  138.         @Override
  139.         public boolean hasNext() {
  140.             return iterator.hasNext();
  141.         }

  142.         @Override
  143.         public V next() {
  144.             return iterator.next();
  145.         }

  146.         @Override
  147.         public void remove() {
  148.             iterator.remove();
  149.             if (values.isEmpty()) {
  150.                 MultiValueMap.this.remove(key);
  151.             }
  152.         }
  153.     }

  154.     /** Serialization version */
  155.     private static final long serialVersionUID = -2214159910087182007L;

  156.     /**
  157.      * Creates a map which decorates the given {@code map} and
  158.      * maps keys to collections of type {@code collectionClass}.
  159.      *
  160.      * @param <K>  the key type
  161.      * @param <V>  the value type
  162.      * @param <C>  the collection class type
  163.      * @param map  the map to wrap
  164.      * @param collectionClass  the type of the collection class
  165.      * @return a new multi-value map
  166.      * @since 4.0
  167.      */
  168.     public static <K, V, C extends Collection<V>> MultiValueMap<K, V> multiValueMap(final Map<K, ? super C> map,
  169.                                                                                     final Class<C> collectionClass) {
  170.         return new MultiValueMap<>(map, new ReflectionFactory<>(collectionClass));
  171.     }

  172.     /**
  173.      * Creates a map which decorates the given {@code map} and
  174.      * creates the value collections using the supplied {@code collectionFactory}.
  175.      *
  176.      * @param <K>  the key type
  177.      * @param <V>  the value type
  178.      * @param <C>  the collection class type
  179.      * @param map  the map to decorate
  180.      * @param collectionFactory  the collection factory (must return a Collection object).
  181.      * @return a new multi-value map
  182.      * @since 4.0
  183.      */
  184.     public static <K, V, C extends Collection<V>> MultiValueMap<K, V> multiValueMap(final Map<K, ? super C> map,
  185.             final Factory<C> collectionFactory) {
  186.         return new MultiValueMap<>(map, collectionFactory);
  187.     }

  188.     /**
  189.      * Creates a map which wraps the given map and
  190.      * maps keys to ArrayLists.
  191.      *
  192.      * @param <K>  the key type
  193.      * @param <V>  the value type
  194.      * @param map  the map to wrap
  195.      * @return a new multi-value map
  196.      * @since 4.0
  197.      */
  198.     @SuppressWarnings({ "unchecked", "rawtypes" })
  199.     public static <K, V> MultiValueMap<K, V> multiValueMap(final Map<K, ? super Collection<V>> map) {
  200.         return MultiValueMap.<K, V, ArrayList>multiValueMap((Map<K, ? super Collection>) map, ArrayList.class);
  201.     }

  202.     /** The factory for creating value collections. */
  203.     private final Factory<? extends Collection<V>> collectionFactory;

  204.     /** The cached values. */
  205.     private transient Collection<V> valuesView;

  206.     /**
  207.      * Creates a MultiValueMap based on a {@code HashMap} and
  208.      * storing the multiple values in an {@code ArrayList}.
  209.      */
  210.     @SuppressWarnings({ "unchecked", "rawtypes" })
  211.     public MultiValueMap() {
  212.         this(new HashMap<>(), new ReflectionFactory(ArrayList.class));
  213.     }

  214.     /**
  215.      * Creates a MultiValueMap which decorates the given {@code map} and
  216.      * creates the value collections using the supplied {@code collectionFactory}.
  217.      *
  218.      * @param <C>  the collection class type
  219.      * @param map  the map to decorate
  220.      * @param collectionFactory  the collection factory which must return a Collection instance
  221.      */
  222.     @SuppressWarnings("unchecked")
  223.     protected <C extends Collection<V>> MultiValueMap(final Map<K, ? super C> map,
  224.                                                       final Factory<C> collectionFactory) {
  225.         super((Map<K, Object>) map);
  226.         if (collectionFactory == null) {
  227.             throw new IllegalArgumentException("The factory must not be null");
  228.         }
  229.         this.collectionFactory = collectionFactory;
  230.     }

  231.     /**
  232.      * Clear the map.
  233.      */
  234.     @Override
  235.     public void clear() {
  236.         // If you believe that you have GC issues here, try uncommenting this code
  237. //        Set pairs = getMap().entrySet();
  238. //        Iterator pairsIterator = pairs.iterator();
  239. //        while (pairsIterator.hasNext()) {
  240. //            Map.Entry keyValuePair = (Map.Entry) pairsIterator.next();
  241. //            Collection coll = (Collection) keyValuePair.getValue();
  242. //            coll.clear();
  243. //        }
  244.         decorated().clear();
  245.     }

  246.     /**
  247.      * Checks whether the map contains the value specified.
  248.      * <p>
  249.      * This checks all collections against all keys for the value, and thus could be slow.
  250.      * </p>
  251.      *
  252.      * @param value  the value to search for
  253.      * @return true if the map contains the value
  254.      */
  255.     @Override
  256.     @SuppressWarnings("unchecked")
  257.     public boolean containsValue(final Object value) {
  258.         final Set<Map.Entry<K, Object>> pairs = decorated().entrySet();
  259.         if (pairs != null) {
  260.             for (final Map.Entry<K, Object> entry : pairs) {
  261.                 if (((Collection<V>) entry.getValue()).contains(value)) {
  262.                     return true;
  263.                 }
  264.             }
  265.         }
  266.         return false;
  267.     }

  268.     /**
  269.      * Checks whether the collection at the specified key contains the value.
  270.      *
  271.      * @param key  the key to search for
  272.      * @param value  the value to search for
  273.      * @return true if the map contains the value
  274.      */
  275.     public boolean containsValue(final Object key, final Object value) {
  276.         final Collection<V> coll = getCollection(key);
  277.         if (coll == null) {
  278.             return false;
  279.         }
  280.         return coll.contains(value);
  281.     }

  282.     /**
  283.      * Creates a new instance of the map value Collection container
  284.      * using the factory.
  285.      * <p>
  286.      * This method can be overridden to perform your own processing
  287.      * instead of using the factory.
  288.      * </p>
  289.      *
  290.      * @param size  the collection size that is about to be added
  291.      * @return the new collection
  292.      */
  293.     protected Collection<V> createCollection(final int size) {
  294.         return collectionFactory.get();
  295.     }

  296.     /**
  297.      * {@inheritDoc}
  298.      * <p>
  299.      * Note: the returned Entry objects will contain as value a {@link Collection}
  300.      * of all values that are mapped to the given key. To get a "flattened" version
  301.      * of all mappings contained in this map, use {@link #iterator()}.
  302.      * </p>
  303.      *
  304.      * @see #iterator()
  305.      */
  306.     @Override
  307.     public Set<Entry<K, Object>> entrySet() { // NOPMD
  308.         return super.entrySet();
  309.     }

  310.     /**
  311.      * Gets the collection mapped to the specified key.
  312.      * This method is a convenience method to typecast the result of {@code get(key)}.
  313.      *
  314.      * @param key  the key to retrieve
  315.      * @return the collection mapped to the key, null if no mapping
  316.      */
  317.     @SuppressWarnings("unchecked")
  318.     public Collection<V> getCollection(final Object key) {
  319.         return (Collection<V>) decorated().get(key);
  320.     }

  321.     /**
  322.      * Gets an iterator for all mappings stored in this {@link MultiValueMap}.
  323.      * <p>
  324.      * The iterator will return multiple Entry objects with the same key
  325.      * if there are multiple values mapped to this key.
  326.      * </p>
  327.      * <p>
  328.      * Note: calling {@link java.util.Map.Entry#setValue(Object)} on any of the returned
  329.      * elements will result in a {@link UnsupportedOperationException}.
  330.      * </p>
  331.      *
  332.      * @return the iterator of all mappings in this map
  333.      * @since 4.0
  334.      */
  335.     public Iterator<Entry<K, V>> iterator() {
  336.         final Collection<K> allKeys = new ArrayList<>(keySet());
  337.         final Iterator<K> keyIterator = allKeys.iterator();

  338.         return new LazyIteratorChain<Entry<K, V>>() {
  339.             @Override
  340.             protected Iterator<? extends Entry<K, V>> nextIterator(final int count) {
  341.                 if (!keyIterator.hasNext()) {
  342.                     return null;
  343.                 }
  344.                 final K key = keyIterator.next();
  345.                 final Transformer<V, Entry<K, V>> transformer = input -> new Entry<K, V>() {
  346.                     @Override
  347.                     public K getKey() {
  348.                         return key;
  349.                     }

  350.                     @Override
  351.                     public V getValue() {
  352.                         return input;
  353.                     }

  354.                     @Override
  355.                     public V setValue(final V value) {
  356.                         throw new UnsupportedOperationException();
  357.                     }
  358.                 };
  359.                 return new TransformIterator<>(new ValuesIterator(key), transformer);
  360.             }
  361.         };
  362.     }

  363.     /**
  364.      * Gets an iterator for the collection mapped to the specified key.
  365.      *
  366.      * @param key  the key to get an iterator for
  367.      * @return the iterator of the collection at the key, empty iterator if key not in map
  368.      */
  369.     public Iterator<V> iterator(final Object key) {
  370.         if (!containsKey(key)) {
  371.             return EmptyIterator.<V>emptyIterator();
  372.         }
  373.         return new ValuesIterator(key);
  374.     }

  375.     /**
  376.      * Adds the value to the collection associated with the specified key.
  377.      * <p>
  378.      * Unlike a normal {@code Map} the previous value is not replaced.
  379.      * Instead, the new value is added to the collection stored against the key.
  380.      * </p>
  381.      *
  382.      * @param key  the key to store against
  383.      * @param value  the value to add to the collection at the key
  384.      * @return the value added if the map changed and null if the map did not change
  385.      */
  386.     @Override
  387.     @SuppressWarnings("unchecked")
  388.     public Object put(final K key, final Object value) {
  389.         boolean result = false;
  390.         Collection<V> coll = getCollection(key);
  391.         if (coll == null) {
  392.             coll = createCollection(1);  // might produce a non-empty collection
  393.             coll.add((V) value);
  394.             if (!coll.isEmpty()) {
  395.                 // only add if non-zero size to maintain class state
  396.                 decorated().put(key, coll);
  397.                 result = true;  // map definitely changed
  398.             }
  399.         } else {
  400.             result = coll.add((V) value);
  401.         }
  402.         return result ? value : null;
  403.     }

  404.     /**
  405.      * Adds a collection of values to the collection associated with
  406.      * the specified key.
  407.      *
  408.      * @param key  the key to store against
  409.      * @param values  the values to add to the collection at the key, null ignored
  410.      * @return true if this map changed
  411.      */
  412.     public boolean putAll(final K key, final Collection<V> values) {
  413.         if (values == null || values.isEmpty()) {
  414.             return false;
  415.         }
  416.         boolean result = false;
  417.         Collection<V> coll = getCollection(key);
  418.         if (coll == null) {
  419.             coll = createCollection(values.size());  // might produce a non-empty collection
  420.             coll.addAll(values);
  421.             if (!coll.isEmpty()) {
  422.                 // only add if non-zero size to maintain class state
  423.                 decorated().put(key, coll);
  424.                 result = true;  // map definitely changed
  425.             }
  426.         } else {
  427.             result = coll.addAll(values);
  428.         }
  429.         return result;
  430.     }

  431.     /**
  432.      * Override superclass to ensure that MultiMap instances are
  433.      * correctly handled.
  434.      * <p>
  435.      * If you call this method with a normal map, each entry is
  436.      * added using {@code put(Object,Object)}.
  437.      * If you call this method with a multi map, each entry is
  438.      * added using {@code putAll(Object,Collection)}.
  439.      * </p>
  440.      *
  441.      * @param map  the map to copy (either a normal or multi map)
  442.      */
  443.     @Override
  444.     @SuppressWarnings("unchecked")
  445.     public void putAll(final Map<? extends K, ?> map) {
  446.         if (map instanceof MultiMap) {
  447.             for (final Map.Entry<? extends K, Object> entry : ((MultiMap<? extends K, V>) map).entrySet()) {
  448.                 putAll(entry.getKey(), (Collection<V>) entry.getValue());
  449.             }
  450.         } else {
  451.             for (final Map.Entry<? extends K, ?> entry : map.entrySet()) {
  452.                 put(entry.getKey(), entry.getValue());
  453.             }
  454.         }
  455.     }

  456.     /**
  457.      * Deserializes the map in using a custom routine.
  458.      *
  459.      * @param in  the input stream
  460.      * @throws IOException if an error occurs while reading from the stream
  461.      * @throws ClassNotFoundException if an object read from the stream cannot be loaded
  462.      * @since 4.0
  463.      */
  464.     @SuppressWarnings("unchecked") // (1) should only fail if input stream is incorrect
  465.     private void readObject(final ObjectInputStream in) throws IOException, ClassNotFoundException {
  466.         in.defaultReadObject();
  467.         map = (Map<K, Object>) in.readObject(); // (1)
  468.     }

  469.     /**
  470.      * Removes a specific value from map.
  471.      * <p>
  472.      * The item is removed from the collection mapped to the specified key.
  473.      * Other values attached to that key are unaffected.
  474.      * </p>
  475.      * <p>
  476.      * If the last value for a key is removed, {@code null} will be returned
  477.      * from a subsequent {@code get(key)}.
  478.      * </p>
  479.      *
  480.      * @param key  the key to remove from
  481.      * @param value the value to remove
  482.      * @return {@code true} if the mapping was removed, {@code false} otherwise
  483.      */
  484.     @Override
  485.     public boolean removeMapping(final Object key, final Object value) {
  486.         final Collection<V> valuesForKey = getCollection(key);
  487.         if (valuesForKey == null) {
  488.             return false;
  489.         }
  490.         final boolean removed = valuesForKey.remove(value);
  491.         if (!removed) {
  492.             return false;
  493.         }
  494.         if (valuesForKey.isEmpty()) {
  495.             remove(key);
  496.         }
  497.         return true;
  498.     }

  499.     /**
  500.      * Gets the size of the collection mapped to the specified key.
  501.      *
  502.      * @param key  the key to get size for
  503.      * @return the size of the collection at the key, zero if key not in map
  504.      */
  505.     public int size(final Object key) {
  506.         final Collection<V> coll = getCollection(key);
  507.         if (coll == null) {
  508.             return 0;
  509.         }
  510.         return coll.size();
  511.     }

  512.     /**
  513.      * Gets the total size of the map by counting all the values.
  514.      *
  515.      * @return the total size of the map counting all values
  516.      */
  517.     public int totalSize() {
  518.         int total = 0;
  519.         for (final Object v : decorated().values()) {
  520.             total += CollectionUtils.size(v);
  521.         }
  522.         return total;
  523.     }

  524.     /**
  525.      * Gets a collection containing all the values in the map.
  526.      * <p>
  527.      * This returns a collection containing the combination of values from all keys.
  528.      * </p>
  529.      *
  530.      * @return a collection view of the values contained in this map
  531.      */
  532.     @Override
  533.     @SuppressWarnings("unchecked")
  534.     public Collection<Object> values() {
  535.         final Collection<V> vs = valuesView;
  536.         return (Collection<Object>) (vs != null ? vs : (valuesView = new Values()));
  537.     }

  538.     /**
  539.      * Serializes this object to an ObjectOutputStream.
  540.      *
  541.      * @param out the target ObjectOutputStream.
  542.      * @throws IOException thrown when an I/O errors occur writing to the target stream.
  543.      * @since 4.0
  544.      */
  545.     private void writeObject(final ObjectOutputStream out) throws IOException {
  546.         out.defaultWriteObject();
  547.         out.writeObject(map);
  548.     }

  549. }