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