001/*
002 * Copyright (C) 2007 The Guava Authors
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 * http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016
017package com.google.common.collect;
018
019import static com.google.common.base.Preconditions.checkState;
020import static com.google.common.collect.CollectPreconditions.checkNonnegative;
021import static java.util.Objects.requireNonNull;
022
023import com.google.common.annotations.GwtCompatible;
024import com.google.common.annotations.GwtIncompatible;
025import com.google.common.annotations.VisibleForTesting;
026import com.google.common.base.Objects;
027import com.google.errorprone.annotations.CanIgnoreReturnValue;
028import com.google.j2objc.annotations.WeakOuter;
029import java.io.IOException;
030import java.io.ObjectInputStream;
031import java.io.ObjectOutputStream;
032import java.util.Arrays;
033import java.util.Collection;
034import java.util.ConcurrentModificationException;
035import java.util.Iterator;
036import java.util.Map;
037import java.util.Map.Entry;
038import java.util.NoSuchElementException;
039import java.util.Set;
040import javax.annotation.CheckForNull;
041import org.checkerframework.checker.nullness.qual.Nullable;
042
043/**
044 * Implementation of {@code Multimap} that does not allow duplicate key-value entries and that
045 * returns collections whose iterators follow the ordering in which the data was added to the
046 * multimap.
047 *
048 * <p>The collections returned by {@code keySet}, {@code keys}, and {@code asMap} iterate through
049 * the keys in the order they were first added to the multimap. Similarly, {@code get}, {@code
050 * removeAll}, and {@code replaceValues} return collections that iterate through the values in the
051 * order they were added. The collections generated by {@code entries} and {@code values} iterate
052 * across the key-value mappings in the order they were added to the multimap.
053 *
054 * <p>The iteration ordering of the collections generated by {@code keySet}, {@code keys}, and
055 * {@code asMap} has a few subtleties. As long as the set of keys remains unchanged, adding or
056 * removing mappings does not affect the key iteration order. However, if you remove all values
057 * associated with a key and then add the key back to the multimap, that key will come last in the
058 * key iteration order.
059 *
060 * <p>The multimap does not store duplicate key-value pairs. Adding a new key-value pair equal to an
061 * existing key-value pair has no effect.
062 *
063 * <p>Keys and values may be null. All optional multimap methods are supported, and all returned
064 * views are modifiable.
065 *
066 * <p>This class is not threadsafe when any concurrent operations update the multimap. Concurrent
067 * read operations will work correctly. To allow concurrent update operations, wrap your multimap
068 * with a call to {@link Multimaps#synchronizedSetMultimap}.
069 *
070 * <p><b>Warning:</b> Do not modify either a key <i>or a value</i> of a {@code LinkedHashMultimap}
071 * in a way that affects its {@link Object#equals} behavior. Undefined behavior and bugs will
072 * result.
073 *
074 * <p>See the Guava User Guide article on <a href=
075 * "https://github.com/google/guava/wiki/NewCollectionTypesExplained#multimap">{@code Multimap}</a>.
076 *
077 * @author Jared Levy
078 * @author Louis Wasserman
079 * @since 2.0
080 */
081@GwtCompatible(serializable = true, emulated = true)
082@ElementTypesAreNonnullByDefault
083public final class LinkedHashMultimap<K extends @Nullable Object, V extends @Nullable Object>
084    extends LinkedHashMultimapGwtSerializationDependencies<K, V> {
085
086  /** Creates a new, empty {@code LinkedHashMultimap} with the default initial capacities. */
087  public static <K extends @Nullable Object, V extends @Nullable Object>
088      LinkedHashMultimap<K, V> create() {
089    return new LinkedHashMultimap<>(DEFAULT_KEY_CAPACITY, DEFAULT_VALUE_SET_CAPACITY);
090  }
091
092  /**
093   * Constructs an empty {@code LinkedHashMultimap} with enough capacity to hold the specified
094   * numbers of keys and values without rehashing.
095   *
096   * @param expectedKeys the expected number of distinct keys
097   * @param expectedValuesPerKey the expected average number of values per key
098   * @throws IllegalArgumentException if {@code expectedKeys} or {@code expectedValuesPerKey} is
099   *     negative
100   */
101  public static <K extends @Nullable Object, V extends @Nullable Object>
102      LinkedHashMultimap<K, V> create(int expectedKeys, int expectedValuesPerKey) {
103    return new LinkedHashMultimap<>(
104        Maps.capacity(expectedKeys), Maps.capacity(expectedValuesPerKey));
105  }
106
107  /**
108   * Constructs a {@code LinkedHashMultimap} with the same mappings as the specified multimap. If a
109   * key-value mapping appears multiple times in the input multimap, it only appears once in the
110   * constructed multimap. The new multimap has the same {@link Multimap#entries()} iteration order
111   * as the input multimap, except for excluding duplicate mappings.
112   *
113   * @param multimap the multimap whose contents are copied to this multimap
114   */
115  public static <K extends @Nullable Object, V extends @Nullable Object>
116      LinkedHashMultimap<K, V> create(Multimap<? extends K, ? extends V> multimap) {
117    LinkedHashMultimap<K, V> result = create(multimap.keySet().size(), DEFAULT_VALUE_SET_CAPACITY);
118    result.putAll(multimap);
119    return result;
120  }
121
122  private interface ValueSetLink<K extends @Nullable Object, V extends @Nullable Object> {
123    ValueSetLink<K, V> getPredecessorInValueSet();
124
125    ValueSetLink<K, V> getSuccessorInValueSet();
126
127    void setPredecessorInValueSet(ValueSetLink<K, V> entry);
128
129    void setSuccessorInValueSet(ValueSetLink<K, V> entry);
130  }
131
132  private static <K extends @Nullable Object, V extends @Nullable Object> void succeedsInValueSet(
133      ValueSetLink<K, V> pred, ValueSetLink<K, V> succ) {
134    pred.setSuccessorInValueSet(succ);
135    succ.setPredecessorInValueSet(pred);
136  }
137
138  private static <K extends @Nullable Object, V extends @Nullable Object> void succeedsInMultimap(
139      ValueEntry<K, V> pred, ValueEntry<K, V> succ) {
140    pred.setSuccessorInMultimap(succ);
141    succ.setPredecessorInMultimap(pred);
142  }
143
144  private static <K extends @Nullable Object, V extends @Nullable Object> void deleteFromValueSet(
145      ValueSetLink<K, V> entry) {
146    succeedsInValueSet(entry.getPredecessorInValueSet(), entry.getSuccessorInValueSet());
147  }
148
149  private static <K extends @Nullable Object, V extends @Nullable Object> void deleteFromMultimap(
150      ValueEntry<K, V> entry) {
151    succeedsInMultimap(entry.getPredecessorInMultimap(), entry.getSuccessorInMultimap());
152  }
153
154  /**
155   * LinkedHashMultimap entries are in no less than three coexisting linked lists: a bucket in the
156   * hash table for a {@code Set<V>} associated with a key, the linked list of insertion-ordered
157   * entries in that {@code Set<V>}, and the linked list of entries in the LinkedHashMultimap as a
158   * whole.
159   */
160  @VisibleForTesting
161  static final class ValueEntry<K extends @Nullable Object, V extends @Nullable Object>
162      extends ImmutableEntry<K, V> implements ValueSetLink<K, V> {
163    final int smearedValueHash;
164
165    @CheckForNull ValueEntry<K, V> nextInValueBucket;
166    /*
167     * The *InValueSet and *InMultimap fields below are null after construction, but we almost
168     * always call succeedsIn*() to initialize them immediately thereafter.
169     *
170     * The exception is the *InValueSet fields of multimapHeaderEntry, which are never set. (That
171     * works out fine as long as we continue to be careful not to try delete them or iterate past
172     * them.)
173     *
174     * We could consider "lying" and omitting @CheckNotNull from all these fields. Normally, I'm not
175     * a fan of that: What if we someday implement (presumably to be enabled during tests only)
176     * bytecode rewriting that checks for any null value that passes through an API with a
177     * known-non-null type? But that particular problem might not arise here, since we're not
178     * actually reading from the fields in any case in which they might be null (as proven by the
179     * requireNonNull checks below). Plus, we're *already* lying here, since newHeader passes a null
180     * key and value, which we pass to the superconstructor, even though the key and value type for
181     * a given entry might not include null. The right fix for the header problems is probably to
182     * define a separate MultimapLink interface with a separate "header" implementation, which
183     * hopefully could avoid implementing Entry or ValueSetLink at all. (But note that that approach
184     * requires us to define extra classes -- unfortunate under Android.) *Then* we could consider
185     * lying about the fields below on the grounds that we always initialize them just after the
186     * constructor -- an example of the kind of lying that our hypotheticaly bytecode rewriter would
187     * already have to deal with, thanks to DI frameworks that perform field and method injection,
188     * frameworks like Android that define post-construct hooks like Activity.onCreate, etc.
189     */
190
191    @CheckForNull ValueSetLink<K, V> predecessorInValueSet;
192    @CheckForNull ValueSetLink<K, V> successorInValueSet;
193
194    @CheckForNull ValueEntry<K, V> predecessorInMultimap;
195    @CheckForNull ValueEntry<K, V> successorInMultimap;
196
197    ValueEntry(
198        @ParametricNullness K key,
199        @ParametricNullness V value,
200        int smearedValueHash,
201        @CheckForNull ValueEntry<K, V> nextInValueBucket) {
202      super(key, value);
203      this.smearedValueHash = smearedValueHash;
204      this.nextInValueBucket = nextInValueBucket;
205    }
206
207    @SuppressWarnings("nullness") // see the comment on the class fields, especially about newHeader
208    static <K extends @Nullable Object, V extends @Nullable Object> ValueEntry<K, V> newHeader() {
209      return new ValueEntry<>(null, null, 0, null);
210    }
211
212    boolean matchesValue(@CheckForNull Object v, int smearedVHash) {
213      return smearedValueHash == smearedVHash && Objects.equal(getValue(), v);
214    }
215
216    @Override
217    public ValueSetLink<K, V> getPredecessorInValueSet() {
218      return requireNonNull(predecessorInValueSet); // see the comment on the class fields
219    }
220
221    @Override
222    public ValueSetLink<K, V> getSuccessorInValueSet() {
223      return requireNonNull(successorInValueSet); // see the comment on the class fields
224    }
225
226    @Override
227    public void setPredecessorInValueSet(ValueSetLink<K, V> entry) {
228      predecessorInValueSet = entry;
229    }
230
231    @Override
232    public void setSuccessorInValueSet(ValueSetLink<K, V> entry) {
233      successorInValueSet = entry;
234    }
235
236    public ValueEntry<K, V> getPredecessorInMultimap() {
237      return requireNonNull(predecessorInMultimap); // see the comment on the class fields
238    }
239
240    public ValueEntry<K, V> getSuccessorInMultimap() {
241      return requireNonNull(successorInMultimap); // see the comment on the class fields
242    }
243
244    public void setSuccessorInMultimap(ValueEntry<K, V> multimapSuccessor) {
245      this.successorInMultimap = multimapSuccessor;
246    }
247
248    public void setPredecessorInMultimap(ValueEntry<K, V> multimapPredecessor) {
249      this.predecessorInMultimap = multimapPredecessor;
250    }
251  }
252
253  private static final int DEFAULT_KEY_CAPACITY = 16;
254  private static final int DEFAULT_VALUE_SET_CAPACITY = 2;
255  @VisibleForTesting static final double VALUE_SET_LOAD_FACTOR = 1.0;
256
257  @VisibleForTesting transient int valueSetCapacity = DEFAULT_VALUE_SET_CAPACITY;
258  private transient ValueEntry<K, V> multimapHeaderEntry;
259
260  private LinkedHashMultimap(int keyCapacity, int valueSetCapacity) {
261    super(Platform.<K, Collection<V>>newLinkedHashMapWithExpectedSize(keyCapacity));
262    checkNonnegative(valueSetCapacity, "expectedValuesPerKey");
263
264    this.valueSetCapacity = valueSetCapacity;
265    this.multimapHeaderEntry = ValueEntry.newHeader();
266    succeedsInMultimap(multimapHeaderEntry, multimapHeaderEntry);
267  }
268
269  /**
270   * {@inheritDoc}
271   *
272   * <p>Creates an empty {@code LinkedHashSet} for a collection of values for one key.
273   *
274   * @return a new {@code LinkedHashSet} containing a collection of values for one key
275   */
276  @Override
277  Set<V> createCollection() {
278    return Platform.newLinkedHashSetWithExpectedSize(valueSetCapacity);
279  }
280
281  /**
282   * {@inheritDoc}
283   *
284   * <p>Creates a decorated insertion-ordered set that also keeps track of the order in which
285   * key-value pairs are added to the multimap.
286   *
287   * @param key key to associate with values in the collection
288   * @return a new decorated set containing a collection of values for one key
289   */
290  @Override
291  Collection<V> createCollection(@ParametricNullness K key) {
292    return new ValueSet(key, valueSetCapacity);
293  }
294
295  /**
296   * {@inheritDoc}
297   *
298   * <p>If {@code values} is not empty and the multimap already contains a mapping for {@code key},
299   * the {@code keySet()} ordering is unchanged. However, the provided values always come last in
300   * the {@link #entries()} and {@link #values()} iteration orderings.
301   */
302  @CanIgnoreReturnValue
303  @Override
304  public Set<V> replaceValues(@ParametricNullness K key, Iterable<? extends V> values) {
305    return super.replaceValues(key, values);
306  }
307
308  /**
309   * Returns a set of all key-value pairs. Changes to the returned set will update the underlying
310   * multimap, and vice versa. The entries set does not support the {@code add} or {@code addAll}
311   * operations.
312   *
313   * <p>The iterator generated by the returned set traverses the entries in the order they were
314   * added to the multimap.
315   *
316   * <p>Each entry is an immutable snapshot of a key-value mapping in the multimap, taken at the
317   * time the entry is returned by a method call to the collection or its iterator.
318   */
319  @Override
320  public Set<Entry<K, V>> entries() {
321    return super.entries();
322  }
323
324  /**
325   * Returns a view collection of all <i>distinct</i> keys contained in this multimap. Note that the
326   * key set contains a key if and only if this multimap maps that key to at least one value.
327   *
328   * <p>The iterator generated by the returned set traverses the keys in the order they were first
329   * added to the multimap.
330   *
331   * <p>Changes to the returned set will update the underlying multimap, and vice versa. However,
332   * <i>adding</i> to the returned set is not possible.
333   */
334  @Override
335  public Set<K> keySet() {
336    return super.keySet();
337  }
338
339  /**
340   * Returns a collection of all values in the multimap. Changes to the returned collection will
341   * update the underlying multimap, and vice versa.
342   *
343   * <p>The iterator generated by the returned collection traverses the values in the order they
344   * were added to the multimap.
345   */
346  @Override
347  public Collection<V> values() {
348    return super.values();
349  }
350
351  @VisibleForTesting
352  @WeakOuter
353  final class ValueSet extends Sets.ImprovedAbstractSet<V> implements ValueSetLink<K, V> {
354    /*
355     * We currently use a fixed load factor of 1.0, a bit higher than normal to reduce memory
356     * consumption.
357     */
358
359    @ParametricNullness private final K key;
360    @VisibleForTesting @Nullable ValueEntry<K, V>[] hashTable;
361    private int size = 0;
362    private int modCount = 0;
363
364    // We use the set object itself as the end of the linked list, avoiding an unnecessary
365    // entry object per key.
366    private ValueSetLink<K, V> firstEntry;
367    private ValueSetLink<K, V> lastEntry;
368
369    ValueSet(@ParametricNullness K key, int expectedValues) {
370      this.key = key;
371      this.firstEntry = this;
372      this.lastEntry = this;
373      // Round expected values up to a power of 2 to get the table size.
374      int tableSize = Hashing.closedTableSize(expectedValues, VALUE_SET_LOAD_FACTOR);
375
376      @SuppressWarnings({"rawtypes", "unchecked"})
377      @Nullable
378      ValueEntry<K, V>[] hashTable = new @Nullable ValueEntry[tableSize];
379      this.hashTable = hashTable;
380    }
381
382    private int mask() {
383      return hashTable.length - 1;
384    }
385
386    @Override
387    public ValueSetLink<K, V> getPredecessorInValueSet() {
388      return lastEntry;
389    }
390
391    @Override
392    public ValueSetLink<K, V> getSuccessorInValueSet() {
393      return firstEntry;
394    }
395
396    @Override
397    public void setPredecessorInValueSet(ValueSetLink<K, V> entry) {
398      lastEntry = entry;
399    }
400
401    @Override
402    public void setSuccessorInValueSet(ValueSetLink<K, V> entry) {
403      firstEntry = entry;
404    }
405
406    @Override
407    public Iterator<V> iterator() {
408      return new Iterator<V>() {
409        ValueSetLink<K, V> nextEntry = firstEntry;
410        @CheckForNull ValueEntry<K, V> toRemove;
411        int expectedModCount = modCount;
412
413        private void checkForComodification() {
414          if (modCount != expectedModCount) {
415            throw new ConcurrentModificationException();
416          }
417        }
418
419        @Override
420        public boolean hasNext() {
421          checkForComodification();
422          return nextEntry != ValueSet.this;
423        }
424
425        @Override
426        @ParametricNullness
427        public V next() {
428          if (!hasNext()) {
429            throw new NoSuchElementException();
430          }
431          ValueEntry<K, V> entry = (ValueEntry<K, V>) nextEntry;
432          V result = entry.getValue();
433          toRemove = entry;
434          nextEntry = entry.getSuccessorInValueSet();
435          return result;
436        }
437
438        @Override
439        public void remove() {
440          checkForComodification();
441          checkState(toRemove != null, "no calls to next() since the last call to remove()");
442          ValueSet.this.remove(toRemove.getValue());
443          expectedModCount = modCount;
444          toRemove = null;
445        }
446      };
447    }
448
449    @Override
450    public int size() {
451      return size;
452    }
453
454    @Override
455    public boolean contains(@CheckForNull Object o) {
456      int smearedHash = Hashing.smearedHash(o);
457      for (ValueEntry<K, V> entry = hashTable[smearedHash & mask()];
458          entry != null;
459          entry = entry.nextInValueBucket) {
460        if (entry.matchesValue(o, smearedHash)) {
461          return true;
462        }
463      }
464      return false;
465    }
466
467    @Override
468    public boolean add(@ParametricNullness V value) {
469      int smearedHash = Hashing.smearedHash(value);
470      int bucket = smearedHash & mask();
471      ValueEntry<K, V> rowHead = hashTable[bucket];
472      for (ValueEntry<K, V> entry = rowHead; entry != null; entry = entry.nextInValueBucket) {
473        if (entry.matchesValue(value, smearedHash)) {
474          return false;
475        }
476      }
477
478      ValueEntry<K, V> newEntry = new ValueEntry<>(key, value, smearedHash, rowHead);
479      succeedsInValueSet(lastEntry, newEntry);
480      succeedsInValueSet(newEntry, this);
481      succeedsInMultimap(multimapHeaderEntry.getPredecessorInMultimap(), newEntry);
482      succeedsInMultimap(newEntry, multimapHeaderEntry);
483      hashTable[bucket] = newEntry;
484      size++;
485      modCount++;
486      rehashIfNecessary();
487      return true;
488    }
489
490    private void rehashIfNecessary() {
491      if (Hashing.needsResizing(size, hashTable.length, VALUE_SET_LOAD_FACTOR)) {
492        @SuppressWarnings("unchecked")
493        ValueEntry<K, V>[] hashTable = new ValueEntry[this.hashTable.length * 2];
494        this.hashTable = hashTable;
495        int mask = hashTable.length - 1;
496        for (ValueSetLink<K, V> entry = firstEntry;
497            entry != this;
498            entry = entry.getSuccessorInValueSet()) {
499          ValueEntry<K, V> valueEntry = (ValueEntry<K, V>) entry;
500          int bucket = valueEntry.smearedValueHash & mask;
501          valueEntry.nextInValueBucket = hashTable[bucket];
502          hashTable[bucket] = valueEntry;
503        }
504      }
505    }
506
507    @CanIgnoreReturnValue
508    @Override
509    public boolean remove(@CheckForNull Object o) {
510      int smearedHash = Hashing.smearedHash(o);
511      int bucket = smearedHash & mask();
512      ValueEntry<K, V> prev = null;
513      for (ValueEntry<K, V> entry = hashTable[bucket];
514          entry != null;
515          prev = entry, entry = entry.nextInValueBucket) {
516        if (entry.matchesValue(o, smearedHash)) {
517          if (prev == null) {
518            // first entry in the bucket
519            hashTable[bucket] = entry.nextInValueBucket;
520          } else {
521            prev.nextInValueBucket = entry.nextInValueBucket;
522          }
523          deleteFromValueSet(entry);
524          deleteFromMultimap(entry);
525          size--;
526          modCount++;
527          return true;
528        }
529      }
530      return false;
531    }
532
533    @Override
534    public void clear() {
535      Arrays.fill(hashTable, null);
536      size = 0;
537      for (ValueSetLink<K, V> entry = firstEntry;
538          entry != this;
539          entry = entry.getSuccessorInValueSet()) {
540        ValueEntry<K, V> valueEntry = (ValueEntry<K, V>) entry;
541        deleteFromMultimap(valueEntry);
542      }
543      succeedsInValueSet(this, this);
544      modCount++;
545    }
546  }
547
548  @Override
549  Iterator<Entry<K, V>> entryIterator() {
550    return new Iterator<Entry<K, V>>() {
551      ValueEntry<K, V> nextEntry = multimapHeaderEntry.getSuccessorInMultimap();
552      @CheckForNull ValueEntry<K, V> toRemove;
553
554      @Override
555      public boolean hasNext() {
556        return nextEntry != multimapHeaderEntry;
557      }
558
559      @Override
560      public Entry<K, V> next() {
561        if (!hasNext()) {
562          throw new NoSuchElementException();
563        }
564        ValueEntry<K, V> result = nextEntry;
565        toRemove = result;
566        nextEntry = nextEntry.getSuccessorInMultimap();
567        return result;
568      }
569
570      @Override
571      public void remove() {
572        checkState(toRemove != null, "no calls to next() since the last call to remove()");
573        LinkedHashMultimap.this.remove(toRemove.getKey(), toRemove.getValue());
574        toRemove = null;
575      }
576    };
577  }
578
579  @Override
580  Iterator<V> valueIterator() {
581    return Maps.valueIterator(entryIterator());
582  }
583
584  @Override
585  public void clear() {
586    super.clear();
587    succeedsInMultimap(multimapHeaderEntry, multimapHeaderEntry);
588  }
589
590  /**
591   * @serialData the expected values per key, the number of distinct keys, the number of entries,
592   *     and the entries in order
593   */
594  @GwtIncompatible // java.io.ObjectOutputStream
595  private void writeObject(ObjectOutputStream stream) throws IOException {
596    stream.defaultWriteObject();
597    stream.writeInt(keySet().size());
598    for (K key : keySet()) {
599      stream.writeObject(key);
600    }
601    stream.writeInt(size());
602    for (Entry<K, V> entry : entries()) {
603      stream.writeObject(entry.getKey());
604      stream.writeObject(entry.getValue());
605    }
606  }
607
608  @GwtIncompatible // java.io.ObjectInputStream
609  private void readObject(ObjectInputStream stream) throws IOException, ClassNotFoundException {
610    stream.defaultReadObject();
611    multimapHeaderEntry = ValueEntry.newHeader();
612    succeedsInMultimap(multimapHeaderEntry, multimapHeaderEntry);
613    valueSetCapacity = DEFAULT_VALUE_SET_CAPACITY;
614    int distinctKeys = stream.readInt();
615    Map<K, Collection<V>> map = Platform.newLinkedHashMapWithExpectedSize(12);
616    for (int i = 0; i < distinctKeys; i++) {
617      @SuppressWarnings("unchecked")
618      K key = (K) stream.readObject();
619      map.put(key, createCollection(key));
620    }
621    int entries = stream.readInt();
622    for (int i = 0; i < entries; i++) {
623      @SuppressWarnings("unchecked")
624      K key = (K) stream.readObject();
625      @SuppressWarnings("unchecked")
626      V value = (V) stream.readObject();
627      /*
628       * requireNonNull is safe for a properly serialized multimap: We've already inserted a
629       * collection for each key that we expect.
630       */
631      requireNonNull(map.get(key)).add(value);
632    }
633    setMap(map);
634  }
635
636  @GwtIncompatible // java serialization not supported
637  private static final long serialVersionUID = 1;
638}