View Javadoc
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.multimap;
18  
19  import java.io.IOException;
20  import java.io.ObjectInputStream;
21  import java.io.ObjectOutputStream;
22  import java.util.AbstractCollection;
23  import java.util.AbstractMap;
24  import java.util.AbstractSet;
25  import java.util.ArrayList;
26  import java.util.Collection;
27  import java.util.Iterator;
28  import java.util.Map;
29  import java.util.Map.Entry;
30  import java.util.Objects;
31  import java.util.Set;
32  
33  import org.apache.commons.collections4.CollectionUtils;
34  import org.apache.commons.collections4.IteratorUtils;
35  import org.apache.commons.collections4.MapIterator;
36  import org.apache.commons.collections4.MultiSet;
37  import org.apache.commons.collections4.MultiValuedMap;
38  import org.apache.commons.collections4.Transformer;
39  import org.apache.commons.collections4.iterators.AbstractIteratorDecorator;
40  import org.apache.commons.collections4.iterators.EmptyMapIterator;
41  import org.apache.commons.collections4.iterators.IteratorChain;
42  import org.apache.commons.collections4.iterators.LazyIteratorChain;
43  import org.apache.commons.collections4.iterators.TransformIterator;
44  import org.apache.commons.collections4.keyvalue.AbstractMapEntry;
45  import org.apache.commons.collections4.keyvalue.UnmodifiableMapEntry;
46  import org.apache.commons.collections4.multiset.AbstractMultiSet;
47  import org.apache.commons.collections4.multiset.UnmodifiableMultiSet;
48  
49  /**
50   * Abstract implementation of the {@link MultiValuedMap} interface to simplify
51   * the creation of subclass implementations.
52   * <p>
53   * Subclasses specify a Map implementation to use as the internal storage.
54   * </p>
55   *
56   * @param <K> the type of the keys in this map
57   * @param <V> the type of the values in this map
58   * @since 4.1
59   */
60  public abstract class AbstractMultiValuedMap<K, V> implements MultiValuedMap<K, V> {
61  
62      /**
63       * Inner class that provides the AsMap view.
64       */
65      private final class AsMap extends AbstractMap<K, Collection<V>> {
66          final class AsMapEntrySet extends AbstractSet<Map.Entry<K, Collection<V>>> {
67  
68              @Override
69              public void clear() {
70                  AsMap.this.clear();
71              }
72  
73              @Override
74              public boolean contains(final Object o) {
75                  return map.entrySet().contains(o);
76              }
77  
78              @Override
79              public Iterator<Map.Entry<K, Collection<V>>> iterator() {
80                  return new AsMapEntrySetIterator(map.entrySet().iterator());
81              }
82  
83              @Override
84              public boolean remove(final Object o) {
85                  if (!contains(o)) {
86                      return false;
87                  }
88                  final Map.Entry<?, ?> entry = (Map.Entry<?, ?>) o;
89                  AbstractMultiValuedMap.this.remove(entry.getKey());
90                  return true;
91              }
92  
93              @Override
94              public int size() {
95                  return AsMap.this.size();
96              }
97          }
98  
99          /**
100          * EntrySet iterator for the asMap view.
101          */
102         final class AsMapEntrySetIterator extends AbstractIteratorDecorator<Map.Entry<K, Collection<V>>> {
103 
104             AsMapEntrySetIterator(final Iterator<Map.Entry<K, Collection<V>>> iterator) {
105                 super(iterator);
106             }
107 
108             @Override
109             public Map.Entry<K, Collection<V>> next() {
110                 final Map.Entry<K, Collection<V>> entry = super.next();
111                 final K key = entry.getKey();
112                 return new UnmodifiableMapEntry<>(key, wrappedCollection(key));
113             }
114         }
115 
116         final transient Map<K, Collection<V>> map;
117 
118         AsMap(final Map<K, Collection<V>> map) {
119             this.map = map;
120         }
121 
122         @Override
123         public void clear() {
124             AbstractMultiValuedMap.this.clear();
125         }
126 
127         @Override
128         public boolean containsKey(final Object key) {
129             return map.containsKey(key);
130         }
131 
132         @Override
133         public Set<Map.Entry<K, Collection<V>>> entrySet() {
134             return new AsMapEntrySet();
135         }
136 
137         @Override
138         public boolean equals(final Object object) {
139             return this == object || map.equals(object);
140         }
141 
142         @Override
143         public Collection<V> get(final Object key) {
144             final Collection<V> collection = map.get(key);
145             if (collection == null) {
146                 return null;
147             }
148             @SuppressWarnings("unchecked")
149             final K k = (K) key;
150             return wrappedCollection(k);
151         }
152 
153         @Override
154         public int hashCode() {
155             return map.hashCode();
156         }
157 
158         @Override
159         public Set<K> keySet() {
160             return AbstractMultiValuedMap.this.keySet();
161         }
162 
163         @Override
164         public Collection<V> remove(final Object key) {
165             final Collection<V> collection = map.remove(key);
166             if (collection == null) {
167                 return null;
168             }
169 
170             final Collection<V> output = createCollection();
171             output.addAll(collection);
172             collection.clear();
173             return output;
174         }
175 
176         @Override
177         public int size() {
178             return map.size();
179         }
180 
181         @Override
182         public String toString() {
183             return map.toString();
184         }
185     }
186 
187     /**
188      * Inner class that provides the Entry<K, V> view
189      */
190     private final class EntryValues extends AbstractCollection<Entry<K, V>> {
191 
192         @Override
193         public Iterator<Entry<K, V>> iterator() {
194             return new LazyIteratorChain<Entry<K, V>>() {
195 
196                 final Collection<K> keysCol = new ArrayList<>(getMap().keySet());
197                 final Iterator<K> keyIterator = keysCol.iterator();
198 
199                 @Override
200                 protected Iterator<? extends Entry<K, V>> nextIterator(final int count) {
201                     if (!keyIterator.hasNext()) {
202                         return null;
203                     }
204                     final K key = keyIterator.next();
205                     final Transformer<V, Entry<K, V>> entryTransformer = input -> new MultiValuedMapEntry(key, input);
206                     return new TransformIterator<>(new ValuesIterator(key), entryTransformer);
207                 }
208             };
209         }
210 
211         @Override
212         public int size() {
213             return AbstractMultiValuedMap.this.size();
214         }
215 
216     }
217 
218     /**
219      * Inner class that provides a MultiSet<K> keys view.
220      */
221     private final class KeysMultiSet extends AbstractMultiSet<K> {
222 
223         private final class MapEntryTransformer implements Transformer<Map.Entry<K, Collection<V>>, MultiSet.Entry<K>> {
224 
225             @Override
226             public MultiSet.Entry<K> transform(final Map.Entry<K, Collection<V>> mapEntry) {
227                 return new AbstractMultiSet.AbstractEntry<K>() {
228 
229                     @Override
230                     public int getCount() {
231                         return mapEntry.getValue().size();
232                     }
233 
234                     @Override
235                     public K getElement() {
236                         return mapEntry.getKey();
237                     }
238                 };
239             }
240         }
241 
242         @Override
243         public boolean contains(final Object o) {
244             return getMap().containsKey(o);
245         }
246 
247         @Override
248         protected Iterator<MultiSet.Entry<K>> createEntrySetIterator() {
249             final MapEntryTransformer transformer = new MapEntryTransformer();
250             return IteratorUtils.transformedIterator(map.entrySet().iterator(), transformer);
251         }
252 
253         @Override
254         public int getCount(final Object object) {
255             int count = 0;
256             final Collection<V> col = AbstractMultiValuedMap.this.getMap().get(object);
257             if (col != null) {
258                 count = col.size();
259             }
260             return count;
261         }
262 
263         @Override
264         public boolean isEmpty() {
265             return getMap().isEmpty();
266         }
267 
268         @Override
269         public int size() {
270             return AbstractMultiValuedMap.this.size();
271         }
272 
273         @Override
274         protected int uniqueElements() {
275             return getMap().size();
276         }
277     }
278 
279     /**
280      * Inner class for MultiValuedMap Entries.
281      */
282     private final class MultiValuedMapEntry extends AbstractMapEntry<K, V> {
283 
284         MultiValuedMapEntry(final K key, final V value) {
285             super(key, value);
286         }
287 
288         @Override
289         public V setValue(final V value) {
290             throw new UnsupportedOperationException();
291         }
292 
293     }
294 
295     /**
296      * Inner class for MapIterator.
297      */
298     private final class MultiValuedMapIterator implements MapIterator<K, V> {
299 
300         private final Iterator<Entry<K, V>> it;
301 
302         private Entry<K, V> current;
303 
304         MultiValuedMapIterator() {
305             this.it = AbstractMultiValuedMap.this.entries().iterator();
306         }
307 
308         @Override
309         public K getKey() {
310             if (current == null) {
311                 throw new IllegalStateException();
312             }
313             return current.getKey();
314         }
315 
316         @Override
317         public V getValue() {
318             if (current == null) {
319                 throw new IllegalStateException();
320             }
321             return current.getValue();
322         }
323 
324         @Override
325         public boolean hasNext() {
326             return it.hasNext();
327         }
328 
329         @Override
330         public K next() {
331             current = it.next();
332             return current.getKey();
333         }
334 
335         @Override
336         public void remove() {
337             it.remove();
338         }
339 
340         @Override
341         public V setValue(final V value) {
342             if (current == null) {
343                 throw new IllegalStateException();
344             }
345             return current.setValue(value);
346         }
347 
348     }
349 
350     /**
351      * Inner class that provides the values view.
352      */
353     private final class Values extends AbstractCollection<V> {
354         @Override
355         public void clear() {
356             AbstractMultiValuedMap.this.clear();
357         }
358 
359         @Override
360         public Iterator<V> iterator() {
361             final IteratorChain<V> chain = new IteratorChain<>();
362             for (final K k : keySet()) {
363                 chain.addIterator(new ValuesIterator(k));
364             }
365             return chain;
366         }
367 
368         @Override
369         public int size() {
370             return AbstractMultiValuedMap.this.size();
371         }
372     }
373 
374     /**
375      * Inner class that provides the values iterator.
376      */
377     private final class ValuesIterator implements Iterator<V> {
378         private final Object key;
379         private final Collection<V> values;
380         private final Iterator<V> iterator;
381 
382         ValuesIterator(final Object key) {
383             this.key = key;
384             this.values = getMap().get(key);
385             this.iterator = values.iterator();
386         }
387 
388         @Override
389         public boolean hasNext() {
390             return iterator.hasNext();
391         }
392 
393         @Override
394         public V next() {
395             return iterator.next();
396         }
397 
398         @Override
399         public void remove() {
400             iterator.remove();
401             if (values.isEmpty()) {
402                 AbstractMultiValuedMap.this.remove(key);
403             }
404         }
405     }
406 
407     /**
408      * Wrapped collection to handle add and remove on the collection returned
409      * by get(object).
410      * <p>
411      * Currently, the wrapped collection is not cached and has to be retrieved
412      * from the underlying map. This is safe, but not very efficient and
413      * should be improved in subsequent releases. For this purpose, the
414      * scope of this collection is set to package private to simplify later
415      * refactoring.
416      */
417     class WrappedCollection implements Collection<V> {
418 
419         protected final K key;
420 
421         WrappedCollection(final K key) {
422             this.key = key;
423         }
424 
425         @Override
426         public boolean add(final V value) {
427             Collection<V> coll = getMapping();
428             if (coll == null) {
429                 coll = createCollection();
430                 AbstractMultiValuedMap.this.map.put(key, coll);
431             }
432             return coll.add(value);
433         }
434 
435         @Override
436         public boolean addAll(final Collection<? extends V> other) {
437             Collection<V> coll = getMapping();
438             if (coll == null) {
439                 coll = createCollection();
440                 AbstractMultiValuedMap.this.map.put(key, coll);
441             }
442             return coll.addAll(other);
443         }
444 
445         @Override
446         public void clear() {
447             final Collection<V> coll = getMapping();
448             if (coll != null) {
449                 coll.clear();
450                 AbstractMultiValuedMap.this.remove(key);
451             }
452         }
453 
454         @Override
455         public boolean contains(final Object obj) {
456             final Collection<V> coll = getMapping();
457             return coll != null && coll.contains(obj);
458         }
459 
460         @Override
461         public boolean containsAll(final Collection<?> other) {
462             final Collection<V> coll = getMapping();
463             return coll != null && coll.containsAll(other);
464         }
465 
466         protected Collection<V> getMapping() {
467             return getMap().get(key);
468         }
469 
470         @Override
471         public boolean isEmpty() {
472             final Collection<V> coll = getMapping();
473             return coll == null || coll.isEmpty();
474         }
475 
476         @Override
477         public Iterator<V> iterator() {
478             final Collection<V> coll = getMapping();
479             if (coll == null) {
480                 return IteratorUtils.EMPTY_ITERATOR;
481             }
482             return new ValuesIterator(key);
483         }
484 
485         @Override
486         public boolean remove(final Object item) {
487             final Collection<V> coll = getMapping();
488             if (coll == null) {
489                 return false;
490             }
491 
492             final boolean result = coll.remove(item);
493             if (coll.isEmpty()) {
494                 AbstractMultiValuedMap.this.remove(key);
495             }
496             return result;
497         }
498 
499         @Override
500         public boolean removeAll(final Collection<?> c) {
501             final Collection<V> coll = getMapping();
502             if (coll == null) {
503                 return false;
504             }
505 
506             final boolean result = coll.removeAll(c);
507             if (coll.isEmpty()) {
508                 AbstractMultiValuedMap.this.remove(key);
509             }
510             return result;
511         }
512 
513         @Override
514         public boolean retainAll(final Collection<?> c) {
515             final Collection<V> coll = getMapping();
516             if (coll == null) {
517                 return false;
518             }
519 
520             final boolean result = coll.retainAll(c);
521             if (coll.isEmpty()) {
522                 AbstractMultiValuedMap.this.remove(key);
523             }
524             return result;
525         }
526 
527         @Override
528         public int size() {
529             final Collection<V> coll = getMapping();
530             return coll == null ? 0 : coll.size();
531         }
532 
533         @Override
534         public Object[] toArray() {
535             final Collection<V> coll = getMapping();
536             if (coll == null) {
537                 return CollectionUtils.EMPTY_COLLECTION.toArray();
538             }
539             return coll.toArray();
540         }
541 
542         @Override
543         @SuppressWarnings("unchecked")
544         public <T> T[] toArray(final T[] a) {
545             final Collection<V> coll = getMapping();
546             if (coll == null) {
547                 return (T[]) CollectionUtils.EMPTY_COLLECTION.toArray(a);
548             }
549             return coll.toArray(a);
550         }
551 
552         @Override
553         public String toString() {
554             final Collection<V> coll = getMapping();
555             if (coll == null) {
556                 return CollectionUtils.EMPTY_COLLECTION.toString();
557             }
558             return coll.toString();
559         }
560 
561     }
562 
563     /** The values view */
564     private transient Collection<V> valuesView;
565 
566     /** The EntryValues view */
567     private transient EntryValues entryValuesView;
568 
569     /** The KeyMultiSet view */
570     private transient MultiSet<K> keysMultiSetView;
571 
572     /** The AsMap view */
573     private transient AsMap asMapView;
574 
575     /** The map used to store the data */
576     private transient Map<K, Collection<V>> map;
577 
578     /**
579      * Constructor needed for subclass serialisation.
580      */
581     protected AbstractMultiValuedMap() {
582     }
583 
584     /**
585      * Constructor that wraps (not copies).
586      *
587      * @param map  the map to wrap, must not be null
588      * @throws NullPointerException if the map is null
589      */
590     @SuppressWarnings("unchecked")
591     protected AbstractMultiValuedMap(final Map<K, ? extends Collection<V>> map) {
592         this.map = (Map<K, Collection<V>>) Objects.requireNonNull(map, "map");
593     }
594 
595     @Override
596     public Map<K, Collection<V>> asMap() {
597         return asMapView != null ? asMapView : (asMapView = new AsMap(map));
598     }
599 
600     @Override
601     public void clear() {
602         getMap().clear();
603     }
604 
605     @Override
606     public boolean containsKey(final Object key) {
607         return getMap().containsKey(key);
608     }
609 
610     @Override
611     public boolean containsMapping(final Object key, final Object value) {
612         final Collection<V> coll = getMap().get(key);
613         return coll != null && coll.contains(value);
614     }
615 
616     @Override
617     public boolean containsValue(final Object value) {
618         return values().contains(value);
619     }
620 
621     /**
622      * Creates a new Collection typed for a given subclass.
623      *
624      * @return a new Collection typed for a given subclass.
625      */
626     protected abstract Collection<V> createCollection();
627 
628     /**
629      * Read the map in using a custom routine.
630      * @param in the input stream
631      * @throws IOException any of the usual I/O related exceptions
632      * @throws ClassNotFoundException if the stream contains an object which class cannot be loaded
633      * @throws ClassCastException if the stream does not contain the correct objects
634      */
635     protected void doReadObject(final ObjectInputStream in)
636             throws IOException, ClassNotFoundException {
637         final int entrySize = in.readInt();
638         for (int i = 0; i < entrySize; i++) {
639             @SuppressWarnings("unchecked") // This will fail at runtime if the stream is incorrect
640             final K key = (K) in.readObject();
641             final Collection<V> values = get(key);
642             final int valueSize = in.readInt();
643             for (int j = 0; j < valueSize; j++) {
644                 @SuppressWarnings("unchecked") // see above
645                 final V value = (V) in.readObject();
646                 values.add(value);
647             }
648         }
649     }
650 
651     /**
652      * Write the map out using a custom routine.
653      * @param out the output stream
654      * @throws IOException any of the usual I/O related exceptions
655      */
656     protected void doWriteObject(final ObjectOutputStream out) throws IOException {
657         out.writeInt(map.size());
658         for (final Map.Entry<K, Collection<V>> entry : map.entrySet()) {
659             out.writeObject(entry.getKey());
660             out.writeInt(entry.getValue().size());
661             for (final V value : entry.getValue()) {
662                 out.writeObject(value);
663             }
664         }
665     }
666 
667     @Override
668     public Collection<Entry<K, V>> entries() {
669         return entryValuesView != null ? entryValuesView : (entryValuesView = new EntryValues());
670     }
671 
672     @Override
673     public boolean equals(final Object obj) {
674         if (this == obj) {
675             return true;
676         }
677         if (obj instanceof MultiValuedMap) {
678             return asMap().equals(((MultiValuedMap<?, ?>) obj).asMap());
679         }
680         return false;
681     }
682 
683     /**
684      * Gets the collection of values associated with the specified key. This
685      * would return an empty collection in case the mapping is not present
686      *
687      * @param key the key to retrieve
688      * @return the {@code Collection} of values, will return an empty {@code Collection} for no mapping
689      */
690     @Override
691     public Collection<V> get(final K key) {
692         return wrappedCollection(key);
693     }
694 
695     /**
696      * Gets the map being wrapped.
697      *
698      * @return the wrapped map
699      */
700     protected Map<K, ? extends Collection<V>> getMap() {
701         return map;
702     }
703 
704     @Override
705     public int hashCode() {
706         return getMap().hashCode();
707     }
708 
709     @Override
710     public boolean isEmpty() {
711         return getMap().isEmpty();
712     }
713 
714     /**
715      * Returns a {@link MultiSet} view of the key mapping contained in this map.
716      * <p>
717      * Returns a MultiSet of keys with its values count as the count of the MultiSet.
718      * This multiset is backed by the map, so any changes in the map is reflected here.
719      * Any method which modifies this multiset like {@code add}, {@code remove},
720      * {@link Iterator#remove()} etc throws {@code UnsupportedOperationException}.
721      *
722      * @return a bag view of the key mapping contained in this map
723      */
724     @Override
725     public MultiSet<K> keys() {
726         if (keysMultiSetView == null) {
727             keysMultiSetView = UnmodifiableMultiSet.unmodifiableMultiSet(new KeysMultiSet());
728         }
729         return keysMultiSetView;
730     }
731 
732     @Override
733     public Set<K> keySet() {
734         return getMap().keySet();
735     }
736 
737     @Override
738     public MapIterator<K, V> mapIterator() {
739         if (isEmpty()) {
740             return EmptyMapIterator.emptyMapIterator();
741         }
742         return new MultiValuedMapIterator();
743     }
744 
745     /**
746      * Adds the value to the collection associated with the specified key.
747      * <p>
748      * Unlike a normal {@code Map} the previous value is not replaced.
749      * Instead the new value is added to the collection stored against the key.
750      *
751      * @param key the key to store against
752      * @param value the value to add to the collection at the key
753      * @return the value added if the map changed and null if the map did not change
754      */
755     @Override
756     public boolean put(final K key, final V value) {
757         Collection<V> coll = getMap().get(key);
758         if (coll == null) {
759             coll = createCollection();
760             if (coll.add(value)) {
761                 map.put(key, coll);
762                 return true;
763             }
764             return false;
765         }
766         return coll.add(value);
767     }
768 
769     /**
770      * Adds Iterable values to the collection associated with the specified key.
771      *
772      * @param key the key to store against
773      * @param values the values to add to the collection at the key, may not be null
774      * @return true if this map changed
775      * @throws NullPointerException if values is null
776      */
777     @Override
778     public boolean putAll(final K key, final Iterable<? extends V> values) {
779         Objects.requireNonNull(values, "values");
780 
781         if (values instanceof Collection<?>) {
782             final Collection<? extends V> valueCollection = (Collection<? extends V>) values;
783             return !valueCollection.isEmpty() && get(key).addAll(valueCollection);
784         }
785         final Iterator<? extends V> it = values.iterator();
786         return it.hasNext() && CollectionUtils.addAll(get(key), it);
787     }
788 
789     /**
790      * Copies all of the mappings from the specified map to this map. The effect
791      * of this call is equivalent to that of calling {@link #put(Object,Object)
792      * put(k, v)} on this map once for each mapping from key {@code k} to value
793      * {@code v} in the specified map. The behavior of this operation is
794      * undefined if the specified map is modified while the operation is in
795      * progress.
796      *
797      * @param map mappings to be stored in this map, may not be null
798      * @return true if the map changed as a result of this operation
799      * @throws NullPointerException if map is null
800      */
801     @Override
802     public boolean putAll(final Map<? extends K, ? extends V> map) {
803         Objects.requireNonNull(map, "map");
804         boolean changed = false;
805         for (final Map.Entry<? extends K, ? extends V> entry : map.entrySet()) {
806             changed |= put(entry.getKey(), entry.getValue());
807         }
808         return changed;
809     }
810 
811     /**
812      * Copies all of the mappings from the specified MultiValuedMap to this map.
813      * The effect of this call is equivalent to that of calling
814      * {@link #put(Object,Object) put(k, v)} on this map once for each mapping
815      * from key {@code k} to value {@code v} in the specified map. The
816      * behavior of this operation is undefined if the specified map is modified
817      * while the operation is in progress.
818      *
819      * @param map mappings to be stored in this map, may not be null
820      * @return true if the map changed as a result of this operation
821      * @throws NullPointerException if map is null
822      */
823     @Override
824     public boolean putAll(final MultiValuedMap<? extends K, ? extends V> map) {
825         Objects.requireNonNull(map, "map");
826         boolean changed = false;
827         for (final Map.Entry<? extends K, ? extends V> entry : map.entries()) {
828             changed |= put(entry.getKey(), entry.getValue());
829         }
830         return changed;
831     }
832 
833     /**
834      * Removes all values associated with the specified key.
835      * <p>
836      * A subsequent {@code get(Object)} would return an empty collection.
837      *
838      * @param key  the key to remove values from
839      * @return the {@code Collection} of values removed, will return an
840      *   empty, unmodifiable collection for no mapping found
841      */
842     @Override
843     public Collection<V> remove(final Object key) {
844         return CollectionUtils.emptyIfNull(getMap().remove(key));
845     }
846 
847     /**
848      * Removes a specific key/value mapping from the multivalued map.
849      * <p>
850      * The value is removed from the collection mapped to the specified key.
851      * Other values attached to that key are unaffected.
852      * <p>
853      * If the last value for a key is removed, an empty collection would be
854      * returned from a subsequent {@link #get(Object)}.
855      *
856      * @param key the key to remove from
857      * @param value the value to remove
858      * @return true if the mapping was removed, false otherwise
859      */
860     @Override
861     public boolean removeMapping(final Object key, final Object value) {
862         final Collection<V> coll = getMap().get(key);
863         if (coll == null) {
864             return false;
865         }
866         final boolean changed = coll.remove(value);
867         if (coll.isEmpty()) {
868             getMap().remove(key);
869         }
870         return changed;
871     }
872 
873     /**
874      * Sets the map being wrapped.
875      * <p>
876      * <strong>NOTE:</strong> this method should only be used during deserialization
877      *
878      * @param map the map to wrap
879      */
880     @SuppressWarnings("unchecked")
881     protected void setMap(final Map<K, ? extends Collection<V>> map) {
882         this.map = (Map<K, Collection<V>>) map;
883     }
884 
885     /**
886      * {@inheritDoc}
887      * <p>
888      * This implementation does <strong>not</strong> cache the total size
889      * of the multivalued map, but rather calculates it by iterating
890      * over the entries of the underlying map.
891      */
892     @Override
893     public int size() {
894         // the total size should be cached to improve performance
895         // but this requires that all modifications of the multimap
896         // (including the wrapped collections and entry/value
897         // collections) are tracked.
898         int size = 0;
899         for (final Collection<V> col : getMap().values()) {
900             size += col.size();
901         }
902         return size;
903     }
904 
905     @Override
906     public String toString() {
907         return getMap().toString();
908     }
909 
910     /**
911      * Gets a collection containing all the values in the map.
912      * <p>
913      * Returns a collection containing all the values from all keys.
914      *
915      * @return a collection view of the values contained in this map
916      */
917     @Override
918     public Collection<V> values() {
919         final Collection<V> vs = valuesView;
920         return vs != null ? vs : (valuesView = new Values());
921     }
922 
923     Collection<V> wrappedCollection(final K key) {
924         return new WrappedCollection(key);
925     }
926 
927 }