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 * http://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.IteratorUtils; 035import org.apache.commons.collections4.MapIterator; 036import org.apache.commons.collections4.MultiSet; 037import org.apache.commons.collections4.MultiValuedMap; 038import org.apache.commons.collections4.Transformer; 039import org.apache.commons.collections4.iterators.AbstractIteratorDecorator; 040import org.apache.commons.collections4.iterators.EmptyMapIterator; 041import org.apache.commons.collections4.iterators.IteratorChain; 042import org.apache.commons.collections4.iterators.LazyIteratorChain; 043import org.apache.commons.collections4.iterators.TransformIterator; 044import org.apache.commons.collections4.keyvalue.AbstractMapEntry; 045import org.apache.commons.collections4.keyvalue.UnmodifiableMapEntry; 046import org.apache.commons.collections4.multiset.AbstractMultiSet; 047import org.apache.commons.collections4.multiset.UnmodifiableMultiSet; 048 049/** 050 * Abstract implementation of the {@link MultiValuedMap} interface to simplify 051 * the creation of subclass implementations. 052 * <p> 053 * Subclasses specify a Map implementation to use as the internal storage. 054 * </p> 055 * 056 * @param <K> the type of the keys in this map 057 * @param <V> the type of the values in this map 058 * @since 4.1 059 */ 060public abstract class AbstractMultiValuedMap<K, V> implements MultiValuedMap<K, V> { 061 062 /** 063 * Inner class that provides the AsMap view. 064 */ 065 private final class AsMap extends AbstractMap<K, Collection<V>> { 066 final class AsMapEntrySet extends AbstractSet<Map.Entry<K, Collection<V>>> { 067 068 @Override 069 public void clear() { 070 AsMap.this.clear(); 071 } 072 073 @Override 074 public boolean contains(final Object o) { 075 return map.entrySet().contains(o); 076 } 077 078 @Override 079 public Iterator<Map.Entry<K, Collection<V>>> iterator() { 080 return new AsMapEntrySetIterator(map.entrySet().iterator()); 081 } 082 083 @Override 084 public boolean remove(final Object o) { 085 if (!contains(o)) { 086 return false; 087 } 088 final Map.Entry<?, ?> entry = (Map.Entry<?, ?>) o; 089 AbstractMultiValuedMap.this.remove(entry.getKey()); 090 return true; 091 } 092 093 @Override 094 public int size() { 095 return AsMap.this.size(); 096 } 097 } 098 099 /** 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}