    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.AbstractCollection;
    024import java.util.AbstractSet;
    025import java.util.Collection;
    026import java.util.Iterator;
    027import java.util.Map;
    028import java.util.NoSuchElementException;
    029import java.util.Objects;
    030import java.util.Set;
    031
    032import org.apache.commons.collections4.CollectionUtils;
    033import org.apache.commons.collections4.IterableMap;
    034import org.apache.commons.collections4.MapIterator;
    035import org.apache.commons.collections4.ResettableIterator;
    036import org.apache.commons.collections4.iterators.EmptyIterator;
    037import org.apache.commons.collections4.iterators.EmptyMapIterator;
    038
    039/**
    040 * A {@code Map} implementation that stores data in simple fields until
    041 * the size is greater than 3.
    042 * <p>
    043 * This map is designed for performance and can outstrip HashMap.
    044 * It also has good garbage collection characteristics.
    045 * </p>
    046 * <ul>
    047 * <li>Optimised for operation at size 3 or less.
    048 * <li>Still works well once size 3 exceeded.
    049 * <li>Gets at size 3 or less are about 0-10% faster than HashMap,
    050 * <li>Puts at size 3 or less are over 4 times faster than HashMap.
    051 * <li>Performance 5% slower than HashMap once size 3 exceeded once.
    052 * </ul>
    053 * <p>
    054 * The design uses two distinct modes of operation - flat and delegate.
    055 * While the map is size 3 or less, operations map straight onto fields using
    056 * switch statements. Once size 4 is reached, the map switches to delegate mode
    057 * and only switches back when cleared. In delegate mode, all operations are
    058 * forwarded straight to a HashMap resulting in the 5% performance loss.
    059 * </p>
    060 * <p>
    061 * The performance gains on puts are due to not needing to create a Map Entry
    062 * object. This is a large saving not only in performance but in garbage collection.
    063 * </p>
    064 * <p>
    065 * Whilst in flat mode this map is also easy for the garbage collector to dispatch.
    066 * This is because it contains no complex objects or arrays which slow the progress.
    067 * </p>
    068 * <p>
    069 * Do not use {@code Flat3Map} if the size is likely to grow beyond 3.
    070 * </p>
    071 * <p>
    072 * <strong>Note that Flat3Map is not synchronized and is not thread-safe.</strong>
    073 * If you wish to use this map from multiple threads concurrently, you must use
    074 * appropriate synchronization. The simplest approach is to wrap this map
    075 * using {@link java.util.Collections#synchronizedMap(Map)}. This class may throw
    076 * exceptions when accessed by concurrent threads without synchronization.
    077 * </p>
    078 *
    079 * @param <K> the type of the keys in this map
    080 * @param <V> the type of the values in this map
    081 * @since 3.0
    082 */
    083public class Flat3Map<K, V> implements IterableMap<K, V>, Serializable, Cloneable {
    084
    085    abstract static class EntryIterator<K, V> {
    086        private final Flat3Map<K, V> parent;
    087        private int nextIndex;
    088        private FlatMapEntry<K, V> currentEntry;
    089
    090        /**
    091         * Create a new Flat3Map.EntryIterator.
    092         */
    093        EntryIterator(final Flat3Map<K, V> parent) {
    094            this.parent = parent;
    095        }
    096
    097        public boolean hasNext() {
    098            return nextIndex < parent.size;
    099        }
    100
    101        public Map.Entry<K, V> nextEntry() {
    102            if (!hasNext()) {
    103                throw new NoSuchElementException(AbstractHashedMap.NO_NEXT_ENTRY);
    104            }
    105            currentEntry = new FlatMapEntry<>(parent, ++nextIndex);
    106            return currentEntry;
    107        }
    108
    109        public void remove() {
    110            if (currentEntry == null) {
    111                throw new IllegalStateException(AbstractHashedMap.REMOVE_INVALID);
    112            }
    113            parent.remove(currentEntry.getKey());
    114            currentEntry.setRemoved(true);
    115            nextIndex--;
    116            currentEntry = null;
    117        }
    118
    119    }
    120
    121    /**
    122     * EntrySet
    123     */
    124    static class EntrySet<K, V> extends AbstractSet<Map.Entry<K, V>> {
    125        private final Flat3Map<K, V> parent;
    126
    127        EntrySet(final Flat3Map<K, V> parent) {
    128            this.parent = parent;
    129        }
    130
    131        @Override
    132        public void clear() {
    133            parent.clear();
    134        }
    135
    136        @Override
    137        public Iterator<Map.Entry<K, V>> iterator() {
    138            if (parent.delegateMap != null) {
    139                return parent.delegateMap.entrySet().iterator();
    140            }
    141            if (parent.isEmpty()) {
    142                return EmptyIterator.<Map.Entry<K, V>>emptyIterator();
    143            }
    144            return new EntrySetIterator<>(parent);
    145        }
    146
    147        @Override
    148        public boolean remove(final Object obj) {
    149            if (!(obj instanceof Map.Entry)) {
    150                return false;
    151            }
    152            final Map.Entry<?, ?> entry = (Map.Entry<?, ?>) obj;
    153            final Object key = entry.getKey();
    154            final boolean result = parent.containsKey(key);
    155            parent.remove(key);
    156            return result;
    157        }
    158
    159        @Override
    160        public int size() {
    161            return parent.size();
    162        }
    163    }
    164    /**
    165     * EntrySetIterator and MapEntry
    166     */
    167    static class EntrySetIterator<K, V> extends EntryIterator<K, V> implements Iterator<Map.Entry<K, V>> {
    168        EntrySetIterator(final Flat3Map<K, V> parent) {
    169            super(parent);
    170        }
    171
    172        @Override
    173        public Map.Entry<K, V> next() {
    174            return nextEntry();
    175        }
    176    }
    177    static class FlatMapEntry<K, V> implements Map.Entry<K, V> {
    178        private final Flat3Map<K, V> parent;
    179        private final int index;
    180        private volatile boolean removed;
    181
    182        FlatMapEntry(final Flat3Map<K, V> parent, final int index) {
    183            this.parent = parent;
    184            this.index = index;
    185            this.removed = false;
    186        }
    187
    188        @Override
    189        public boolean equals(final Object obj) {
    190            if (removed) {
    191                return false;
    192            }
    193            if (!(obj instanceof Map.Entry)) {
    194                return false;
    195            }
    196            final Map.Entry<?, ?> other = (Map.Entry<?, ?>) obj;
    197            return Objects.equals(getKey(), other.getKey()) &&
    198                   Objects.equals(getValue(), other.getValue());
    199        }
    200
    201        @Override
    202        public K getKey() {
    203            if (removed) {
    204                throw new IllegalStateException(AbstractHashedMap.GETKEY_INVALID);
    205            }
    206            switch (index) {
    207            case 3:
    208                return parent.key3;
    209            case 2:
    210                return parent.key2;
    211            case 1:
    212                return parent.key1;
    213            }
    214            throw new IllegalStateException("Invalid map index: " + index);
    215        }
    216
    217        @Override
    218        public V getValue() {
    219            if (removed) {
    220                throw new IllegalStateException(AbstractHashedMap.GETVALUE_INVALID);
    221            }
    222            switch (index) {
    223            case 3:
    224                return parent.value3;
    225            case 2:
    226                return parent.value2;
    227            case 1:
    228                return parent.value1;
    229            }
    230            throw new IllegalStateException("Invalid map index: " + index);
    231        }
    232
    233        @Override
    234        public int hashCode() {
    235            if (removed) {
    236                return 0;
    237            }
    238            final Object key = getKey();
    239            final Object value = getValue();
    240            return (key == null ? 0 : key.hashCode()) ^
    241                   (value == null ? 0 : value.hashCode());
    242        }
    243
    244        /**
    245         * Used by the iterator that created this entry to indicate that
    246         * {@link java.util.Iterator#remove()} has been called.
    247         * <p>
    248         * As a consequence, all subsequent call to {@link #getKey()},
    249         * {@link #setValue(Object)} and {@link #getValue()} will fail.
    250         *
    251         * @param removed the new value of the removed flag
    252         */
    253        void setRemoved(final boolean removed) {
    254            this.removed = removed;
    255        }
    256
    257        @Override
    258        public V setValue(final V value) {
    259            if (removed) {
    260                throw new IllegalStateException(AbstractHashedMap.SETVALUE_INVALID);
    261            }
    262            final V old = getValue();
    263            switch (index) {
    264            case 3:
    265                parent.value3 = value;
    266                break;
    267            case 2:
    268                parent.value2 = value;
    269                break;
    270            case 1:
    271                parent.value1 = value;
    272                break;
    273            default:
    274                throw new IllegalStateException("Invalid map index: " + index);
    275            }
    276            return old;
    277        }
    278
    279        @Override
    280        public String toString() {
    281            if (!removed) {
    282                return getKey() + "=" + getValue();
    283            }
    284            return "";
    285        }
    286
    287    }
    288
    289    /**
    290     * FlatMapIterator
    291     */
    292    static class FlatMapIterator<K, V> implements MapIterator<K, V>, ResettableIterator<K> {
    293        private final Flat3Map<K, V> parent;
    294        private int nextIndex;
    295        private boolean canRemove;
    296
    297        FlatMapIterator(final Flat3Map<K, V> parent) {
    298            this.parent = parent;
    299        }
    300
    301        @Override
    302        public K getKey() {
    303            if (!canRemove) {
    304                throw new IllegalStateException(AbstractHashedMap.GETKEY_INVALID);
    305            }
    306            switch (nextIndex) {
    307            case 3:
    308                return parent.key3;
    309            case 2:
    310                return parent.key2;
    311            case 1:
    312                return parent.key1;
    313            }
    314            throw new IllegalStateException("Invalid map index: " + nextIndex);
    315        }
    316
    317        @Override
    318        public V getValue() {
    319            if (!canRemove) {
    320                throw new IllegalStateException(AbstractHashedMap.GETVALUE_INVALID);
    321            }
    322            switch (nextIndex) {
    323            case 3:
    324                return parent.value3;
    325            case 2:
    326                return parent.value2;
    327            case 1:
    328                return parent.value1;
    329            }
    330            throw new IllegalStateException("Invalid map index: " + nextIndex);
    331        }
    332
    333        @Override
    334        public boolean hasNext() {
    335            return nextIndex < parent.size;
    336        }
    337
    338        @Override
    339        public K next() {
    340            if (!hasNext()) {
    341                throw new NoSuchElementException(AbstractHashedMap.NO_NEXT_ENTRY);
    342            }
    343            canRemove = true;
    344            nextIndex++;
    345            return getKey();
    346        }
    347
    348        @Override
    349        public void remove() {
    350            if (!canRemove) {
    351                throw new IllegalStateException(AbstractHashedMap.REMOVE_INVALID);
    352            }
    353            parent.remove(getKey());
    354            nextIndex--;
    355            canRemove = false;
    356        }
    357
    358        @Override
    359        public void reset() {
    360            nextIndex = 0;
    361            canRemove = false;
    362        }
    363
    364        @Override
    365        public V setValue(final V value) {
    366            if (!canRemove) {
    367                throw new IllegalStateException(AbstractHashedMap.SETVALUE_INVALID);
    368            }
    369            final V old = getValue();
    370            switch (nextIndex) {
    371            case 3:
    372                parent.value3 = value;
    373                break;
    374            case 2:
    375                parent.value2 = value;
    376                break;
    377            case 1:
    378                parent.value1 = value;
    379                break;
    380            default:
    381                throw new IllegalStateException("Invalid map index: " + nextIndex);
    382            }
    383            return old;
    384        }
    385
    386        @Override
    387        public String toString() {
    388            if (canRemove) {
    389                return "Iterator[" + getKey() + "=" + getValue() + "]";
    390            }
    391            return "Iterator[]";
    392        }
    393    }
    394
    395    /**
    396     * KeySet
    397     */
    398    static class KeySet<K> extends AbstractSet<K> {
    399
    400        private final Flat3Map<K, ?> parent;
    401
    402        KeySet(final Flat3Map<K, ?> parent) {
    403            this.parent = parent;
    404        }
    405
    406        @Override
    407        public void clear() {
    408            parent.clear();
    409        }
    410
    411        @Override
    412        public boolean contains(final Object key) {
    413            return parent.containsKey(key);
    414        }
    415
    416        @Override
    417        public Iterator<K> iterator() {
    418            if (parent.delegateMap != null) {
    419                return parent.delegateMap.keySet().iterator();
    420            }
    421            if (parent.isEmpty()) {
    422                return EmptyIterator.<K>emptyIterator();
    423            }
    424            return new KeySetIterator<>(parent);
    425        }
    426
    427        @Override
    428        public boolean remove(final Object key) {
    429            final boolean result = parent.containsKey(key);
    430            parent.remove(key);
    431            return result;
    432        }
    433
    434        @Override
    435        public int size() {
    436            return parent.size();
    437        }
    438    }
    439
    440    /**
    441     * KeySetIterator
    442     */
    443    static class KeySetIterator<K> extends EntryIterator<K, Object> implements Iterator<K> {
    444
    445        @SuppressWarnings("unchecked")
    446        KeySetIterator(final Flat3Map<K, ?> parent) {
    447            super((Flat3Map<K, Object>) parent);
    448        }
    449
    450        @Override
    451        public K next() {
    452            return nextEntry().getKey();
    453        }
    454    }
    455
    456    /**
    457     * Values
    458     */
    459    static class Values<V> extends AbstractCollection<V> {
    460
    461        private final Flat3Map<?, V> parent;
    462
    463        Values(final Flat3Map<?, V> parent) {
    464            this.parent = parent;
    465        }
    466
    467        @Override
    468        public void clear() {
    469            parent.clear();
    470        }
    471
    472        @Override
    473        public boolean contains(final Object value) {
    474            return parent.containsValue(value);
    475        }
    476
    477        @Override
    478        public Iterator<V> iterator() {
    479            if (parent.delegateMap != null) {
    480                return parent.delegateMap.values().iterator();
    481            }
    482            if (parent.isEmpty()) {
    483                return EmptyIterator.<V>emptyIterator();
    484            }
    485            return new ValuesIterator<>(parent);
    486        }
    487
    488        @Override
    489        public int size() {
    490            return parent.size();
    491        }
    492    }
    493
    494    /**
    495     * ValuesIterator
    496     */
    497    static class ValuesIterator<V> extends EntryIterator<Object, V> implements Iterator<V> {
    498
    499        @SuppressWarnings("unchecked")
    500        ValuesIterator(final Flat3Map<?, V> parent) {
    501            super((Flat3Map<Object, V>) parent);
    502        }
    503
    504        @Override
    505        public V next() {
    506            return nextEntry().getValue();
    507        }
    508    }
    509
    510    /** Serialization version */
    511    private static final long serialVersionUID = -6701087419741928296L;
    512
    513    /** The size of the map, used while in flat mode */
    514    private transient int size;
    515
    516    /** Hash, used while in flat mode */
    517    private transient int hash1;
    518
    519    /** Hash, used while in flat mode */
    520    private transient int hash2;
    521
    522    /** Hash, used while in flat mode */
    523    private transient int hash3;
    524
    525    /** Key, used while in flat mode */
    526    private transient K key1;
    527
    528    /** Key, used while in flat mode */
    529    private transient K key2;
    530
    531    /** Key, used while in flat mode */
    532    private transient K key3;
    533
    534    /** Value, used while in flat mode */
    535    private transient V value1;
    536
    537    /** Value, used while in flat mode */
    538    private transient V value2;
    539
    540    /** Value, used while in flat mode */
    541    private transient V value3;
    542
    543    /** Map, used while in delegate mode */
    544    private transient AbstractHashedMap<K, V> delegateMap;
    545
    546    /**
    547     * Constructs a new instance.
    548     */
    549    public Flat3Map() {
    550    }
    551
    552    /**
    553     * Constructor copying elements from another map.
    554     *
    555     * @param map  the map to copy
    556     * @throws NullPointerException if the map is null
    557     */
    558    public Flat3Map(final Map<? extends K, ? extends V> map) {
    559        putAll(map);
    560    }
    561
    562    /**
    563     * Clears the map, resetting the size to zero and nullifying references
    564     * to avoid garbage collection issues.
    565     */
    566    @Override
    567    public void clear() {
    568        if (delegateMap != null) {
    569            delegateMap.clear();  // should aid gc
    570            delegateMap = null;  // switch back to flat mode
    571        } else {
    572            size = 0;
    573            hash1 = hash2 = hash3 = 0;
    574            key1 = key2 = key3 = null;
    575            value1 = value2 = value3 = null;
    576        }
    577    }
    578
    579    /**
    580     * Clones the map without cloning the keys or values.
    581     *
    582     * @return a shallow clone
    583     * @since 3.1
    584     */
    585    @Override
    586    @SuppressWarnings("unchecked")
    587    public Flat3Map<K, V> clone() {
    588        try {
    589            final Flat3Map<K, V> cloned = (Flat3Map<K, V>) super.clone();
    590            if (cloned.delegateMap != null) {
    591                cloned.delegateMap = cloned.delegateMap.clone();
    592            }
    593            return cloned;
    594        } catch (final CloneNotSupportedException ex) {
    595            throw new UnsupportedOperationException(ex);
    596        }
    597    }
    598
    599    /**
    600     * Checks whether the map contains the specified key.
    601     *
    602     * @param key  the key to search for
    603     * @return true if the map contains the key
    604     */
    605    @Override
    606    public boolean containsKey(final Object key) {
    607        if (delegateMap != null) {
    608            return delegateMap.containsKey(key);
    609        }
    610        if (key == null) {
    611            switch (size) {  // drop through
    612            case 3:
    613                if (key3 == null) {
    614                    return true;
    615                }
    616            case 2:
    617                if (key2 == null) {
    618                    return true;
    619                }
    620            case 1:
    621                if (key1 == null) {
    622                    return true;
    623                }
    624            }
    625        } else if (size > 0) {
    626            final int hashCode = key.hashCode();
    627            switch (size) {  // drop through
    628            case 3:
    629                if (hash3 == hashCode && key.equals(key3)) {
    630                    return true;
    631                }
    632            case 2:
    633                if (hash2 == hashCode && key.equals(key2)) {
    634                    return true;
    635                }
    636            case 1:
    637                if (hash1 == hashCode && key.equals(key1)) {
    638                    return true;
    639                }
    640            }
    641        }
    642        return false;
    643    }
    644
    645    /**
    646     * Checks whether the map contains the specified value.
    647     *
    648     * @param value  the value to search for
    649     * @return true if the map contains the key
    650     */
    651    @Override
    652    public boolean containsValue(final Object value) {
    653        if (delegateMap != null) {
    654            return delegateMap.containsValue(value);
    655        }
    656        if (value == null) {  // drop through
    657            switch (size) {
    658            case 3:
    659                if (value3 == null) {
    660                    return true;
    661                }
    662            case 2:
    663                if (value2 == null) {
    664                    return true;
    665                }
    666            case 1:
    667                if (value1 == null) {
    668                    return true;
    669                }
    670            }
    671        } else {
    672            switch (size) {  // drop through
    673            case 3:
    674                if (value.equals(value3)) {
    675                    return true;
    676                }
    677            case 2:
    678                if (value.equals(value2)) {
    679                    return true;
    680                }
    681            case 1:
    682                if (value.equals(value1)) {
    683                    return true;
    684                }
    685            }
    686        }
    687        return false;
    688    }
    689
    690    /**
    691     * Converts the flat map data to a map.
    692     */
    693    private void convertToMap() {
    694        delegateMap = createDelegateMap();
    695        switch (size) {  // drop through
    696        case 3:
    697            delegateMap.put(key3, value3);
    698        case 2:
    699            delegateMap.put(key2, value2);
    700        case 1:
    701            delegateMap.put(key1, value1);
    702        case 0:
    703            break;
    704        default:
    705            throw new IllegalStateException("Invalid map index: " + size);
    706        }
    707
    708        size = 0;
    709        hash1 = hash2 = hash3 = 0;
    710        key1 = key2 = key3 = null;
    711        value1 = value2 = value3 = null;
    712    }
    713
    714    /**
    715     * Create an instance of the map used for storage when in delegation mode.
    716     * <p>
    717     * This can be overridden by subclasses to provide a different map implementation.
    718     * Not every AbstractHashedMap is suitable, identity and reference based maps
    719     * would be poor choices.
    720     * </p>
    721     *
    722     * @return a new AbstractHashedMap or subclass
    723     * @since 3.1
    724     */
    725    protected AbstractHashedMap<K, V> createDelegateMap() {
    726        return new HashedMap<>();
    727    }
    728
    729    /**
    730     * Gets the entrySet view of the map.
    731     * Changes made to the view affect this map.
    732     * <p>
    733     * NOTE: from 4.0, the returned Map Entry will be an independent object and will
    734     * not change anymore as the iterator progresses. To avoid this additional object
    735     * creation and simply iterate through the entries, use {@link #mapIterator()}.
    736     * </p>
    737     *
    738     * @return the entrySet view
    739     */
    740    @Override
    741    public Set<Map.Entry<K, V>> entrySet() {
    742        if (delegateMap != null) {
    743            return delegateMap.entrySet();
    744        }
    745        return new EntrySet<>(this);
    746    }
    747
    748    /**
    749     * Compares this map with another.
    750     *
    751     * @param obj  the object to compare to
    752     * @return true if equal
    753     */
    754    @Override
    755    public boolean equals(final Object obj) {
    756        if (obj == this) {
    757            return true;
    758        }
    759        if (delegateMap != null) {
    760            return delegateMap.equals(obj);
    761        }
    762        if (!(obj instanceof Map)) {
    763            return false;
    764        }
    765        final Map<?, ?> other = (Map<?, ?>) obj;
    766        if (size != other.size()) {
    767            return false;
    768        }
    769        if (size > 0) {
    770            Object otherValue = null;
    771            switch (size) {  // drop through
    772            case 3:
    773                if (!other.containsKey(key3)) {
    774                    return false;
    775                }
    776                otherValue = other.get(key3);
    777                if (!Objects.equals(value3, otherValue)) {
    778                    return false;
    779                }
    780            case 2:
    781                if (!other.containsKey(key2)) {
    782                    return false;
    783                }
    784                otherValue = other.get(key2);
    785                if (!Objects.equals(value2, otherValue)) {
    786                    return false;
    787                }
    788            case 1:
    789                if (!other.containsKey(key1)) {
    790                    return false;
    791                }
    792                otherValue = other.get(key1);
    793                if (!Objects.equals(value1, otherValue)) {
    794                    return false;
    795                }
    796            }
    797        }
    798        return true;
    799    }
    800
    801    /**
    802     * Gets the value mapped to the key specified.
    803     *
    804     * @param key  the key
    805     * @return the mapped value, null if no match
    806     */
    807    @Override
    808    public V get(final Object key) {
    809        if (delegateMap != null) {
    810            return delegateMap.get(key);
    811        }
    812        if (key == null) {
    813            switch (size) {
    814            // drop through
    815            case 3:
    816                if (key3 == null) {
    817                    return value3;
    818                }
    819            case 2:
    820                if (key2 == null) {
    821                    return value2;
    822                }
    823            case 1:
    824                if (key1 == null) {
    825                    return value1;
    826                }
    827            }
    828        } else if (size > 0) {
    829            final int hashCode = key.hashCode();
    830            switch (size) {
    831            // drop through
    832            case 3:
    833                if (hash3 == hashCode && key.equals(key3)) {
    834                    return value3;
    835                }
    836            case 2:
    837                if (hash2 == hashCode && key.equals(key2)) {
    838                    return value2;
    839                }
    840            case 1:
    841                if (hash1 == hashCode && key.equals(key1)) {
    842                    return value1;
    843                }
    844            }
    845        }
    846        return null;
    847    }
    848
    849    /**
    850     * Gets the standard Map hashCode.
    851     *
    852     * @return the hash code defined in the Map interface
    853     */
    854    @Override
    855    public int hashCode() {
    856        if (delegateMap != null) {
    857            return delegateMap.hashCode();
    858        }
    859        int total = 0;
    860        switch (size) {  // drop through
    861        case 3:
    862            total += hash3 ^ (value3 == null ? 0 : value3.hashCode());
    863        case 2:
    864            total += hash2 ^ (value2 == null ? 0 : value2.hashCode());
    865        case 1:
    866            total += hash1 ^ (value1 == null ? 0 : value1.hashCode());
    867        case 0:
    868            break;
    869        default:
    870            throw new IllegalStateException("Invalid map index: " + size);
    871        }
    872        return total;
    873    }
    874
    875    /**
    876     * Checks whether the map is currently empty.
    877     *
    878     * @return true if the map is currently size zero
    879     */
    880    @Override
    881    public boolean isEmpty() {
    882        return size() == 0;
    883    }
    884
    885    /**
    886     * Gets the keySet view of the map.
    887     * Changes made to the view affect this map.
    888     * To simply iterate through the keys, use {@link #mapIterator()}.
    889     *
    890     * @return the keySet view
    891     */
    892    @Override
    893    public Set<K> keySet() {
    894        if (delegateMap != null) {
    895            return delegateMap.keySet();
    896        }
    897        return new KeySet<>(this);
    898    }
    899
    900    /**
    901     * Gets an iterator over the map.
    902     * Changes made to the iterator affect this map.
    903     * <p>
    904     * A MapIterator returns the keys in the map. It also provides convenient
    905     * methods to get the key and value, and set the value.
    906     * It avoids the need to create an entrySet/keySet/values object.
    907     * It also avoids creating the Map Entry object.
    908     * </p>
    909     *
    910     * @return the map iterator
    911     */
    912    @Override
    913    public MapIterator<K, V> mapIterator() {
    914        if (delegateMap != null) {
    915            return delegateMap.mapIterator();
    916        }
    917        if (size == 0) {
    918            return EmptyMapIterator.<K, V>emptyMapIterator();
    919        }
    920        return new FlatMapIterator<>(this);
    921    }
    922
    923    /**
    924     * Puts a key-value mapping into this map.
    925     *
    926     * @param key  the key to add
    927     * @param value  the value to add
    928     * @return the value previously mapped to this key, null if none
    929     */
    930    @Override
    931    public V put(final K key, final V value) {
    932        if (delegateMap != null) {
    933            return delegateMap.put(key, value);
    934        }
    935        // change existing mapping
    936        if (key == null) {
    937            switch (size) {  // drop through
    938            case 3:
    939                if (key3 == null) {
    940                    final V old = value3;
    941                    value3 = value;
    942                    return old;
    943                }
    944            case 2:
    945                if (key2 == null) {
    946                    final V old = value2;
    947                    value2 = value;
    948                    return old;
    949                }
    950            case 1:
    951                if (key1 == null) {
    952                    final V old = value1;
    953                    value1 = value;
    954                    return old;
    955                }
    956            }
    957        } else if (size > 0) {
    958            final int hashCode = key.hashCode();
    959            switch (size) {  // drop through
    960            case 3:
    961                if (hash3 == hashCode && key.equals(key3)) {
    962                    final V old = value3;
    963                    value3 = value;
    964                    return old;
    965                }
    966            case 2:
    967                if (hash2 == hashCode && key.equals(key2)) {
    968                    final V old = value2;
    969                    value2 = value;
    970                    return old;
    971                }
    972            case 1:
    973                if (hash1 == hashCode && key.equals(key1)) {
    974                    final V old = value1;
    975                    value1 = value;
    976                    return old;
    977                }
    978            }
    979        }
    980
    981        // add new mapping
    982        switch (size) {
    983        default:
    984            convertToMap();
    985            delegateMap.put(key, value);
    986            return null;
    987        case 2:
    988            hash3 = key == null ? 0 : key.hashCode();
    989            key3 = key;
    990            value3 = value;
    991            break;
    992        case 1:
    993            hash2 = key == null ? 0 : key.hashCode();
    994            key2 = key;
    995            value2 = value;
    996            break;
    997        case 0:
    998            hash1 = key == null ? 0 : key.hashCode();
    999            key1 = key;
    1000            value1 = value;
    1001            break;
    1002        }
    1003        size++;
    1004        return null;
    1005    }
    1006
    1007    /**
    1008     * Puts all the values from the specified map into this map.
    1009     *
    1010     * @param map  the map to add
    1011     * @throws NullPointerException if the map is null
    1012     */
    1013    @Override
    1014    public void putAll(final Map<? extends K, ? extends V> map) {
    1015        final int size = map.size();
    1016        if (size == 0) {
    1017            return;
    1018        }
    1019        if (delegateMap != null) {
    1020            delegateMap.putAll(map);
    1021            return;
    1022        }
    1023        if (size < 4) {
    1024            for (final Map.Entry<? extends K, ? extends V> entry : map.entrySet()) {
    1025                put(entry.getKey(), entry.getValue());
    1026            }
    1027        } else {
    1028            convertToMap();
    1029            delegateMap.putAll(map);
    1030        }
    1031    }
    1032
    1033    /**
    1034     * Deserializes the map in using a custom routine.
    1035     *
    1036     * @param in the input stream
    1037     * @throws IOException if an error occurs while reading from the stream
    1038     * @throws ClassNotFoundException if an object read from the stream cannot be loaded
    1039     */
    1040    @SuppressWarnings("unchecked")
    1041    private void readObject(final ObjectInputStream in) throws IOException, ClassNotFoundException {
    1042        in.defaultReadObject();
    1043        final int count = in.readInt();
    1044        if (count > 3) {
    1045            delegateMap = createDelegateMap();
    1046        }
    1047        for (int i = count; i > 0; i--) {
    1048            put((K) in.readObject(), (V) in.readObject());
    1049        }
    1050    }
    1051
    1052    /**
    1053     * Removes the specified mapping from this map.
    1054     *
    1055     * @param key  the mapping to remove
    1056     * @return the value mapped to the removed key, null if key not in map
    1057     */
    1058    @Override
    1059    public V remove(final Object key) {
    1060        if (delegateMap != null) {
    1061            return delegateMap.remove(key);
    1062        }
    1063        if (size == 0) {
    1064            return null;
    1065        }
    1066        if (key == null) {
    1067            switch (size) {  // drop through
    1068            case 3:
    1069                if (key3 == null) {
    1070                    final V old = value3;
    1071                    hash3 = 0;
    1072                    key3 = null;
    1073                    value3 = null;
    1074                    size = 2;
    1075                    return old;
    1076                }
    1077                if (key2 == null) {
    1078                    final V old = value2;
    1079                    hash2 = hash3;
    1080                    key2 = key3;
    1081                    value2 = value3;
    1082                    hash3 = 0;
    1083                    key3 = null;
    1084                    value3 = null;
    1085                    size = 2;
    1086                    return old;
    1087                }
    1088                if (key1 == null) {
    1089                    final V old = value1;
    1090                    hash1 = hash3;
    1091                    key1 = key3;
    1092                    value1 = value3;
    1093                    hash3 = 0;
    1094                    key3 = null;
    1095                    value3 = null;
    1096                    size = 2;
    1097                    return old;
    1098                }
    1099                return null;
    1100            case 2:
    1101                if (key2 == null) {
    1102                    final V old = value2;
    1103                    hash2 = 0;
    1104                    key2 = null;
    1105                    value2 = null;
    1106                    size = 1;
    1107                    return old;
    1108                }
    1109                if (key1 == null) {
    1110                    final V old = value1;
    1111                    hash1 = hash2;
    1112                    key1 = key2;
    1113                    value1 = value2;
    1114                    hash2 = 0;
    1115                    key2 = null;
    1116                    value2 = null;
    1117                    size = 1;
    1118                    return old;
    1119                }
    1120                return null;
    1121            case 1:
    1122                if (key1 == null) {
    1123                    final V old = value1;
    1124                    hash1 = 0;
    1125                    key1 = null;
    1126                    value1 = null;
    1127                    size = 0;
    1128                    return old;
    1129                }
    1130            }
    1131        } else if (size > 0) {
    1132            final int hashCode = key.hashCode();
    1133            switch (size) {  // drop through
    1134            case 3:
    1135                if (hash3 == hashCode && key.equals(key3)) {
    1136                    final V old = value3;
    1137                    hash3 = 0;
    1138                    key3 = null;
    1139                    value3 = null;
    1140                    size = 2;
    1141                    return old;
    1142                }
    1143                if (hash2 == hashCode && key.equals(key2)) {
    1144                    final V old = value2;
    1145                    hash2 = hash3;
    1146                    key2 = key3;
    1147                    value2 = value3;
    1148                    hash3 = 0;
    1149                    key3 = null;
    1150                    value3 = null;
    1151                    size = 2;
    1152                    return old;
    1153                }
    1154                if (hash1 == hashCode && key.equals(key1)) {
    1155                    final V old = value1;
    1156                    hash1 = hash3;
    1157                    key1 = key3;
    1158                    value1 = value3;
    1159                    hash3 = 0;
    1160                    key3 = null;
    1161                    value3 = null;
    1162                    size = 2;
    1163                    return old;
    1164                }
    1165                return null;
    1166            case 2:
    1167                if (hash2 == hashCode && key.equals(key2)) {
    1168                    final V old = value2;
    1169                    hash2 = 0;
    1170                    key2 = null;
    1171                    value2 = null;
    1172                    size = 1;
    1173                    return old;
    1174                }
    1175                if (hash1 == hashCode && key.equals(key1)) {
    1176                    final V old = value1;
    1177                    hash1 = hash2;
    1178                    key1 = key2;
    1179                    value1 = value2;
    1180                    hash2 = 0;
    1181                    key2 = null;
    1182                    value2 = null;
    1183                    size = 1;
    1184                    return old;
    1185                }
    1186                return null;
    1187            case 1:
    1188                if (hash1 == hashCode && key.equals(key1)) {
    1189                    final V old = value1;
    1190                    hash1 = 0;
    1191                    key1 = null;
    1192                    value1 = null;
    1193                    size = 0;
    1194                    return old;
    1195                }
    1196            }
    1197        }
    1198        return null;
    1199    }
    1200
    1201    /**
    1202     * Gets the size of the map.
    1203     *
    1204     * @return the size
    1205     */
    1206    @Override
    1207    public int size() {
    1208        if (delegateMap != null) {
    1209            return delegateMap.size();
    1210        }
    1211        return size;
    1212    }
    1213
    1214    /**
    1215     * Gets the map as a String.
    1216     *
    1217     * @return a string version of the map
    1218     */
    1219    @Override
    1220    public String toString() {
    1221        if (delegateMap != null) {
    1222            return delegateMap.toString();
    1223        }
    1224        if (size == 0) {
    1225            return "{}";
    1226        }
    1227        final StringBuilder buf = new StringBuilder(128);
    1228        buf.append('{');
    1229        switch (size) {  // drop through
    1230        case 3:
    1231            buf.append(key3 == this ? "(this Map)" : key3);
    1232            buf.append('=');
    1233            buf.append(value3 == this ? "(this Map)" : value3);
    1234            buf.append(CollectionUtils.COMMA);
    1235        case 2:
    1236            buf.append(key2 == this ? "(this Map)" : key2);
    1237            buf.append('=');
    1238            buf.append(value2 == this ? "(this Map)" : value2);
    1239            buf.append(CollectionUtils.COMMA);
    1240        case 1:
    1241            buf.append(key1 == this ? "(this Map)" : key1);
    1242            buf.append('=');
    1243            buf.append(value1 == this ? "(this Map)" : value1);
    1244            break;
    1245        // case 0: has already been dealt with
    1246        default:
    1247            throw new IllegalStateException("Invalid map index: " + size);
    1248        }
    1249        buf.append('}');
    1250        return buf.toString();
    1251    }
    1252
    1253    /**
    1254     * Gets the values view of the map.
    1255     * Changes made to the view affect this map.
    1256     * To simply iterate through the values, use {@link #mapIterator()}.
    1257     *
    1258     * @return the values view
    1259     */
    1260    @Override
    1261    public Collection<V> values() {
    1262        if (delegateMap != null) {
    1263            return delegateMap.values();
    1264        }
    1265        return new Values<>(this);
    1266    }
    1267
    1268    /**
    1269     * Serializes this object to an ObjectOutputStream.
    1270     *
    1271     * @param out the target ObjectOutputStream.
    1272     * @throws IOException thrown when an I/O errors occur writing to the target stream.
    1273     */
    1274    private void writeObject(final ObjectOutputStream out) throws IOException {
    1275        out.defaultWriteObject();
    1276        out.writeInt(size());
    1277        for (final MapIterator<?, ?> it = mapIterator(); it.hasNext();) {
    1278            out.writeObject(it.next());  // key
    1279            out.writeObject(it.getValue());  // value
    1280        }
    1281    }
    1282
    1283}