001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * https://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.apache.commons.collections4.multimap; 018 019import java.io.IOException; 020import java.io.ObjectInputStream; 021import java.io.ObjectOutputStream; 022import java.util.AbstractCollection; 023import java.util.AbstractMap; 024import java.util.AbstractSet; 025import java.util.ArrayList; 026import java.util.Collection; 027import java.util.Iterator; 028import java.util.Map; 029import java.util.Map.Entry; 030import java.util.Objects; 031import java.util.Set; 032 033import org.apache.commons.collections4.CollectionUtils; 034import org.apache.commons.collections4.IterableUtils; 035import org.apache.commons.collections4.IteratorUtils; 036import org.apache.commons.collections4.MapIterator; 037import org.apache.commons.collections4.MultiSet; 038import org.apache.commons.collections4.MultiValuedMap; 039import org.apache.commons.collections4.Transformer; 040import org.apache.commons.collections4.iterators.AbstractIteratorDecorator; 041import org.apache.commons.collections4.iterators.EmptyMapIterator; 042import org.apache.commons.collections4.iterators.IteratorChain; 043import org.apache.commons.collections4.iterators.LazyIteratorChain; 044import org.apache.commons.collections4.iterators.TransformIterator; 045import org.apache.commons.collections4.keyvalue.AbstractMapEntry; 046import org.apache.commons.collections4.keyvalue.UnmodifiableMapEntry; 047import org.apache.commons.collections4.multiset.AbstractMultiSet; 048import org.apache.commons.collections4.multiset.UnmodifiableMultiSet; 049 050/** 051 * Abstract implementation of the {@link MultiValuedMap} interface to simplify 052 * the creation of subclass implementations. 053 * <p> 054 * Subclasses specify a Map implementation to use as the internal storage. 055 * </p> 056 * 057 * @param <K> The type of the keys in this map 058 * @param <V> The type of the values in this map 059 * @since 4.1 060 */ 061public abstract class AbstractMultiValuedMap<K, V> implements MultiValuedMap<K, V> { 062 063 /** 064 * Inner class that provides the AsMap view. 065 */ 066 private final class AsMap extends AbstractMap<K, Collection<V>> { 067 final class AsMapEntrySet extends AbstractSet<Map.Entry<K, Collection<V>>> { 068 069 @Override 070 public void clear() { 071 AsMap.this.clear(); 072 } 073 074 @Override 075 public boolean contains(final Object o) { 076 return map.entrySet().contains(o); 077 } 078 079 @Override 080 public Iterator<Map.Entry<K, Collection<V>>> iterator() { 081 return new AsMapEntrySetIterator(map.entrySet().iterator()); 082 } 083 084 @Override 085 public boolean remove(final Object o) { 086 if (!contains(o)) { 087 return false; 088 } 089 final Map.Entry<?, ?> entry = (Map.Entry<?, ?>) o; 090 AbstractMultiValuedMap.this.remove(entry.getKey()); 091 return true; 092 } 093 094 @Override 095 public int size() { 096 return AsMap.this.size(); 097 } 098 } 099 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}