001/*
002 * Copyright (C) 2008 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.checkNotNull;
020import static com.google.common.base.Preconditions.checkState;
021import static com.google.common.collect.CollectPreconditions.checkEntryNotNull;
022import static com.google.common.collect.CollectPreconditions.checkNonnegative;
023
024import com.google.common.annotations.Beta;
025import com.google.common.annotations.GwtCompatible;
026import com.google.errorprone.annotations.CanIgnoreReturnValue;
027import com.google.errorprone.annotations.concurrent.LazyInit;
028import com.google.j2objc.annotations.WeakOuter;
029import java.io.Serializable;
030import java.util.AbstractMap;
031import java.util.Arrays;
032import java.util.Collection;
033import java.util.Collections;
034import java.util.Comparator;
035import java.util.Iterator;
036import java.util.Map;
037import java.util.SortedMap;
038import javax.annotation.Nullable;
039
040/**
041 * A {@link Map} whose contents will never change, with many other important properties detailed at
042 * {@link ImmutableCollection}.
043 *
044 * <p>See the Guava User Guide article on <a href=
045 * "https://github.com/google/guava/wiki/ImmutableCollectionsExplained"> immutable collections</a>.
046 *
047 * @author Jesse Wilson
048 * @author Kevin Bourrillion
049 * @since 2.0
050 */
051@GwtCompatible(serializable = true, emulated = true)
052@SuppressWarnings("serial") // we're overriding default serialization
053public abstract class ImmutableMap<K, V> implements Map<K, V>, Serializable {
054
055  /**
056   * Returns the empty map. This map behaves and performs comparably to {@link
057   * Collections#emptyMap}, and is preferable mainly for consistency and maintainability of your
058   * code.
059   */
060  @SuppressWarnings("unchecked")
061  public static <K, V> ImmutableMap<K, V> of() {
062    return (ImmutableMap<K, V>) RegularImmutableMap.EMPTY;
063  }
064
065  /**
066   * Returns an immutable map containing a single entry. This map behaves and performs comparably to
067   * {@link Collections#singletonMap} but will not accept a null key or value. It is preferable
068   * mainly for consistency and maintainability of your code.
069   */
070  public static <K, V> ImmutableMap<K, V> of(K k1, V v1) {
071    checkEntryNotNull(k1, v1);
072    return RegularImmutableMap.create(1, new Object[] {k1, v1});
073  }
074
075  /**
076   * Returns an immutable map containing the given entries, in order.
077   *
078   * @throws IllegalArgumentException if duplicate keys are provided
079   */
080  public static <K, V> ImmutableMap<K, V> of(K k1, V v1, K k2, V v2) {
081    checkEntryNotNull(k1, v1);
082    checkEntryNotNull(k2, v2);
083    return RegularImmutableMap.create(2, new Object[] {k1, v1, k2, v2});
084  }
085
086  /**
087   * Returns an immutable map containing the given entries, in order.
088   *
089   * @throws IllegalArgumentException if duplicate keys are provided
090   */
091  public static <K, V> ImmutableMap<K, V> of(K k1, V v1, K k2, V v2, K k3, V v3) {
092    checkEntryNotNull(k1, v1);
093    checkEntryNotNull(k2, v2);
094    checkEntryNotNull(k3, v3);
095    return RegularImmutableMap.create(3, new Object[] {k1, v1, k2, v2, k3, v3});
096  }
097
098  /**
099   * Returns an immutable map containing the given entries, in order.
100   *
101   * @throws IllegalArgumentException if duplicate keys are provided
102   */
103  public static <K, V> ImmutableMap<K, V> of(K k1, V v1, K k2, V v2, K k3, V v3, K k4, V v4) {
104    checkEntryNotNull(k1, v1);
105    checkEntryNotNull(k2, v2);
106    checkEntryNotNull(k3, v3);
107    checkEntryNotNull(k4, v4);
108    return RegularImmutableMap.create(4, new Object[] {k1, v1, k2, v2, k3, v3, k4, v4});
109  }
110
111  /**
112   * Returns an immutable map containing the given entries, in order.
113   *
114   * @throws IllegalArgumentException if duplicate keys are provided
115   */
116  public static <K, V> ImmutableMap<K, V> of(
117      K k1, V v1, K k2, V v2, K k3, V v3, K k4, V v4, K k5, V v5) {
118    checkEntryNotNull(k1, v1);
119    checkEntryNotNull(k2, v2);
120    checkEntryNotNull(k3, v3);
121    checkEntryNotNull(k4, v4);
122    checkEntryNotNull(k5, v5);
123    return RegularImmutableMap.create(5, new Object[] {k1, v1, k2, v2, k3, v3, k4, v4, k5, v5});
124  }
125
126  // looking for of() with > 5 entries? Use the builder instead.
127
128  /**
129   * Verifies that {@code key} and {@code value} are non-null, and returns a new
130   * immutable entry with those values.
131   * <p>A call to {@link Map.Entry#setValue} on the returned entry will always
132   * throw {@link UnsupportedOperationException}.
133   */
134  static <K, V> Entry<K, V> entryOf(K key, V value) {
135    checkEntryNotNull(key, value);
136    return new AbstractMap.SimpleImmutableEntry<>(key, value);
137  }
138
139  /**
140   * Returns a new builder. The generated builder is equivalent to the builder created by the {@link
141   * Builder} constructor.
142   */
143  public static <K, V> Builder<K, V> builder() {
144    return new Builder<>();
145  }
146
147  /**
148   * Returns a new builder, expecting the specified number of entries to be added.
149   *
150   * <p>If {@code expectedSize} is exactly the number of entries added to the builder before {@link
151   * Builder#build} is called, the builder is likely to perform better than an unsized {@link
152   * #builder()} would have.
153   *
154   * <p>It is not specified if any performance benefits apply if {@code expectedSize} is close to,
155   * but not exactly, the number of entries added to the builder.
156   * 
157   * @since 23.1
158   */
159  @Beta
160  public static <K, V> Builder<K, V> builderWithExpectedSize(int expectedSize) {
161    checkNonnegative(expectedSize, "expectedSize");
162    return new Builder<>(expectedSize);
163  }
164
165  static void checkNoConflict(
166      boolean safe, String conflictDescription, Entry<?, ?> entry1, Entry<?, ?> entry2) {
167    if (!safe) {
168      throw new IllegalArgumentException(
169          "Multiple entries with same " + conflictDescription + ": " + entry1 + " and " + entry2);
170    }
171  }
172
173  /**
174   * A builder for creating immutable map instances, especially {@code public
175   * static final} maps ("constant maps"). Example: <pre>   {@code
176   *
177   * static final ImmutableMap<String, Integer> WORD_TO_INT =
178   *     new ImmutableMap.Builder<String, Integer>()
179   *         .put("one", 1)
180   *         .put("two", 2)
181   *         .put("three", 3)
182   *         .build();
183   * }</pre>
184   *
185   * <p>For <i>small</i> immutable maps, the {@code ImmutableMap.of()} methods are even more
186   * convenient.
187   *
188   * <p>By default, a {@code Builder} will generate maps that iterate over entries in the order
189   * they were inserted into the builder, equivalently to {@code LinkedHashMap}.  For example, in
190   * the above example, {@code WORD_TO_INT.entrySet()} is guaranteed to iterate over the entries in
191   * the order {@code "one"=1, "two"=2, "three"=3}, and {@code keySet()} and {@code values()}
192   * respect the same order.   If you want a different order, consider using 
193   * {@link ImmutableSortedMap} to sort by keys, or call {@link #orderEntriesByValue(Comparator)}, 
194   * which changes this builder to sort entries by value.
195   *
196   * <p>Builder instances can be reused - it is safe to call {@link #build}
197   * multiple times to build multiple maps in series. Each map is a superset of
198   * the maps created before it.
199   *
200   * @since 2.0
201   */
202  public static class Builder<K, V> {
203    Comparator<? super V> valueComparator;
204    Object[] alternatingKeysAndValues;
205    int size;
206    boolean entriesUsed;
207
208    /**
209     * Creates a new builder. The returned builder is equivalent to the builder generated by {@link
210     * ImmutableMap#builder}.
211     */
212    public Builder() {
213      this(ImmutableCollection.Builder.DEFAULT_INITIAL_CAPACITY);
214    }
215
216    @SuppressWarnings("unchecked")
217    Builder(int initialCapacity) {
218      this.alternatingKeysAndValues = new Object[2 * initialCapacity];
219      this.size = 0;
220      this.entriesUsed = false;
221    }
222
223    private void ensureCapacity(int minCapacity) {
224      if (minCapacity * 2 > alternatingKeysAndValues.length) {
225        alternatingKeysAndValues =
226            Arrays.copyOf(
227                alternatingKeysAndValues,
228                ImmutableCollection.Builder.expandedCapacity(
229                    alternatingKeysAndValues.length, minCapacity * 2));
230        entriesUsed = false;
231      }
232    }
233
234    /**
235     * Associates {@code key} with {@code value} in the built map. Duplicate keys are not allowed,
236     * and will cause {@link #build} to fail.
237     */
238    @CanIgnoreReturnValue
239    public Builder<K, V> put(K key, V value) {
240      ensureCapacity(size + 1);
241      checkEntryNotNull(key, value);
242      alternatingKeysAndValues[2 * size] = key;
243      alternatingKeysAndValues[2 * size + 1] = value;
244      size++;
245      return this;
246    }
247
248    /**
249     * Adds the given {@code entry} to the map, making it immutable if necessary. Duplicate keys are
250     * not allowed, and will cause {@link #build} to fail.
251     *
252     * @since 11.0
253     */
254    @CanIgnoreReturnValue
255    public Builder<K, V> put(Entry<? extends K, ? extends V> entry) {
256      return put(entry.getKey(), entry.getValue());
257    }
258
259    /**
260     * Associates all of the given map's keys and values in the built map. Duplicate keys are not
261     * allowed, and will cause {@link #build} to fail.
262     *
263     * @throws NullPointerException if any key or value in {@code map} is null
264     */
265    @CanIgnoreReturnValue
266    public Builder<K, V> putAll(Map<? extends K, ? extends V> map) {
267      return putAll(map.entrySet());
268    }
269
270    /**
271     * Adds all of the given entries to the built map. Duplicate keys are not allowed, and will
272     * cause {@link #build} to fail.
273     *
274     * @throws NullPointerException if any key, value, or entry is null
275     * @since 19.0
276     */
277    @CanIgnoreReturnValue
278    @Beta
279    public Builder<K, V> putAll(Iterable<? extends Entry<? extends K, ? extends V>> entries) {
280      if (entries instanceof Collection) {
281        ensureCapacity(size + ((Collection<?>) entries).size());
282      }
283      for (Entry<? extends K, ? extends V> entry : entries) {
284        put(entry);
285      }
286      return this;
287    }
288
289    /**
290     * Configures this {@code Builder} to order entries by value according to the specified
291     * comparator.
292     *
293     * <p>The sort order is stable, that is, if two entries have values that compare as equivalent,
294     * the entry that was inserted first will be first in the built map's iteration order.
295     *
296     * @throws IllegalStateException if this method was already called
297     * @since 19.0
298     */
299    @CanIgnoreReturnValue
300    @Beta
301    public Builder<K, V> orderEntriesByValue(Comparator<? super V> valueComparator) {
302      checkState(this.valueComparator == null, "valueComparator was already set");
303      this.valueComparator = checkNotNull(valueComparator, "valueComparator");
304      return this;
305    }
306
307    /*
308     * TODO(kevinb): Should build() and the ImmutableBiMap & ImmutableSortedMap
309     * versions throw an IllegalStateException instead?
310     */
311
312    /**
313     * Returns a newly-created immutable map.  The iteration order of the returned map is
314     * the order in which entries were inserted into the builder, unless
315     * {@link #orderEntriesByValue} was called, in which case entries are sorted by value.
316     *
317     * @throws IllegalArgumentException if duplicate keys were added
318     */
319    @SuppressWarnings("unchecked")
320    public ImmutableMap<K, V> build() {
321      /*
322       * If entries is full, then this implementation may end up using the entries array
323       * directly and writing over the entry objects with non-terminal entries, but this is
324       * safe; if this Builder is used further, it will grow the entries array (so it can't
325       * affect the original array), and future build() calls will always copy any entry
326       * objects that cannot be safely reused.
327       */
328      sortEntries();
329      entriesUsed = true;
330      return RegularImmutableMap.create(size, alternatingKeysAndValues);
331    }
332
333    void sortEntries() {
334      if (valueComparator != null) {
335        if (entriesUsed) {
336          alternatingKeysAndValues = Arrays.copyOf(alternatingKeysAndValues, 2 * size);
337        }
338        Entry<K, V>[] entries = new Entry[size];
339        for (int i = 0; i < size; i++) {
340          entries[i] =
341              new AbstractMap.SimpleImmutableEntry<K, V>(
342                  (K) alternatingKeysAndValues[2 * i], (V) alternatingKeysAndValues[2 * i + 1]);
343        }
344        Arrays.sort(
345            entries, 0, size, Ordering.from(valueComparator).onResultOf(Maps.<V>valueFunction()));
346        for (int i = 0; i < size; i++) {
347          alternatingKeysAndValues[2 * i] = entries[i].getKey();
348          alternatingKeysAndValues[2 * i + 1] = entries[i].getValue();
349        }
350      }
351    }
352  }
353
354  /**
355   * Returns an immutable map containing the same entries as {@code map}. The returned map iterates
356   * over entries in the same order as the {@code entrySet} of the original map.  If {@code map}
357   * somehow contains entries with duplicate keys (for example, if it is a {@code SortedMap}
358   * whose comparator is not <i>consistent with equals</i>), the results of this method are
359   * undefined.
360   *
361   * <p>Despite the method name, this method attempts to avoid actually copying
362   * the data when it is safe to do so. The exact circumstances under which a
363   * copy will or will not be performed are undocumented and subject to change.
364   *
365   * @throws NullPointerException if any key or value in {@code map} is null
366   */
367  public static <K, V> ImmutableMap<K, V> copyOf(Map<? extends K, ? extends V> map) {
368    if ((map instanceof ImmutableMap) && !(map instanceof SortedMap)) {
369      @SuppressWarnings("unchecked") // safe since map is not writable
370      ImmutableMap<K, V> kvMap = (ImmutableMap<K, V>) map;
371      if (!kvMap.isPartialView()) {
372        return kvMap;
373      }
374    }
375    return copyOf(map.entrySet());
376  }
377
378  /**
379   * Returns an immutable map containing the specified entries. The returned map iterates over
380   * entries in the same order as the original iterable.
381   *
382   * @throws NullPointerException if any key, value, or entry is null
383   * @throws IllegalArgumentException if two entries have the same key
384   * @since 19.0
385   */
386  @Beta
387  public static <K, V> ImmutableMap<K, V> copyOf(
388      Iterable<? extends Entry<? extends K, ? extends V>> entries) {
389    int initialCapacity =
390        (entries instanceof Collection)
391            ? ((Collection<?>) entries).size()
392            : ImmutableCollection.Builder.DEFAULT_INITIAL_CAPACITY;
393    ImmutableMap.Builder<K, V> builder = new ImmutableMap.Builder<K, V>(initialCapacity);
394    builder.putAll(entries);
395    return builder.build();
396  }
397
398  static final Entry<?, ?>[] EMPTY_ENTRY_ARRAY = new Entry<?, ?>[0];
399
400  abstract static class IteratorBasedImmutableMap<K, V> extends ImmutableMap<K, V> {
401    abstract UnmodifiableIterator<Entry<K, V>> entryIterator();
402
403    @Override
404    ImmutableSet<K> createKeySet() {
405      return new ImmutableMapKeySet<>(this);
406    }
407
408    @Override
409    ImmutableSet<Entry<K, V>> createEntrySet() {
410      @WeakOuter
411      class EntrySetImpl extends ImmutableMapEntrySet<K, V> {
412        @Override
413        ImmutableMap<K, V> map() {
414          return IteratorBasedImmutableMap.this;
415        }
416
417        @Override
418        public UnmodifiableIterator<Entry<K, V>> iterator() {
419          return entryIterator();
420        }
421      }
422      return new EntrySetImpl();
423    }
424
425    @Override
426    ImmutableCollection<V> createValues() {
427      return new ImmutableMapValues<>(this);
428    }
429  }
430
431  ImmutableMap() {}
432
433  /**
434   * Guaranteed to throw an exception and leave the map unmodified.
435   *
436   * @throws UnsupportedOperationException always
437   * @deprecated Unsupported operation.
438   */
439  @CanIgnoreReturnValue
440  @Deprecated
441  @Override
442  public final V put(K k, V v) {
443    throw new UnsupportedOperationException();
444  }
445
446  /**
447   * Guaranteed to throw an exception and leave the map unmodified.
448   *
449   * @throws UnsupportedOperationException always
450   * @deprecated Unsupported operation.
451   */
452  @CanIgnoreReturnValue
453  @Deprecated
454  @Override
455  public final V remove(Object o) {
456    throw new UnsupportedOperationException();
457  }
458
459  /**
460   * Guaranteed to throw an exception and leave the map unmodified.
461   *
462   * @throws UnsupportedOperationException always
463   * @deprecated Unsupported operation.
464   */
465  @Deprecated
466  @Override
467  public final void putAll(Map<? extends K, ? extends V> map) {
468    throw new UnsupportedOperationException();
469  }
470
471  /**
472   * Guaranteed to throw an exception and leave the map unmodified.
473   *
474   * @throws UnsupportedOperationException always
475   * @deprecated Unsupported operation.
476   */
477  @Deprecated
478  @Override
479  public final void clear() {
480    throw new UnsupportedOperationException();
481  }
482
483  @Override
484  public boolean isEmpty() {
485    return size() == 0;
486  }
487
488  @Override
489  public boolean containsKey(@Nullable Object key) {
490    return get(key) != null;
491  }
492
493  @Override
494  public boolean containsValue(@Nullable Object value) {
495    return values().contains(value);
496  }
497
498  // Overriding to mark it Nullable
499  @Override
500  public abstract V get(@Nullable Object key);
501
502  @LazyInit private transient ImmutableSet<Entry<K, V>> entrySet;
503
504  /**
505   * Returns an immutable set of the mappings in this map.  The iteration order is specified by
506   * the method used to create this map.  Typically, this is insertion order.
507   */
508  @Override
509  public ImmutableSet<Entry<K, V>> entrySet() {
510    ImmutableSet<Entry<K, V>> result = entrySet;
511    return (result == null) ? entrySet = createEntrySet() : result;
512  }
513
514  abstract ImmutableSet<Entry<K, V>> createEntrySet();
515
516  @LazyInit private transient ImmutableSet<K> keySet;
517
518  /**
519   * Returns an immutable set of the keys in this map, in the same order that they appear in
520   * {@link #entrySet}.
521   */
522  @Override
523  public ImmutableSet<K> keySet() {
524    ImmutableSet<K> result = keySet;
525    return (result == null) ? keySet = createKeySet() : result;
526  }
527
528  /*
529   * This could have a good default implementation of return new ImmutableKeySet<K, V>(this),
530   * but ProGuard can't figure out how to eliminate that default when RegularImmutableMap
531   * overrides it.
532   */
533  abstract ImmutableSet<K> createKeySet();
534
535  UnmodifiableIterator<K> keyIterator() {
536    final UnmodifiableIterator<Entry<K, V>> entryIterator = entrySet().iterator();
537    return new UnmodifiableIterator<K>() {
538      @Override
539      public boolean hasNext() {
540        return entryIterator.hasNext();
541      }
542
543      @Override
544      public K next() {
545        return entryIterator.next().getKey();
546      }
547    };
548  }
549
550  @LazyInit private transient ImmutableCollection<V> values;
551
552  /**
553   * Returns an immutable collection of the values in this map, in the same order that they appear
554   * in {@link #entrySet}.
555   */
556  @Override
557  public ImmutableCollection<V> values() {
558    ImmutableCollection<V> result = values;
559    return (result == null) ? values = createValues() : result;
560  }
561
562  /*
563   * This could have a good default implementation of {@code return new
564   * ImmutableMapValues<K, V>(this)}, but ProGuard can't figure out how to eliminate that default
565   * when RegularImmutableMap overrides it.
566   */
567  abstract ImmutableCollection<V> createValues();
568
569  // cached so that this.multimapView().inverse() only computes inverse once
570  @LazyInit private transient ImmutableSetMultimap<K, V> multimapView;
571
572  /**
573   * Returns a multimap view of the map.
574   *
575   * @since 14.0
576   */
577  public ImmutableSetMultimap<K, V> asMultimap() {
578    if (isEmpty()) {
579      return ImmutableSetMultimap.of();
580    }
581    ImmutableSetMultimap<K, V> result = multimapView;
582    return (result == null)
583        ? (multimapView =
584            new ImmutableSetMultimap<>(new MapViewOfValuesAsSingletonSets(), size(), null))
585        : result;
586  }
587
588  @WeakOuter
589  private final class MapViewOfValuesAsSingletonSets
590      extends IteratorBasedImmutableMap<K, ImmutableSet<V>> {
591
592    @Override
593    public int size() {
594      return ImmutableMap.this.size();
595    }
596
597    @Override
598    ImmutableSet<K> createKeySet() {
599      return ImmutableMap.this.keySet();
600    }
601
602    @Override
603    public boolean containsKey(@Nullable Object key) {
604      return ImmutableMap.this.containsKey(key);
605    }
606
607    @Override
608    public ImmutableSet<V> get(@Nullable Object key) {
609      V outerValue = ImmutableMap.this.get(key);
610      return (outerValue == null) ? null : ImmutableSet.of(outerValue);
611    }
612
613    @Override
614    boolean isPartialView() {
615      return ImmutableMap.this.isPartialView();
616    }
617
618    @Override
619    public int hashCode() {
620      // ImmutableSet.of(value).hashCode() == value.hashCode(), so the hashes are the same
621      return ImmutableMap.this.hashCode();
622    }
623
624    @Override
625    boolean isHashCodeFast() {
626      return ImmutableMap.this.isHashCodeFast();
627    }
628
629    @Override
630    UnmodifiableIterator<Entry<K, ImmutableSet<V>>> entryIterator() {
631      final Iterator<Entry<K, V>> backingIterator = ImmutableMap.this.entrySet().iterator();
632      return new UnmodifiableIterator<Entry<K, ImmutableSet<V>>>() {
633        @Override
634        public boolean hasNext() {
635          return backingIterator.hasNext();
636        }
637
638        @Override
639        public Entry<K, ImmutableSet<V>> next() {
640          final Entry<K, V> backingEntry = backingIterator.next();
641          return new AbstractMapEntry<K, ImmutableSet<V>>() {
642            @Override
643            public K getKey() {
644              return backingEntry.getKey();
645            }
646
647            @Override
648            public ImmutableSet<V> getValue() {
649              return ImmutableSet.of(backingEntry.getValue());
650            }
651          };
652        }
653      };
654    }
655  }
656
657  @Override
658  public boolean equals(@Nullable Object object) {
659    return Maps.equalsImpl(this, object);
660  }
661
662  abstract boolean isPartialView();
663
664  @Override
665  public int hashCode() {
666    return Sets.hashCodeImpl(entrySet());
667  }
668
669  boolean isHashCodeFast() {
670    return false;
671  }
672
673  @Override
674  public String toString() {
675    return Maps.toStringImpl(this);
676  }
677
678  /**
679   * Serialized type for all ImmutableMap instances. It captures the logical contents and they are
680   * reconstructed using public factory methods. This ensures that the implementation types remain
681   * as implementation details.
682   */
683  static class SerializedForm implements Serializable {
684    private final Object[] keys;
685    private final Object[] values;
686
687    SerializedForm(ImmutableMap<?, ?> map) {
688      keys = new Object[map.size()];
689      values = new Object[map.size()];
690      int i = 0;
691      for (Entry<?, ?> entry : map.entrySet()) {
692        keys[i] = entry.getKey();
693        values[i] = entry.getValue();
694        i++;
695      }
696    }
697
698    Object readResolve() {
699      Builder<Object, Object> builder = new Builder<>(keys.length);
700      return createMap(builder);
701    }
702
703    Object createMap(Builder<Object, Object> builder) {
704      for (int i = 0; i < keys.length; i++) {
705        builder.put(keys[i], values[i]);
706      }
707      return builder.build();
708    }
709
710    private static final long serialVersionUID = 0;
711  }
712
713  Object writeReplace() {
714    return new SerializedForm(this);
715  }
716}