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