View Javadoc
1   /*
2    * Copyright (C) 2007 The Guava Authors
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    * http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  
17  package com.google.common.collect;
18  
19  import static com.google.common.base.Preconditions.checkPositionIndex;
20  import static com.google.common.base.Preconditions.checkState;
21  import static com.google.common.collect.CollectPreconditions.checkRemove;
22  import static java.util.Collections.unmodifiableList;
23  
24  import com.google.common.annotations.GwtCompatible;
25  import com.google.common.annotations.GwtIncompatible;
26  
27  import java.io.IOException;
28  import java.io.ObjectInputStream;
29  import java.io.ObjectOutputStream;
30  import java.io.Serializable;
31  import java.util.AbstractSequentialList;
32  import java.util.Collection;
33  import java.util.ConcurrentModificationException;
34  import java.util.HashMap;
35  import java.util.Iterator;
36  import java.util.List;
37  import java.util.ListIterator;
38  import java.util.Map;
39  import java.util.Map.Entry;
40  import java.util.NoSuchElementException;
41  import java.util.Set;
42  
43  import javax.annotation.Nullable;
44  
45  /**
46   * An implementation of {@code ListMultimap} that supports deterministic
47   * iteration order for both keys and values. The iteration order is preserved
48   * across non-distinct key values. For example, for the following multimap
49   * definition: <pre>   {@code
50   *
51   *   Multimap<K, V> multimap = LinkedListMultimap.create();
52   *   multimap.put(key1, foo);
53   *   multimap.put(key2, bar);
54   *   multimap.put(key1, baz);}</pre>
55   *
56   * ... the iteration order for {@link #keys()} is {@code [key1, key2, key1]},
57   * and similarly for {@link #entries()}. Unlike {@link LinkedHashMultimap}, the
58   * iteration order is kept consistent between keys, entries and values. For
59   * example, calling: <pre>   {@code
60   *
61   *   map.remove(key1, foo);}</pre>
62   *
63   * <p>changes the entries iteration order to {@code [key2=bar, key1=baz]} and the
64   * key iteration order to {@code [key2, key1]}. The {@link #entries()} iterator
65   * returns mutable map entries, and {@link #replaceValues} attempts to preserve
66   * iteration order as much as possible.
67   *
68   * <p>The collections returned by {@link #keySet()} and {@link #asMap} iterate
69   * through the keys in the order they were first added to the multimap.
70   * Similarly, {@link #get}, {@link #removeAll}, and {@link #replaceValues}
71   * return collections that iterate through the values in the order they were
72   * added. The collections generated by {@link #entries()}, {@link #keys()}, and
73   * {@link #values} iterate across the key-value mappings in the order they were
74   * added to the multimap.
75   *
76   * <p>The {@link #values()} and {@link #entries()} methods both return a
77   * {@code List}, instead of the {@code Collection} specified by the {@link
78   * ListMultimap} interface.
79   *
80   * <p>The methods {@link #get}, {@link #keySet()}, {@link #keys()},
81   * {@link #values}, {@link #entries()}, and {@link #asMap} return collections
82   * that are views of the multimap. If the multimap is modified while an
83   * iteration over any of those collections is in progress, except through the
84   * iterator's methods, the results of the iteration are undefined.
85   *
86   * <p>Keys and values may be null. All optional multimap methods are supported,
87   * and all returned views are modifiable.
88   *
89   * <p>This class is not threadsafe when any concurrent operations update the
90   * multimap. Concurrent read operations will work correctly. To allow concurrent
91   * update operations, wrap your multimap with a call to {@link
92   * Multimaps#synchronizedListMultimap}.
93   *
94   * <p>See the Guava User Guide article on <a href=
95   * "http://code.google.com/p/guava-libraries/wiki/NewCollectionTypesExplained#Multimap">
96   * {@code Multimap}</a>.
97   *
98   * @author Mike Bostock
99   * @since 2.0 (imported from Google Collections Library)
100  */
101 @GwtCompatible(serializable = true, emulated = true)
102 public class LinkedListMultimap<K, V> extends AbstractMultimap<K, V>
103     implements ListMultimap<K, V>, Serializable {
104   /*
105    * Order is maintained using a linked list containing all key-value pairs. In
106    * addition, a series of disjoint linked lists of "siblings", each containing
107    * the values for a specific key, is used to implement {@link
108    * ValueForKeyIterator} in constant time.
109    */
110 
111   private static final class Node<K, V> extends AbstractMapEntry<K, V> {
112     final K key;
113     V value;
114     Node<K, V> next; // the next node (with any key)
115     Node<K, V> previous; // the previous node (with any key)
116     Node<K, V> nextSibling; // the next node with the same key
117     Node<K, V> previousSibling; // the previous node with the same key
118 
119     Node(@Nullable K key, @Nullable V value) {
120       this.key = key;
121       this.value = value;
122     }
123 
124     @Override
125     public K getKey() {
126       return key;
127     }
128 
129     @Override
130     public V getValue() {
131       return value;
132     }
133 
134     @Override
135     public V setValue(@Nullable V newValue) {
136       V result = value;
137       this.value = newValue;
138       return result;
139     }
140   }
141   
142   private static class KeyList<K, V> {
143     Node<K, V> head;
144     Node<K, V> tail;
145     int count;
146     
147     KeyList(Node<K, V> firstNode) {
148       this.head = firstNode;
149       this.tail = firstNode;
150       firstNode.previousSibling = null;
151       firstNode.nextSibling = null;
152       this.count = 1;
153     }
154   }
155 
156   private transient Node<K, V> head; // the head for all keys
157   private transient Node<K, V> tail; // the tail for all keys
158   private transient Map<K, KeyList<K, V>> keyToKeyList;
159   private transient int size;
160   
161   /*
162    * Tracks modifications to keyToKeyList so that addition or removal of keys invalidates
163    * preexisting iterators. This does *not* track simple additions and removals of values
164    * that are not the first to be added or last to be removed for their key.
165    */
166   private transient int modCount;
167 
168   /**
169    * Creates a new, empty {@code LinkedListMultimap} with the default initial
170    * capacity.
171    */
172   public static <K, V> LinkedListMultimap<K, V> create() {
173     return new LinkedListMultimap<K, V>();
174   }
175 
176   /**
177    * Constructs an empty {@code LinkedListMultimap} with enough capacity to hold
178    * the specified number of keys without rehashing.
179    *
180    * @param expectedKeys the expected number of distinct keys
181    * @throws IllegalArgumentException if {@code expectedKeys} is negative
182    */
183   public static <K, V> LinkedListMultimap<K, V> create(int expectedKeys) {
184     return new LinkedListMultimap<K, V>(expectedKeys);
185   }
186 
187   /**
188    * Constructs a {@code LinkedListMultimap} with the same mappings as the
189    * specified {@code Multimap}. The new multimap has the same
190    * {@link Multimap#entries()} iteration order as the input multimap.
191    *
192    * @param multimap the multimap whose contents are copied to this multimap
193    */
194   public static <K, V> LinkedListMultimap<K, V> create(
195       Multimap<? extends K, ? extends V> multimap) {
196     return new LinkedListMultimap<K, V>(multimap);
197   }
198 
199   LinkedListMultimap() {
200     keyToKeyList = Maps.newHashMap();
201   }
202 
203   private LinkedListMultimap(int expectedKeys) {
204     keyToKeyList = new HashMap<K, KeyList<K, V>>(expectedKeys);
205   }
206 
207   private LinkedListMultimap(Multimap<? extends K, ? extends V> multimap) {
208     this(multimap.keySet().size());
209     putAll(multimap);
210   }
211 
212   /**
213    * Adds a new node for the specified key-value pair before the specified
214    * {@code nextSibling} element, or at the end of the list if {@code
215    * nextSibling} is null. Note: if {@code nextSibling} is specified, it MUST be
216    * for an node for the same {@code key}!
217    */
218   private Node<K, V> addNode(
219       @Nullable K key, @Nullable V value, @Nullable Node<K, V> nextSibling) {
220     Node<K, V> node = new Node<K, V>(key, value);
221     if (head == null) { // empty list
222       head = tail = node;
223       keyToKeyList.put(key, new KeyList<K, V>(node));
224       modCount++;
225     } else if (nextSibling == null) { // non-empty list, add to tail
226       tail.next = node;
227       node.previous = tail;
228       tail = node;
229       KeyList<K, V> keyList = keyToKeyList.get(key);
230       if (keyList == null) {
231         keyToKeyList.put(key, keyList = new KeyList<K, V>(node));
232         modCount++;
233       } else {
234         keyList.count++;
235         Node<K, V> keyTail = keyList.tail;
236         keyTail.nextSibling = node;
237         node.previousSibling = keyTail;
238         keyList.tail = node;
239       }
240     } else { // non-empty list, insert before nextSibling
241       KeyList<K, V> keyList = keyToKeyList.get(key);
242       keyList.count++;
243       node.previous = nextSibling.previous;
244       node.previousSibling = nextSibling.previousSibling;
245       node.next = nextSibling;
246       node.nextSibling = nextSibling;
247       if (nextSibling.previousSibling == null) { // nextSibling was key head
248         keyToKeyList.get(key).head = node;
249       } else {
250         nextSibling.previousSibling.nextSibling = node;
251       }
252       if (nextSibling.previous == null) { // nextSibling was head
253         head = node;
254       } else {
255         nextSibling.previous.next = node;
256       }
257       nextSibling.previous = node;
258       nextSibling.previousSibling = node;
259     }
260     size++;
261     return node;
262   }
263 
264   /**
265    * Removes the specified node from the linked list. This method is only
266    * intended to be used from the {@code Iterator} classes. See also {@link
267    * LinkedListMultimap#removeAllNodes(Object)}.
268    */
269   private void removeNode(Node<K, V> node) {
270     if (node.previous != null) {
271       node.previous.next = node.next;
272     } else { // node was head
273       head = node.next;
274     }
275     if (node.next != null) {
276       node.next.previous = node.previous;
277     } else { // node was tail
278       tail = node.previous;
279     }
280     if (node.previousSibling == null && node.nextSibling == null) {
281       KeyList<K, V> keyList = keyToKeyList.remove(node.key);
282       keyList.count = 0;
283       modCount++;
284     } else {
285       KeyList<K, V> keyList = keyToKeyList.get(node.key);
286       keyList.count--;
287 
288       if (node.previousSibling == null) {
289         keyList.head = node.nextSibling;
290       } else {
291         node.previousSibling.nextSibling = node.nextSibling;
292       }
293       
294       if (node.nextSibling == null) {
295         keyList.tail = node.previousSibling;
296       } else {
297         node.nextSibling.previousSibling = node.previousSibling;
298       }
299     }
300     size--;
301   }
302 
303   /** Removes all nodes for the specified key. */
304   private void removeAllNodes(@Nullable Object key) {
305     Iterators.clear(new ValueForKeyIterator(key));
306   }
307 
308   /** Helper method for verifying that an iterator element is present. */
309   private static void checkElement(@Nullable Object node) {
310     if (node == null) {
311       throw new NoSuchElementException();
312     }
313   }
314 
315   /** An {@code Iterator} over all nodes. */
316   private class NodeIterator implements ListIterator<Entry<K, V>> {
317     int nextIndex;
318     Node<K, V> next;
319     Node<K, V> current;
320     Node<K, V> previous;
321     int expectedModCount = modCount;
322 
323     NodeIterator(int index) {
324       int size = size();
325       checkPositionIndex(index, size);
326       if (index >= (size / 2)) {
327         previous = tail;
328         nextIndex = size;
329         while (index++ < size) {
330           previous();
331         }
332       } else {
333         next = head;
334         while (index-- > 0) {
335           next();
336         }
337       }
338       current = null;
339     }
340     private void checkForConcurrentModification() {
341       if (modCount != expectedModCount) {
342         throw new ConcurrentModificationException();
343       }
344     }
345     @Override
346     public boolean hasNext() {
347       checkForConcurrentModification();
348       return next != null;
349     }
350     @Override
351     public Node<K, V> next() {
352       checkForConcurrentModification();
353       checkElement(next);
354       previous = current = next;
355       next = next.next;
356       nextIndex++;
357       return current;
358     }
359     @Override
360     public void remove() {
361       checkForConcurrentModification();
362       checkRemove(current != null);
363       if (current != next) { // after call to next()
364         previous = current.previous;
365         nextIndex--;
366       } else { // after call to previous()
367         next = current.next;
368       }
369       removeNode(current);
370       current = null;
371       expectedModCount = modCount;
372     }
373     @Override
374     public boolean hasPrevious() {
375       checkForConcurrentModification();
376       return previous != null;
377     }
378     @Override
379     public Node<K, V> previous() {
380       checkForConcurrentModification();
381       checkElement(previous);
382       next = current = previous;
383       previous = previous.previous;
384       nextIndex--;
385       return current;
386     }
387     @Override
388     public int nextIndex() {
389       return nextIndex;
390     }
391     @Override
392     public int previousIndex() {
393       return nextIndex - 1;
394     }
395     @Override
396     public void set(Entry<K, V> e) {
397       throw new UnsupportedOperationException();
398     }
399     @Override
400     public void add(Entry<K, V> e) {
401       throw new UnsupportedOperationException();
402     }
403     void setValue(V value) {
404       checkState(current != null);
405       current.value = value;
406     }
407   }
408 
409   /** An {@code Iterator} over distinct keys in key head order. */
410   private class DistinctKeyIterator implements Iterator<K> {
411     final Set<K> seenKeys = Sets.<K>newHashSetWithExpectedSize(keySet().size());
412     Node<K, V> next = head;
413     Node<K, V> current;
414     int expectedModCount = modCount;
415     
416     private void checkForConcurrentModification() {
417       if (modCount != expectedModCount) {
418         throw new ConcurrentModificationException();
419       }
420     }
421     @Override
422     public boolean hasNext() {
423       checkForConcurrentModification();
424       return next != null;
425     }
426     @Override
427     public K next() {
428       checkForConcurrentModification();
429       checkElement(next);
430       current = next;
431       seenKeys.add(current.key);
432       do { // skip ahead to next unseen key
433         next = next.next;
434       } while ((next != null) && !seenKeys.add(next.key));
435       return current.key;
436     }
437     @Override
438     public void remove() {
439       checkForConcurrentModification();
440       checkRemove(current != null);
441       removeAllNodes(current.key);
442       current = null;
443       expectedModCount = modCount;
444     }
445   }
446 
447   /** A {@code ListIterator} over values for a specified key. */
448   private class ValueForKeyIterator implements ListIterator<V> {
449     final Object key;
450     int nextIndex;
451     Node<K, V> next;
452     Node<K, V> current;
453     Node<K, V> previous;
454 
455     /** Constructs a new iterator over all values for the specified key. */
456     ValueForKeyIterator(@Nullable Object key) {
457       this.key = key;
458       KeyList<K, V> keyList = keyToKeyList.get(key);
459       next = (keyList == null) ? null : keyList.head;
460     }
461 
462     /**
463      * Constructs a new iterator over all values for the specified key starting
464      * at the specified index. This constructor is optimized so that it starts
465      * at either the head or the tail, depending on which is closer to the
466      * specified index. This allows adds to the tail to be done in constant
467      * time.
468      *
469      * @throws IndexOutOfBoundsException if index is invalid
470      */
471     public ValueForKeyIterator(@Nullable Object key, int index) {
472       KeyList<K, V> keyList = keyToKeyList.get(key);
473       int size = (keyList == null) ? 0 : keyList.count;
474       checkPositionIndex(index, size);
475       if (index >= (size / 2)) {
476         previous = (keyList == null) ? null : keyList.tail;
477         nextIndex = size;
478         while (index++ < size) {
479           previous();
480         }
481       } else {
482         next = (keyList == null) ? null : keyList.head;
483         while (index-- > 0) {
484           next();
485         }
486       }
487       this.key = key;
488       current = null;
489     }
490 
491     @Override
492     public boolean hasNext() {
493       return next != null;
494     }
495 
496     @Override
497     public V next() {
498       checkElement(next);
499       previous = current = next;
500       next = next.nextSibling;
501       nextIndex++;
502       return current.value;
503     }
504 
505     @Override
506     public boolean hasPrevious() {
507       return previous != null;
508     }
509 
510     @Override
511     public V previous() {
512       checkElement(previous);
513       next = current = previous;
514       previous = previous.previousSibling;
515       nextIndex--;
516       return current.value;
517     }
518 
519     @Override
520     public int nextIndex() {
521       return nextIndex;
522     }
523 
524     @Override
525     public int previousIndex() {
526       return nextIndex - 1;
527     }
528 
529     @Override
530     public void remove() {
531       checkRemove(current != null);
532       if (current != next) { // after call to next()
533         previous = current.previousSibling;
534         nextIndex--;
535       } else { // after call to previous()
536         next = current.nextSibling;
537       }
538       removeNode(current);
539       current = null;
540     }
541 
542     @Override
543     public void set(V value) {
544       checkState(current != null);
545       current.value = value;
546     }
547 
548     @Override
549     @SuppressWarnings("unchecked")
550     public void add(V value) {
551       previous = addNode((K) key, value, next);
552       nextIndex++;
553       current = null;
554     }
555   }
556 
557   // Query Operations
558 
559   @Override
560   public int size() {
561     return size;
562   }
563 
564   @Override
565   public boolean isEmpty() {
566     return head == null;
567   }
568 
569   @Override
570   public boolean containsKey(@Nullable Object key) {
571     return keyToKeyList.containsKey(key);
572   }
573 
574   @Override
575   public boolean containsValue(@Nullable Object value) {
576     return values().contains(value);
577   }
578 
579   // Modification Operations
580 
581   /**
582    * Stores a key-value pair in the multimap.
583    *
584    * @param key key to store in the multimap
585    * @param value value to store in the multimap
586    * @return {@code true} always
587    */
588   @Override
589   public boolean put(@Nullable K key, @Nullable V value) {
590     addNode(key, value, null);
591     return true;
592   }
593 
594   // Bulk Operations
595 
596   /**
597    * {@inheritDoc}
598    *
599    * <p>If any entries for the specified {@code key} already exist in the
600    * multimap, their values are changed in-place without affecting the iteration
601    * order.
602    *
603    * <p>The returned list is immutable and implements
604    * {@link java.util.RandomAccess}.
605    */
606   @Override
607   public List<V> replaceValues(@Nullable K key, Iterable<? extends V> values) {
608     List<V> oldValues = getCopy(key);
609     ListIterator<V> keyValues = new ValueForKeyIterator(key);
610     Iterator<? extends V> newValues = values.iterator();
611 
612     // Replace existing values, if any.
613     while (keyValues.hasNext() && newValues.hasNext()) {
614       keyValues.next();
615       keyValues.set(newValues.next());
616     }
617 
618     // Remove remaining old values, if any.
619     while (keyValues.hasNext()) {
620       keyValues.next();
621       keyValues.remove();
622     }
623 
624     // Add remaining new values, if any.
625     while (newValues.hasNext()) {
626       keyValues.add(newValues.next());
627     }
628 
629     return oldValues;
630   }
631 
632   private List<V> getCopy(@Nullable Object key) {
633     return unmodifiableList(Lists.newArrayList(new ValueForKeyIterator(key)));
634   }
635 
636   /**
637    * {@inheritDoc}
638    *
639    * <p>The returned list is immutable and implements
640    * {@link java.util.RandomAccess}.
641    */
642   @Override
643   public List<V> removeAll(@Nullable Object key) {
644     List<V> oldValues = getCopy(key);
645     removeAllNodes(key);
646     return oldValues;
647   }
648 
649   @Override
650   public void clear() {
651     head = null;
652     tail = null;
653     keyToKeyList.clear();
654     size = 0;
655     modCount++;
656   }
657 
658   // Views
659 
660   /**
661    * {@inheritDoc}
662    *
663    * <p>If the multimap is modified while an iteration over the list is in
664    * progress (except through the iterator's own {@code add}, {@code set} or
665    * {@code remove} operations) the results of the iteration are undefined.
666    *
667    * <p>The returned list is not serializable and does not have random access.
668    */
669   @Override
670   public List<V> get(final @Nullable K key) {
671     return new AbstractSequentialList<V>() {
672       @Override public int size() {
673         KeyList<K, V> keyList = keyToKeyList.get(key);
674         return (keyList == null) ? 0 : keyList.count;
675       }
676       @Override public ListIterator<V> listIterator(int index) {
677         return new ValueForKeyIterator(key, index);
678       }
679     };
680   }
681 
682   @Override
683   Set<K> createKeySet() {
684     return new Sets.ImprovedAbstractSet<K>() {
685       @Override public int size() {
686         return keyToKeyList.size();
687       }
688       @Override public Iterator<K> iterator() {
689         return new DistinctKeyIterator();
690       }
691       @Override public boolean contains(Object key) { // for performance
692         return containsKey(key);
693       }
694       @Override
695       public boolean remove(Object o) { // for performance
696         return !LinkedListMultimap.this.removeAll(o).isEmpty();
697       }
698     };
699   }
700 
701   /**
702    * {@inheritDoc}
703    *
704    * <p>The iterator generated by the returned collection traverses the values
705    * in the order they were added to the multimap. Because the values may have
706    * duplicates and follow the insertion ordering, this method returns a {@link
707    * List}, instead of the {@link Collection} specified in the {@link
708    * ListMultimap} interface.
709    */
710   @Override
711   public List<V> values() {
712     return (List<V>) super.values();
713   }
714 
715   @Override
716   List<V> createValues() {
717     return new AbstractSequentialList<V>() {
718       @Override public int size() {
719         return size;
720       }
721 
722       @Override public ListIterator<V> listIterator(int index) {
723         final NodeIterator nodeItr = new NodeIterator(index);
724         return new TransformedListIterator<Entry<K, V>, V>(nodeItr) {
725           @Override
726           V transform(Entry<K, V> entry) {
727             return entry.getValue();
728           }
729 
730           @Override
731           public void set(V value) {
732             nodeItr.setValue(value);
733           }
734         };
735       }
736     };
737   }
738 
739   /**
740    * {@inheritDoc}
741    *
742    * <p>The iterator generated by the returned collection traverses the entries
743    * in the order they were added to the multimap. Because the entries may have
744    * duplicates and follow the insertion ordering, this method returns a {@link
745    * List}, instead of the {@link Collection} specified in the {@link
746    * ListMultimap} interface.
747    *
748    * <p>An entry's {@link Entry#getKey} method always returns the same key,
749    * regardless of what happens subsequently. As long as the corresponding
750    * key-value mapping is not removed from the multimap, {@link Entry#getValue}
751    * returns the value from the multimap, which may change over time, and {@link
752    * Entry#setValue} modifies that value. Removing the mapping from the
753    * multimap does not alter the value returned by {@code getValue()}, though a
754    * subsequent {@code setValue()} call won't update the multimap but will lead
755    * to a revised value being returned by {@code getValue()}.
756    */
757   @Override
758   public List<Entry<K, V>> entries() {
759     return (List<Entry<K, V>>) super.entries();
760   }
761 
762   @Override
763   List<Entry<K, V>> createEntries() {
764     return new AbstractSequentialList<Entry<K, V>>() {
765       @Override public int size() {
766         return size;
767       }
768 
769       @Override public ListIterator<Entry<K, V>> listIterator(int index) {
770         return new NodeIterator(index);
771       }
772     };
773   }
774 
775   @Override
776   Iterator<Entry<K, V>> entryIterator() {
777     throw new AssertionError("should never be called");
778   }
779 
780   @Override
781   Map<K, Collection<V>> createAsMap() {
782     return new Multimaps.AsMap<K, V>(this);
783   }
784 
785   /**
786    * @serialData the number of distinct keys, and then for each distinct key:
787    *     the first key, the number of values for that key, and the key's values,
788    *     followed by successive keys and values from the entries() ordering
789    */
790   @GwtIncompatible("java.io.ObjectOutputStream")
791   private void writeObject(ObjectOutputStream stream) throws IOException {
792     stream.defaultWriteObject();
793     stream.writeInt(size());
794     for (Entry<K, V> entry : entries()) {
795       stream.writeObject(entry.getKey());
796       stream.writeObject(entry.getValue());
797     }
798   }
799 
800   @GwtIncompatible("java.io.ObjectInputStream")
801   private void readObject(ObjectInputStream stream)
802       throws IOException, ClassNotFoundException {
803     stream.defaultReadObject();
804     keyToKeyList = Maps.newLinkedHashMap();
805     int size = stream.readInt();
806     for (int i = 0; i < size; i++) {
807       @SuppressWarnings("unchecked") // reading data stored by writeObject
808       K key = (K) stream.readObject();
809       @SuppressWarnings("unchecked") // reading data stored by writeObject
810       V value = (V) stream.readObject();
811       put(key, value);
812     }
813   }
814 
815   @GwtIncompatible("java serialization not supported")
816   private static final long serialVersionUID = 0;
817 }