    001/*
    002 * Licensed to the Apache Software Foundation (ASF) under one or more
    003 * contributor license agreements.  See the NOTICE file distributed with
    004 * this work for additional information regarding copyright ownership.
    005 * The ASF licenses this file to You under the Apache License, Version 2.0
    006 * (the "License"); you may not use this file except in compliance with
    007 * the License.  You may obtain a copy of the License at
    008 *
    009 *      http://www.apache.org/licenses/LICENSE-2.0
    010 *
    011 * Unless required by applicable law or agreed to in writing, software
    012 * distributed under the License is distributed on an "AS IS" BASIS,
    013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    014 * See the License for the specific language governing permissions and
    015 * limitations under the License.
    016 */
    017package org.apache.commons.collections4.map;
    018
    019import java.io.IOException;
    020import java.io.ObjectInputStream;
    021import java.io.ObjectOutputStream;
    022import java.io.Serializable;
    023import java.util.AbstractList;
    024import java.util.AbstractSet;
    025import java.util.ArrayList;
    026import java.util.Collection;
    027import java.util.HashMap;
    028import java.util.Iterator;
    029import java.util.List;
    030import java.util.ListIterator;
    031import java.util.Map;
    032import java.util.NoSuchElementException;
    033import java.util.Set;
    034
    035import org.apache.commons.collections4.OrderedMap;
    036import org.apache.commons.collections4.OrderedMapIterator;
    037import org.apache.commons.collections4.ResettableIterator;
    038import org.apache.commons.collections4.iterators.AbstractUntypedIteratorDecorator;
    039import org.apache.commons.collections4.keyvalue.AbstractMapEntry;
    040import org.apache.commons.collections4.list.UnmodifiableList;
    041
    042/**
    043 * Decorates a {@code Map} to ensure that the order of addition is retained
    044 * using a {@code List} to maintain order.
    045 * <p>
    046 * The order will be used via the iterators and toArray methods on the views.
    047 * The order is also returned by the {@code MapIterator}.
    048 * The {@code orderedMapIterator()} method accesses an iterator that can
    049 * iterate both forwards and backwards through the map.
    050 * In addition, non-interface methods are provided to access the map by index.
    051 * </p>
    052 * <p>
    053 * If an object is added to the Map for a second time, it will remain in the
    054 * original position in the iteration.
    055 * </p>
    056 * <p>
    057 * <strong>Note that ListOrderedMap is not synchronized and is not thread-safe.</strong>
    058 * If you wish to use this map from multiple threads concurrently, you must use
    059 * appropriate synchronization. The simplest approach is to wrap this map
    060 * using {@link java.util.Collections#synchronizedMap(Map)}. This class may throw
    061 * exceptions when accessed by concurrent threads without synchronization.
    062 * </p>
    063 * <p>
    064 * <strong>Note that ListOrderedMap doesn't work with
    065 * {@link java.util.IdentityHashMap IdentityHashMap}, {@link CaseInsensitiveMap},
    066 * or similar maps that violate the general contract of {@link java.util.Map}.</strong>
    067 * The {@code ListOrderedMap} (or, more precisely, the underlying {@code List})
    068 * is relying on {@link Object#equals(Object) equals()}. This is fine, as long as the
    069 * decorated {@code Map} is also based on {@link Object#equals(Object) equals()},
    070 * and {@link Object#hashCode() hashCode()}, which
    071 * {@link java.util.IdentityHashMap IdentityHashMap}, and
    072 * {@link CaseInsensitiveMap} don't: The former uses {@code ==}, and
    073 * the latter uses {@link Object#equals(Object) equals()} on a lower-cased
    074 * key.
    075 * </p>
    076 * <p>
    077 * This class is {@link Serializable} starting with Commons Collections 3.1.
    078 * </p>
    079 *
    080 * @param <K> the type of the keys in this map
    081 * @param <V> the type of the values in this map
    082 * @since 3.0
    083 */
    084public class ListOrderedMap<K, V>
    085        extends AbstractMapDecorator<K, V>
    086        implements OrderedMap<K, V>, Serializable {
    087
    088    static class EntrySetView<K, V> extends AbstractSet<Map.Entry<K, V>> {
    089        private final ListOrderedMap<K, V> parent;
    090        private final List<K> insertOrder;
    091        private Set<Map.Entry<K, V>> entrySet;
    092
    093        EntrySetView(final ListOrderedMap<K, V> parent, final List<K> insertOrder) {
    094            this.parent = parent;
    095            this.insertOrder = insertOrder;
    096        }
    097
    098        @Override
    099        public void clear() {
    100            parent.clear();
    101        }
    102
    103        @Override
    104        public boolean contains(final Object obj) {
    105            return getEntrySet().contains(obj);
    106        }
    107        @Override
    108        public boolean containsAll(final Collection<?> coll) {
    109            return getEntrySet().containsAll(coll);
    110        }
    111
    112        @Override
    113        public boolean equals(final Object obj) {
    114            if (obj == this) {
    115                return true;
    116            }
    117            return getEntrySet().equals(obj);
    118        }
    119
    120        private Set<Map.Entry<K, V>> getEntrySet() {
    121            if (entrySet == null) {
    122                entrySet = parent.decorated().entrySet();
    123            }
    124            return entrySet;
    125        }
    126
    127        @Override
    128        public int hashCode() {
    129            return getEntrySet().hashCode();
    130        }
    131
    132        @Override
    133        public boolean isEmpty() {
    134            return parent.isEmpty();
    135        }
    136
    137        @Override
    138        public Iterator<Map.Entry<K, V>> iterator() {
    139            return new ListOrderedIterator<>(parent, insertOrder);
    140        }
    141
    142        @Override
    143        @SuppressWarnings("unchecked")
    144        public boolean remove(final Object obj) {
    145            if (!(obj instanceof Map.Entry)) {
    146                return false;
    147            }
    148            if (getEntrySet().contains(obj)) {
    149                final Object key = ((Map.Entry<K, V>) obj).getKey();
    150                parent.remove(key);
    151                return true;
    152            }
    153            return false;
    154        }
    155
    156        @Override
    157        public int size() {
    158            return parent.size();
    159        }
    160
    161        @Override
    162        public String toString() {
    163            return getEntrySet().toString();
    164        }
    165    }
    166
    167    static class KeySetView<K> extends AbstractSet<K> {
    168        private final ListOrderedMap<K, Object> parent;
    169
    170        @SuppressWarnings("unchecked")
    171        KeySetView(final ListOrderedMap<K, ?> parent) {
    172            this.parent = (ListOrderedMap<K, Object>) parent;
    173        }
    174
    175        @Override
    176        public void clear() {
    177            parent.clear();
    178        }
    179
    180        @Override
    181        public boolean contains(final Object value) {
    182            return parent.containsKey(value);
    183        }
    184
    185        @Override
    186        public Iterator<K> iterator() {
    187            return new AbstractUntypedIteratorDecorator<Map.Entry<K, Object>, K>(parent.entrySet().iterator()) {
    188                @Override
    189                public K next() {
    190                    return getIterator().next().getKey();
    191                }
    192            };
    193        }
    194
    195        @Override
    196        public int size() {
    197            return parent.size();
    198        }
    199    }
    200
    201    static class ListOrderedIterator<K, V> extends AbstractUntypedIteratorDecorator<K, Map.Entry<K, V>> {
    202        private final ListOrderedMap<K, V> parent;
    203        private K last;
    204
    205        ListOrderedIterator(final ListOrderedMap<K, V> parent, final List<K> insertOrder) {
    206            super(insertOrder.iterator());
    207            this.parent = parent;
    208        }
    209
    210        @Override
    211        public Map.Entry<K, V> next() {
    212            last = getIterator().next();
    213            return new ListOrderedMapEntry<>(parent, last);
    214        }
    215
    216        @Override
    217        public void remove() {
    218            super.remove();
    219            parent.decorated().remove(last);
    220        }
    221    }
    222
    223    static class ListOrderedMapEntry<K, V> extends AbstractMapEntry<K, V> {
    224        private final ListOrderedMap<K, V> parent;
    225
    226        ListOrderedMapEntry(final ListOrderedMap<K, V> parent, final K key) {
    227            super(key, null);
    228            this.parent = parent;
    229        }
    230
    231        @Override
    232        public V getValue() {
    233            return parent.get(getKey());
    234        }
    235
    236        @Override
    237        public V setValue(final V value) {
    238            return parent.decorated().put(getKey(), value);
    239        }
    240    }
    241
    242    static class ListOrderedMapIterator<K, V> implements OrderedMapIterator<K, V>, ResettableIterator<K> {
    243        private final ListOrderedMap<K, V> parent;
    244        private ListIterator<K> iterator;
    245        private K last;
    246        private boolean readable;
    247
    248        ListOrderedMapIterator(final ListOrderedMap<K, V> parent) {
    249            this.parent = parent;
    250            this.iterator = parent.insertOrder.listIterator();
    251        }
    252
    253        @Override
    254        public K getKey() {
    255            if (!readable) {
    256                throw new IllegalStateException(AbstractHashedMap.GETKEY_INVALID);
    257            }
    258            return last;
    259        }
    260
    261        @Override
    262        public V getValue() {
    263            if (!readable) {
    264                throw new IllegalStateException(AbstractHashedMap.GETVALUE_INVALID);
    265            }
    266            return parent.get(last);
    267        }
    268
    269        @Override
    270        public boolean hasNext() {
    271            return iterator.hasNext();
    272        }
    273
    274        @Override
    275        public boolean hasPrevious() {
    276            return iterator.hasPrevious();
    277        }
    278
    279        @Override
    280        public K next() {
    281            last = iterator.next();
    282            readable = true;
    283            return last;
    284        }
    285
    286        @Override
    287        public K previous() {
    288            last = iterator.previous();
    289            readable = true;
    290            return last;
    291        }
    292
    293        @Override
    294        public void remove() {
    295            if (!readable) {
    296                throw new IllegalStateException(AbstractHashedMap.REMOVE_INVALID);
    297            }
    298            iterator.remove();
    299            parent.map.remove(last);
    300            readable = false;
    301        }
    302
    303        @Override
    304        public void reset() {
    305            iterator = parent.insertOrder.listIterator();
    306            last = null;
    307            readable = false;
    308        }
    309
    310        @Override
    311        public V setValue(final V value) {
    312            if (!readable) {
    313                throw new IllegalStateException(AbstractHashedMap.SETVALUE_INVALID);
    314            }
    315            return parent.map.put(last, value);
    316        }
    317
    318        @Override
    319        public String toString() {
    320            if (readable) {
    321                return "Iterator[" + getKey() + "=" + getValue() + "]";
    322            }
    323            return "Iterator[]";
    324        }
    325    }
    326
    327    static class ValuesView<V> extends AbstractList<V> {
    328        private final ListOrderedMap<Object, V> parent;
    329
    330        @SuppressWarnings("unchecked")
    331        ValuesView(final ListOrderedMap<?, V> parent) {
    332            this.parent = (ListOrderedMap<Object, V>) parent;
    333        }
    334
    335        @Override
    336        public void clear() {
    337            parent.clear();
    338        }
    339
    340        @Override
    341        public boolean contains(final Object value) {
    342            return parent.containsValue(value);
    343        }
    344
    345        @Override
    346        public V get(final int index) {
    347            return parent.getValue(index);
    348        }
    349
    350        @Override
    351        public Iterator<V> iterator() {
    352            return new AbstractUntypedIteratorDecorator<Map.Entry<Object, V>, V>(parent.entrySet().iterator()) {
    353                @Override
    354                public V next() {
    355                    return getIterator().next().getValue();
    356                }
    357            };
    358        }
    359
    360        @Override
    361        public V remove(final int index) {
    362            return parent.remove(index);
    363        }
    364
    365        @Override
    366        public V set(final int index, final V value) {
    367            return parent.setValue(index, value);
    368        }
    369
    370        @Override
    371        public int size() {
    372            return parent.size();
    373        }
    374    }
    375
    376    /** Serialization version */
    377    private static final long serialVersionUID = 2728177751851003750L;
    378
    379    /**
    380     * Factory method to create an ordered map.
    381     * <p>
    382     * An {@code ArrayList} is used to retain order.
    383     * </p>
    384     *
    385     * @param <K>  the key type
    386     * @param <V>  the value type
    387     * @param map  the map to decorate, must not be null
    388     * @return a new list ordered map
    389     * @throws NullPointerException if map is null
    390     * @since 4.0
    391     */
    392    public static <K, V> ListOrderedMap<K, V> listOrderedMap(final Map<K, V> map) {
    393        return new ListOrderedMap<>(map);
    394    }
    395
    396    /** Internal list to hold the sequence of objects */
    397    private final List<K> insertOrder = new ArrayList<>();
    398
    399    /**
    400     * Constructs a new empty {@code ListOrderedMap} that decorates
    401     * a {@code HashMap}.
    402     *
    403     * @since 3.1
    404     */
    405    public ListOrderedMap() {
    406        this(new HashMap<>());
    407    }
    408
    409    /**
    410     * Constructor that wraps (not copies).
    411     *
    412     * @param map  the map to decorate, must not be null
    413     * @throws NullPointerException if map is null
    414     */
    415    protected ListOrderedMap(final Map<K, V> map) {
    416        super(map);
    417        insertOrder.addAll(decorated().keySet());
    418    }
    419
    420    /**
    421     * Gets an unmodifiable List view of the keys which changes as the map changes.
    422     * <p>
    423     * The returned list is unmodifiable because changes to the values of
    424     * the list (using {@link java.util.ListIterator#set(Object)}) will
    425     * effectively remove the value from the list and reinsert that value at
    426     * the end of the list, which is an unexpected side effect of changing the
    427     * value of a list.  This occurs because changing the key, changes when the
    428     * mapping is added to the map and thus where it appears in the list.
    429     * </p>
    430     * <p>
    431     * An alternative to this method is to use the better named
    432     * {@link #keyList()} or {@link #keySet()}.
    433     * </p>
    434     *
    435     * @see #keyList()
    436     * @see #keySet()
    437     * @return The ordered list of keys.
    438     */
    439    public List<K> asList() {
    440        return keyList();
    441    }
    442
    443    @Override
    444    public void clear() {
    445        decorated().clear();
    446        insertOrder.clear();
    447    }
    448
    449    /**
    450     * Gets a view over the entries in the map.
    451     * <p>
    452     * The Set will be ordered by object insertion into the map.
    453     * </p>
    454     *
    455     * @return the fully modifiable set view over the entries
    456     */
    457    @Override
    458    public Set<Map.Entry<K, V>> entrySet() {
    459        return new EntrySetView<>(this, insertOrder);
    460    }
    461
    462    /**
    463     * Gets the first key in this map by insert order.
    464     *
    465     * @return the first key currently in this map
    466     * @throws NoSuchElementException if this map is empty
    467     */
    468    @Override
    469    public K firstKey() {
    470        if (isEmpty()) {
    471            throw new NoSuchElementException("Map is empty");
    472        }
    473        return insertOrder.get(0);
    474    }
    475
    476    /**
    477     * Gets the key at the specified index.
    478     *
    479     * @param index  the index to retrieve
    480     * @return the key at the specified index
    481     * @throws IndexOutOfBoundsException if the index is invalid
    482     */
    483    public K get(final int index) {
    484        return insertOrder.get(index);
    485    }
    486
    487    /**
    488     * Gets the value at the specified index.
    489     *
    490     * @param index  the index to retrieve
    491     * @return the key at the specified index
    492     * @throws IndexOutOfBoundsException if the index is invalid
    493     */
    494    public V getValue(final int index) {
    495        return get(insertOrder.get(index));
    496    }
    497
    498    /**
    499     * Gets the index of the specified key.
    500     *
    501     * @param key  the key to find the index of
    502     * @return the index, or -1 if not found
    503     */
    504    public int indexOf(final Object key) {
    505        return insertOrder.indexOf(key);
    506    }
    507
    508    /**
    509     * Gets a view over the keys in the map as a List.
    510     * <p>
    511     * The List will be ordered by object insertion into the map.
    512     * The List is unmodifiable.
    513     * </p>
    514     *
    515     * @see #keySet()
    516     * @return the unmodifiable list view over the keys
    517     * @since 3.2
    518     */
    519    public List<K> keyList() {
    520        return UnmodifiableList.unmodifiableList(insertOrder);
    521    }
    522
    523    /**
    524     * Gets a view over the keys in the map.
    525     * <p>
    526     * The Collection will be ordered by object insertion into the map.
    527     * </p>
    528     *
    529     * @see #keyList()
    530     * @return the fully modifiable collection view over the keys
    531     */
    532    @Override
    533    public Set<K> keySet() {
    534        return new KeySetView<>(this);
    535    }
    536
    537    /**
    538     * Gets the last key in this map by insert order.
    539     *
    540     * @return the last key currently in this map
    541     * @throws NoSuchElementException if this map is empty
    542     */
    543    @Override
    544    public K lastKey() {
    545        if (isEmpty()) {
    546            throw new NoSuchElementException("Map is empty");
    547        }
    548        return insertOrder.get(size() - 1);
    549    }
    550
    551    @Override
    552    public OrderedMapIterator<K, V> mapIterator() {
    553        return new ListOrderedMapIterator<>(this);
    554    }
    555
    556    /**
    557     * Gets the next key to the one specified using insert order.
    558     * This method performs a list search to find the key and is O(n).
    559     *
    560     * @param key  the key to find previous for
    561     * @return the next key, null if no match or at start
    562     */
    563    @Override
    564    public K nextKey(final Object key) {
    565        final int index = insertOrder.indexOf(key);
    566        if (index >= 0 && index < size() - 1) {
    567            return insertOrder.get(index + 1);
    568        }
    569        return null;
    570    }
    571
    572    /**
    573     * Gets the previous key to the one specified using insert order.
    574     * This method performs a list search to find the key and is O(n).
    575     *
    576     * @param key  the key to find previous for
    577     * @return the previous key, null if no match or at start
    578     */
    579    @Override
    580    public K previousKey(final Object key) {
    581        final int index = insertOrder.indexOf(key);
    582        if (index > 0) {
    583            return insertOrder.get(index - 1);
    584        }
    585        return null;
    586    }
    587
    588    /**
    589     * Puts a key-value mapping into the map at the specified index.
    590     * <p>
    591     * If the map already contains the key, then the original mapping
    592     * is removed and the new mapping added at the specified index.
    593     * The remove may change the effect of the index. The index is
    594     * always calculated relative to the original state of the map.
    595     * </p>
    596     * <p>
    597     * Thus, the steps are: (1) remove the existing key-value mapping,
    598     * then (2) insert the new key-value mapping at the position it
    599     * would have been inserted had the remove not occurred.
    600     * </p>
    601     *
    602     * @param index  the index at which the mapping should be inserted
    603     * @param key  the key
    604     * @param value  the value
    605     * @return the value previously mapped to the key
    606     * @throws IndexOutOfBoundsException if the index is out of range [0, size]
    607     * @since 3.2
    608     */
    609    public V put(int index, final K key, final V value) {
    610        if (index < 0 || index > insertOrder.size()) {
    611            throw new IndexOutOfBoundsException("Index: " + index + ", Size: " + insertOrder.size());
    612        }
    613
    614        final Map<K, V> m = decorated();
    615        if (m.containsKey(key)) {
    616            final V result = m.remove(key);
    617            final int pos = insertOrder.indexOf(key);
    618            insertOrder.remove(pos);
    619            if (pos < index) {
    620                index--;
    621            }
    622            insertOrder.add(index, key);
    623            m.put(key, value);
    624            return result;
    625        }
    626        insertOrder.add(index, key);
    627        m.put(key, value);
    628        return null;
    629    }
    630
    631    @Override
    632    public V put(final K key, final V value) {
    633        if (decorated().containsKey(key)) {
    634            // re-adding doesn't change order
    635            return decorated().put(key, value);
    636        }
    637        // first add, so add to both map and list
    638        final V result = decorated().put(key, value);
    639        insertOrder.add(key);
    640        return result;
    641    }
    642
    643    /**
    644     * Puts the values contained in a supplied Map into the Map starting at
    645     * the specified index.
    646     *
    647     * @param index the index in the Map to start at.
    648     * @param map the Map containing the entries to be added.
    649     * @throws IndexOutOfBoundsException if the index is out of range [0, size]
    650     */
    651    public void putAll(int index, final Map<? extends K, ? extends V> map) {
    652        if (index < 0 || index > insertOrder.size()) {
    653            throw new IndexOutOfBoundsException("Index: " + index + ", Size: " + insertOrder.size());
    654        }
    655        for (final Map.Entry<? extends K, ? extends V> entry : map.entrySet()) {
    656            final K key = entry.getKey();
    657            final boolean contains = containsKey(key);
    658            // The return value of put is null if the key did not exist OR the value was null
    659            // so it cannot be used to determine whether the key was added
    660            put(index, entry.getKey(), entry.getValue());
    661            if (!contains) {
    662                // if no key was replaced, increment the index
    663                index++;
    664            } else {
    665                // otherwise put the next item after the currently inserted key
    666                index = indexOf(entry.getKey()) + 1;
    667            }
    668        }
    669    }
    670
    671    @Override
    672    public void putAll(final Map<? extends K, ? extends V> map) {
    673        for (final Map.Entry<? extends K, ? extends V> entry : map.entrySet()) {
    674            put(entry.getKey(), entry.getValue());
    675        }
    676    }
    677
    678    /**
    679     * Deserializes the map in using a custom routine.
    680     *
    681     * @param in  the input stream
    682     * @throws IOException if an error occurs while reading from the stream
    683     * @throws ClassNotFoundException if an object read from the stream cannot be loaded
    684     * @since 3.1
    685     */
    686    @SuppressWarnings("unchecked") // (1) should only fail if input stream is incorrect
    687    private void readObject(final ObjectInputStream in) throws IOException, ClassNotFoundException {
    688        in.defaultReadObject();
    689        map = (Map<K, V>) in.readObject(); // (1)
    690    }
    691
    692    /**
    693     * Removes the element at the specified index.
    694     *
    695     * @param index  the index of the object to remove
    696     * @return the removed value, or {@code null} if none existed
    697     * @throws IndexOutOfBoundsException if the index is invalid
    698     */
    699    public V remove(final int index) {
    700        return remove(get(index));
    701    }
    702
    703    @Override
    704    public V remove(final Object key) {
    705        V result = null;
    706        if (decorated().containsKey(key)) {
    707            result = decorated().remove(key);
    708            insertOrder.remove(key);
    709        }
    710        return result;
    711    }
    712
    713    /**
    714     * Sets the value at the specified index.
    715     *
    716     * @param index  the index of the value to set
    717     * @param value  the new value to set
    718     * @return the previous value at that index
    719     * @throws IndexOutOfBoundsException if the index is invalid
    720     * @since 3.2
    721     */
    722    public V setValue(final int index, final V value) {
    723        final K key = insertOrder.get(index);
    724        return put(key, value);
    725    }
    726
    727    /**
    728     * Returns the Map as a string.
    729     *
    730     * @return the Map as a String
    731     */
    732    @Override
    733    public String toString() {
    734        if (isEmpty()) {
    735            return "{}";
    736        }
    737        final StringBuilder buf = new StringBuilder();
    738        buf.append('{');
    739        boolean first = true;
    740        for (final Map.Entry<K, V> entry : entrySet()) {
    741            final K key = entry.getKey();
    742            final V value = entry.getValue();
    743            if (first) {
    744                first = false;
    745            } else {
    746                buf.append(", ");
    747            }
    748            buf.append(key == this ? "(this Map)" : key);
    749            buf.append('=');
    750            buf.append(value == this ? "(this Map)" : value);
    751        }
    752        buf.append('}');
    753        return buf.toString();
    754    }
    755
    756    /**
    757     * Gets a view over the values in the map as a List.
    758     * <p>
    759     * The List will be ordered by object insertion into the map.
    760     * The List supports remove and set, but does not support add.
    761     * </p>
    762     *
    763     * @see #values()
    764     * @return the partially modifiable list view over the values
    765     * @since 3.2
    766     */
    767    public List<V> valueList() {
    768        return new ValuesView<>(this);
    769    }
    770
    771    /**
    772     * Gets a view over the values in the map.
    773     * <p>
    774     * The Collection will be ordered by object insertion into the map.
    775     * </p>
    776     * <p>
    777     * From Commons Collections 3.2, this Collection can be cast
    778     * to a list, see {@link #valueList()}
    779     * </p>
    780     *
    781     * @see #valueList()
    782     * @return the fully modifiable collection view over the values
    783     */
    784    @Override
    785    public Collection<V> values() {
    786        return new ValuesView<>(this);
    787    }
    788
    789    /**
    790     * Serializes this object to an ObjectOutputStream.
    791     *
    792     * @param out the target ObjectOutputStream.
    793     * @throws IOException thrown when an I/O errors occur writing to the target stream.
    794     * @since 3.1
    795     */
    796    private void writeObject(final ObjectOutputStream out) throws IOException {
    797        out.defaultWriteObject();
    798        out.writeObject(map);
    799    }
    800
    801}