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