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