001/*
002 * Copyright (C) 2007 The Guava Authors
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
005 * in compliance with the License. You may obtain a copy of the License at
006 *
007 * http://www.apache.org/licenses/LICENSE-2.0
008 *
009 * Unless required by applicable law or agreed to in writing, software distributed under the License
010 * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
011 * or implied. See the License for the specific language governing permissions and limitations under
012 * the License.
013 */
014
015package com.google.common.collect;
016
017import static com.google.common.base.Preconditions.checkArgument;
018import static com.google.common.base.Preconditions.checkNotNull;
019import static com.google.common.collect.CollectPreconditions.checkNonnegative;
020import static com.google.common.collect.CollectPreconditions.checkRemove;
021import static com.google.common.collect.Hashing.smearedHash;
022
023import com.google.common.annotations.GwtCompatible;
024import com.google.common.annotations.GwtIncompatible;
025import com.google.common.base.Objects;
026import com.google.common.collect.Maps.IteratorBasedAbstractMap;
027import com.google.errorprone.annotations.CanIgnoreReturnValue;
028import com.google.errorprone.annotations.concurrent.LazyInit;
029import com.google.j2objc.annotations.RetainedWith;
030import com.google.j2objc.annotations.WeakOuter;
031import java.io.IOException;
032import java.io.ObjectInputStream;
033import java.io.ObjectOutputStream;
034import java.io.Serializable;
035import java.util.Arrays;
036import java.util.ConcurrentModificationException;
037import java.util.Iterator;
038import java.util.Map;
039import java.util.NoSuchElementException;
040import java.util.Set;
041import java.util.function.BiConsumer;
042import java.util.function.BiFunction;
043import org.checkerframework.checker.nullness.qual.Nullable;
044
045/**
046 * A {@link BiMap} backed by two hash tables. This implementation allows null keys and values. A
047 * {@code HashBiMap} and its inverse are both serializable.
048 *
049 * <p>This implementation guarantees insertion-based iteration order of its keys.
050 *
051 * <p>See the Guava User Guide article on <a href=
052 * "https://github.com/google/guava/wiki/NewCollectionTypesExplained#bimap"> {@code BiMap} </a>.
053 *
054 * @author Louis Wasserman
055 * @author Mike Bostock
056 * @since 2.0
057 */
058@GwtCompatible(emulated = true)
059public final class HashBiMap<K, V> extends IteratorBasedAbstractMap<K, V>
060    implements BiMap<K, V>, Serializable {
061
062  /** Returns a new, empty {@code HashBiMap} with the default initial capacity (16). */
063  public static <K, V> HashBiMap<K, V> create() {
064    return create(16);
065  }
066
067  /**
068   * Constructs a new, empty bimap with the specified expected size.
069   *
070   * @param expectedSize the expected number of entries
071   * @throws IllegalArgumentException if the specified expected size is negative
072   */
073  public static <K, V> HashBiMap<K, V> create(int expectedSize) {
074    return new HashBiMap<>(expectedSize);
075  }
076
077  /**
078   * Constructs a new bimap containing initial values from {@code map}. The bimap is created with an
079   * initial capacity sufficient to hold the mappings in the specified map.
080   */
081  public static <K, V> HashBiMap<K, V> create(Map<? extends K, ? extends V> map) {
082    HashBiMap<K, V> bimap = create(map.size());
083    bimap.putAll(map);
084    return bimap;
085  }
086
087  private static final class BiEntry<K, V> extends ImmutableEntry<K, V> {
088    final int keyHash;
089    final int valueHash;
090
091    @Nullable BiEntry<K, V> nextInKToVBucket;
092    @Nullable BiEntry<K, V> nextInVToKBucket;
093
094    @Nullable BiEntry<K, V> nextInKeyInsertionOrder;
095    @Nullable BiEntry<K, V> prevInKeyInsertionOrder;
096
097    BiEntry(K key, int keyHash, V value, int valueHash) {
098      super(key, value);
099      this.keyHash = keyHash;
100      this.valueHash = valueHash;
101    }
102  }
103
104  private static final double LOAD_FACTOR = 1.0;
105
106  private transient BiEntry<K, V>[] hashTableKToV;
107  private transient BiEntry<K, V>[] hashTableVToK;
108  private transient @Nullable BiEntry<K, V> firstInKeyInsertionOrder;
109  private transient @Nullable BiEntry<K, V> lastInKeyInsertionOrder;
110  private transient int size;
111  private transient int mask;
112  private transient int modCount;
113
114  private HashBiMap(int expectedSize) {
115    init(expectedSize);
116  }
117
118  private void init(int expectedSize) {
119    checkNonnegative(expectedSize, "expectedSize");
120    int tableSize = Hashing.closedTableSize(expectedSize, LOAD_FACTOR);
121    this.hashTableKToV = createTable(tableSize);
122    this.hashTableVToK = createTable(tableSize);
123    this.firstInKeyInsertionOrder = null;
124    this.lastInKeyInsertionOrder = null;
125    this.size = 0;
126    this.mask = tableSize - 1;
127    this.modCount = 0;
128  }
129
130  /**
131   * Finds and removes {@code entry} from the bucket linked lists in both the key-to-value direction
132   * and the value-to-key direction.
133   */
134  private void delete(BiEntry<K, V> entry) {
135    int keyBucket = entry.keyHash & mask;
136    BiEntry<K, V> prevBucketEntry = null;
137    for (BiEntry<K, V> bucketEntry = hashTableKToV[keyBucket];
138        true;
139        bucketEntry = bucketEntry.nextInKToVBucket) {
140      if (bucketEntry == entry) {
141        if (prevBucketEntry == null) {
142          hashTableKToV[keyBucket] = entry.nextInKToVBucket;
143        } else {
144          prevBucketEntry.nextInKToVBucket = entry.nextInKToVBucket;
145        }
146        break;
147      }
148      prevBucketEntry = bucketEntry;
149    }
150
151    int valueBucket = entry.valueHash & mask;
152    prevBucketEntry = null;
153    for (BiEntry<K, V> bucketEntry = hashTableVToK[valueBucket];
154        true;
155        bucketEntry = bucketEntry.nextInVToKBucket) {
156      if (bucketEntry == entry) {
157        if (prevBucketEntry == null) {
158          hashTableVToK[valueBucket] = entry.nextInVToKBucket;
159        } else {
160          prevBucketEntry.nextInVToKBucket = entry.nextInVToKBucket;
161        }
162        break;
163      }
164      prevBucketEntry = bucketEntry;
165    }
166
167    if (entry.prevInKeyInsertionOrder == null) {
168      firstInKeyInsertionOrder = entry.nextInKeyInsertionOrder;
169    } else {
170      entry.prevInKeyInsertionOrder.nextInKeyInsertionOrder = entry.nextInKeyInsertionOrder;
171    }
172
173    if (entry.nextInKeyInsertionOrder == null) {
174      lastInKeyInsertionOrder = entry.prevInKeyInsertionOrder;
175    } else {
176      entry.nextInKeyInsertionOrder.prevInKeyInsertionOrder = entry.prevInKeyInsertionOrder;
177    }
178
179    size--;
180    modCount++;
181  }
182
183  private void insert(BiEntry<K, V> entry, @Nullable BiEntry<K, V> oldEntryForKey) {
184    int keyBucket = entry.keyHash & mask;
185    entry.nextInKToVBucket = hashTableKToV[keyBucket];
186    hashTableKToV[keyBucket] = entry;
187
188    int valueBucket = entry.valueHash & mask;
189    entry.nextInVToKBucket = hashTableVToK[valueBucket];
190    hashTableVToK[valueBucket] = entry;
191
192    if (oldEntryForKey == null) {
193      entry.prevInKeyInsertionOrder = lastInKeyInsertionOrder;
194      entry.nextInKeyInsertionOrder = null;
195      if (lastInKeyInsertionOrder == null) {
196        firstInKeyInsertionOrder = entry;
197      } else {
198        lastInKeyInsertionOrder.nextInKeyInsertionOrder = entry;
199      }
200      lastInKeyInsertionOrder = entry;
201    } else {
202      entry.prevInKeyInsertionOrder = oldEntryForKey.prevInKeyInsertionOrder;
203      if (entry.prevInKeyInsertionOrder == null) {
204        firstInKeyInsertionOrder = entry;
205      } else {
206        entry.prevInKeyInsertionOrder.nextInKeyInsertionOrder = entry;
207      }
208      entry.nextInKeyInsertionOrder = oldEntryForKey.nextInKeyInsertionOrder;
209      if (entry.nextInKeyInsertionOrder == null) {
210        lastInKeyInsertionOrder = entry;
211      } else {
212        entry.nextInKeyInsertionOrder.prevInKeyInsertionOrder = entry;
213      }
214    }
215
216    size++;
217    modCount++;
218  }
219
220  private BiEntry<K, V> seekByKey(@Nullable Object key, int keyHash) {
221    for (BiEntry<K, V> entry = hashTableKToV[keyHash & mask];
222        entry != null;
223        entry = entry.nextInKToVBucket) {
224      if (keyHash == entry.keyHash && Objects.equal(key, entry.key)) {
225        return entry;
226      }
227    }
228    return null;
229  }
230
231  private BiEntry<K, V> seekByValue(@Nullable Object value, int valueHash) {
232    for (BiEntry<K, V> entry = hashTableVToK[valueHash & mask];
233        entry != null;
234        entry = entry.nextInVToKBucket) {
235      if (valueHash == entry.valueHash && Objects.equal(value, entry.value)) {
236        return entry;
237      }
238    }
239    return null;
240  }
241
242  @Override
243  public boolean containsKey(@Nullable Object key) {
244    return seekByKey(key, smearedHash(key)) != null;
245  }
246
247  /**
248   * Returns {@code true} if this BiMap contains an entry whose value is equal to {@code value} (or,
249   * equivalently, if this inverse view contains a key that is equal to {@code value}).
250   *
251   * <p>Due to the property that values in a BiMap are unique, this will tend to execute in
252   * faster-than-linear time.
253   *
254   * @param value the object to search for in the values of this BiMap
255   * @return true if a mapping exists from a key to the specified value
256   */
257  @Override
258  public boolean containsValue(@Nullable Object value) {
259    return seekByValue(value, smearedHash(value)) != null;
260  }
261
262  @Override
263  public @Nullable V get(@Nullable Object key) {
264    return Maps.valueOrNull(seekByKey(key, smearedHash(key)));
265  }
266
267  @CanIgnoreReturnValue
268  @Override
269  public V put(@Nullable K key, @Nullable V value) {
270    return put(key, value, false);
271  }
272
273  private V put(@Nullable K key, @Nullable V value, boolean force) {
274    int keyHash = smearedHash(key);
275    int valueHash = smearedHash(value);
276
277    BiEntry<K, V> oldEntryForKey = seekByKey(key, keyHash);
278    if (oldEntryForKey != null
279        && valueHash == oldEntryForKey.valueHash
280        && Objects.equal(value, oldEntryForKey.value)) {
281      return value;
282    }
283
284    BiEntry<K, V> oldEntryForValue = seekByValue(value, valueHash);
285    if (oldEntryForValue != null) {
286      if (force) {
287        delete(oldEntryForValue);
288      } else {
289        throw new IllegalArgumentException("value already present: " + value);
290      }
291    }
292
293    BiEntry<K, V> newEntry = new BiEntry<>(key, keyHash, value, valueHash);
294    if (oldEntryForKey != null) {
295      delete(oldEntryForKey);
296      insert(newEntry, oldEntryForKey);
297      oldEntryForKey.prevInKeyInsertionOrder = null;
298      oldEntryForKey.nextInKeyInsertionOrder = null;
299      return oldEntryForKey.value;
300    } else {
301      insert(newEntry, null);
302      rehashIfNecessary();
303      return null;
304    }
305  }
306
307  @CanIgnoreReturnValue
308  @Override
309  public @Nullable V forcePut(@Nullable K key, @Nullable V value) {
310    return put(key, value, true);
311  }
312
313  private @Nullable K putInverse(@Nullable V value, @Nullable K key, boolean force) {
314    int valueHash = smearedHash(value);
315    int keyHash = smearedHash(key);
316
317    BiEntry<K, V> oldEntryForValue = seekByValue(value, valueHash);
318    BiEntry<K, V> oldEntryForKey = seekByKey(key, keyHash);
319    if (oldEntryForValue != null
320        && keyHash == oldEntryForValue.keyHash
321        && Objects.equal(key, oldEntryForValue.key)) {
322      return key;
323    } else if (oldEntryForKey != null && !force) {
324      throw new IllegalArgumentException("key already present: " + key);
325    }
326
327    /*
328     * The ordering here is important: if we deleted the key entry and then the value entry,
329     * the key entry's prev or next pointer might point to the dead value entry, and when we
330     * put the new entry in the key entry's position in iteration order, it might invalidate
331     * the linked list.
332     */
333
334    if (oldEntryForValue != null) {
335      delete(oldEntryForValue);
336    }
337
338    if (oldEntryForKey != null) {
339      delete(oldEntryForKey);
340    }
341
342    BiEntry<K, V> newEntry = new BiEntry<>(key, keyHash, value, valueHash);
343    insert(newEntry, oldEntryForKey);
344
345    if (oldEntryForKey != null) {
346      oldEntryForKey.prevInKeyInsertionOrder = null;
347      oldEntryForKey.nextInKeyInsertionOrder = null;
348    }
349    if (oldEntryForValue != null) {
350      oldEntryForValue.prevInKeyInsertionOrder = null;
351      oldEntryForValue.nextInKeyInsertionOrder = null;
352    }
353    rehashIfNecessary();
354    return Maps.keyOrNull(oldEntryForValue);
355  }
356
357  private void rehashIfNecessary() {
358    BiEntry<K, V>[] oldKToV = hashTableKToV;
359    if (Hashing.needsResizing(size, oldKToV.length, LOAD_FACTOR)) {
360      int newTableSize = oldKToV.length * 2;
361
362      this.hashTableKToV = createTable(newTableSize);
363      this.hashTableVToK = createTable(newTableSize);
364      this.mask = newTableSize - 1;
365      this.size = 0;
366
367      for (BiEntry<K, V> entry = firstInKeyInsertionOrder;
368          entry != null;
369          entry = entry.nextInKeyInsertionOrder) {
370        insert(entry, entry);
371      }
372      this.modCount++;
373    }
374  }
375
376  @SuppressWarnings("unchecked")
377  private BiEntry<K, V>[] createTable(int length) {
378    return new BiEntry[length];
379  }
380
381  @CanIgnoreReturnValue
382  @Override
383  public @Nullable V remove(@Nullable Object key) {
384    BiEntry<K, V> entry = seekByKey(key, smearedHash(key));
385    if (entry == null) {
386      return null;
387    } else {
388      delete(entry);
389      entry.prevInKeyInsertionOrder = null;
390      entry.nextInKeyInsertionOrder = null;
391      return entry.value;
392    }
393  }
394
395  @Override
396  public void clear() {
397    size = 0;
398    Arrays.fill(hashTableKToV, null);
399    Arrays.fill(hashTableVToK, null);
400    firstInKeyInsertionOrder = null;
401    lastInKeyInsertionOrder = null;
402    modCount++;
403  }
404
405  @Override
406  public int size() {
407    return size;
408  }
409
410  abstract class Itr<T> implements Iterator<T> {
411    BiEntry<K, V> next = firstInKeyInsertionOrder;
412    BiEntry<K, V> toRemove = null;
413    int expectedModCount = modCount;
414    int remaining = size();
415
416    @Override
417    public boolean hasNext() {
418      if (modCount != expectedModCount) {
419        throw new ConcurrentModificationException();
420      }
421      return next != null && remaining > 0;
422    }
423
424    @Override
425    public T next() {
426      if (!hasNext()) {
427        throw new NoSuchElementException();
428      }
429
430      BiEntry<K, V> entry = next;
431      next = entry.nextInKeyInsertionOrder;
432      toRemove = entry;
433      remaining--;
434      return output(entry);
435    }
436
437    @Override
438    public void remove() {
439      if (modCount != expectedModCount) {
440        throw new ConcurrentModificationException();
441      }
442      checkRemove(toRemove != null);
443      delete(toRemove);
444      expectedModCount = modCount;
445      toRemove = null;
446    }
447
448    abstract T output(BiEntry<K, V> entry);
449  }
450
451  @Override
452  public Set<K> keySet() {
453    return new KeySet();
454  }
455
456  @WeakOuter
457  private final class KeySet extends Maps.KeySet<K, V> {
458    KeySet() {
459      super(HashBiMap.this);
460    }
461
462    @Override
463    public Iterator<K> iterator() {
464      return new Itr<K>() {
465        @Override
466        K output(BiEntry<K, V> entry) {
467          return entry.key;
468        }
469      };
470    }
471
472    @Override
473    public boolean remove(@Nullable Object o) {
474      BiEntry<K, V> entry = seekByKey(o, smearedHash(o));
475      if (entry == null) {
476        return false;
477      } else {
478        delete(entry);
479        entry.prevInKeyInsertionOrder = null;
480        entry.nextInKeyInsertionOrder = null;
481        return true;
482      }
483    }
484  }
485
486  @Override
487  public Set<V> values() {
488    return inverse().keySet();
489  }
490
491  @Override
492  Iterator<Entry<K, V>> entryIterator() {
493    return new Itr<Entry<K, V>>() {
494      @Override
495      Entry<K, V> output(BiEntry<K, V> entry) {
496        return new MapEntry(entry);
497      }
498
499      class MapEntry extends AbstractMapEntry<K, V> {
500        BiEntry<K, V> delegate;
501
502        MapEntry(BiEntry<K, V> entry) {
503          this.delegate = entry;
504        }
505
506        @Override
507        public K getKey() {
508          return delegate.key;
509        }
510
511        @Override
512        public V getValue() {
513          return delegate.value;
514        }
515
516        @Override
517        public V setValue(V value) {
518          V oldValue = delegate.value;
519          int valueHash = smearedHash(value);
520          if (valueHash == delegate.valueHash && Objects.equal(value, oldValue)) {
521            return value;
522          }
523          checkArgument(seekByValue(value, valueHash) == null, "value already present: %s", value);
524          delete(delegate);
525          BiEntry<K, V> newEntry = new BiEntry<>(delegate.key, delegate.keyHash, value, valueHash);
526          insert(newEntry, delegate);
527          delegate.prevInKeyInsertionOrder = null;
528          delegate.nextInKeyInsertionOrder = null;
529          expectedModCount = modCount;
530          if (toRemove == delegate) {
531            toRemove = newEntry;
532          }
533          delegate = newEntry;
534          return oldValue;
535        }
536      }
537    };
538  }
539
540  @Override
541  public void forEach(BiConsumer<? super K, ? super V> action) {
542    checkNotNull(action);
543    for (BiEntry<K, V> entry = firstInKeyInsertionOrder;
544        entry != null;
545        entry = entry.nextInKeyInsertionOrder) {
546      action.accept(entry.key, entry.value);
547    }
548  }
549
550  @Override
551  public void replaceAll(BiFunction<? super K, ? super V, ? extends V> function) {
552    checkNotNull(function);
553    BiEntry<K, V> oldFirst = firstInKeyInsertionOrder;
554    clear();
555    for (BiEntry<K, V> entry = oldFirst; entry != null; entry = entry.nextInKeyInsertionOrder) {
556      put(entry.key, function.apply(entry.key, entry.value));
557    }
558  }
559
560  @LazyInit @RetainedWith private transient @Nullable BiMap<V, K> inverse;
561
562  @Override
563  public BiMap<V, K> inverse() {
564    BiMap<V, K> result = inverse;
565    return (result == null) ? inverse = new Inverse() : result;
566  }
567
568  private final class Inverse extends IteratorBasedAbstractMap<V, K>
569      implements BiMap<V, K>, Serializable {
570    BiMap<K, V> forward() {
571      return HashBiMap.this;
572    }
573
574    @Override
575    public int size() {
576      return size;
577    }
578
579    @Override
580    public void clear() {
581      forward().clear();
582    }
583
584    @Override
585    public boolean containsKey(@Nullable Object value) {
586      return forward().containsValue(value);
587    }
588
589    @Override
590    public K get(@Nullable Object value) {
591      return Maps.keyOrNull(seekByValue(value, smearedHash(value)));
592    }
593
594    @CanIgnoreReturnValue
595    @Override
596    public @Nullable K put(@Nullable V value, @Nullable K key) {
597      return putInverse(value, key, false);
598    }
599
600    @Override
601    public @Nullable K forcePut(@Nullable V value, @Nullable K key) {
602      return putInverse(value, key, true);
603    }
604
605    @Override
606    public @Nullable K remove(@Nullable Object value) {
607      BiEntry<K, V> entry = seekByValue(value, smearedHash(value));
608      if (entry == null) {
609        return null;
610      } else {
611        delete(entry);
612        entry.prevInKeyInsertionOrder = null;
613        entry.nextInKeyInsertionOrder = null;
614        return entry.key;
615      }
616    }
617
618    @Override
619    public BiMap<K, V> inverse() {
620      return forward();
621    }
622
623    @Override
624    public Set<V> keySet() {
625      return new InverseKeySet();
626    }
627
628    @WeakOuter
629    private final class InverseKeySet extends Maps.KeySet<V, K> {
630      InverseKeySet() {
631        super(Inverse.this);
632      }
633
634      @Override
635      public boolean remove(@Nullable Object o) {
636        BiEntry<K, V> entry = seekByValue(o, smearedHash(o));
637        if (entry == null) {
638          return false;
639        } else {
640          delete(entry);
641          return true;
642        }
643      }
644
645      @Override
646      public Iterator<V> iterator() {
647        return new Itr<V>() {
648          @Override
649          V output(BiEntry<K, V> entry) {
650            return entry.value;
651          }
652        };
653      }
654    }
655
656    @Override
657    public Set<K> values() {
658      return forward().keySet();
659    }
660
661    @Override
662    Iterator<Entry<V, K>> entryIterator() {
663      return new Itr<Entry<V, K>>() {
664        @Override
665        Entry<V, K> output(BiEntry<K, V> entry) {
666          return new InverseEntry(entry);
667        }
668
669        class InverseEntry extends AbstractMapEntry<V, K> {
670          BiEntry<K, V> delegate;
671
672          InverseEntry(BiEntry<K, V> entry) {
673            this.delegate = entry;
674          }
675
676          @Override
677          public V getKey() {
678            return delegate.value;
679          }
680
681          @Override
682          public K getValue() {
683            return delegate.key;
684          }
685
686          @Override
687          public K setValue(K key) {
688            K oldKey = delegate.key;
689            int keyHash = smearedHash(key);
690            if (keyHash == delegate.keyHash && Objects.equal(key, oldKey)) {
691              return key;
692            }
693            checkArgument(seekByKey(key, keyHash) == null, "value already present: %s", key);
694            delete(delegate);
695            BiEntry<K, V> newEntry =
696                new BiEntry<>(key, keyHash, delegate.value, delegate.valueHash);
697            delegate = newEntry;
698            insert(newEntry, null);
699            expectedModCount = modCount;
700            return oldKey;
701          }
702        }
703      };
704    }
705
706    @Override
707    public void forEach(BiConsumer<? super V, ? super K> action) {
708      checkNotNull(action);
709      HashBiMap.this.forEach((k, v) -> action.accept(v, k));
710    }
711
712    @Override
713    public void replaceAll(BiFunction<? super V, ? super K, ? extends K> function) {
714      checkNotNull(function);
715      BiEntry<K, V> oldFirst = firstInKeyInsertionOrder;
716      clear();
717      for (BiEntry<K, V> entry = oldFirst; entry != null; entry = entry.nextInKeyInsertionOrder) {
718        put(entry.value, function.apply(entry.value, entry.key));
719      }
720    }
721
722    Object writeReplace() {
723      return new InverseSerializedForm<>(HashBiMap.this);
724    }
725  }
726
727  private static final class InverseSerializedForm<K, V> implements Serializable {
728    private final HashBiMap<K, V> bimap;
729
730    InverseSerializedForm(HashBiMap<K, V> bimap) {
731      this.bimap = bimap;
732    }
733
734    Object readResolve() {
735      return bimap.inverse();
736    }
737  }
738
739  /**
740   * @serialData the number of entries, first key, first value, second key, second value, and so on.
741   */
742  @GwtIncompatible // java.io.ObjectOutputStream
743  private void writeObject(ObjectOutputStream stream) throws IOException {
744    stream.defaultWriteObject();
745    Serialization.writeMap(this, stream);
746  }
747
748  @GwtIncompatible // java.io.ObjectInputStream
749  private void readObject(ObjectInputStream stream) throws IOException, ClassNotFoundException {
750    stream.defaultReadObject();
751    int size = Serialization.readCount(stream);
752    init(16); // resist hostile attempts to allocate gratuitous heap
753    Serialization.populateMap(this, stream, size);
754  }
755
756  @GwtIncompatible // Not needed in emulated source
757  private static final long serialVersionUID = 0;
758}