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