Flat3Map.java

  1. /*
  2.  * Licensed to the Apache Software Foundation (ASF) under one or more
  3.  * contributor license agreements.  See the NOTICE file distributed with
  4.  * this work for additional information regarding copyright ownership.
  5.  * The ASF licenses this file to You under the Apache License, Version 2.0
  6.  * (the "License"); you may not use this file except in compliance with
  7.  * the License.  You may obtain a copy of the License at
  8.  *
  9.  *      http://www.apache.org/licenses/LICENSE-2.0
  10.  *
  11.  * Unless required by applicable law or agreed to in writing, software
  12.  * distributed under the License is distributed on an "AS IS" BASIS,
  13.  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14.  * See the License for the specific language governing permissions and
  15.  * limitations under the License.
  16.  */
  17. package org.apache.commons.collections4.map;

  18. import java.io.IOException;
  19. import java.io.ObjectInputStream;
  20. import java.io.ObjectOutputStream;
  21. import java.io.Serializable;
  22. import java.util.AbstractCollection;
  23. import java.util.AbstractSet;
  24. import java.util.Collection;
  25. import java.util.Iterator;
  26. import java.util.Map;
  27. import java.util.NoSuchElementException;
  28. import java.util.Objects;
  29. import java.util.Set;

  30. import org.apache.commons.collections4.CollectionUtils;
  31. import org.apache.commons.collections4.IterableMap;
  32. import org.apache.commons.collections4.MapIterator;
  33. import org.apache.commons.collections4.ResettableIterator;
  34. import org.apache.commons.collections4.iterators.EmptyIterator;
  35. import org.apache.commons.collections4.iterators.EmptyMapIterator;

  36. /**
  37.  * A {@code Map} implementation that stores data in simple fields until
  38.  * the size is greater than 3.
  39.  * <p>
  40.  * This map is designed for performance and can outstrip HashMap.
  41.  * It also has good garbage collection characteristics.
  42.  * </p>
  43.  * <ul>
  44.  * <li>Optimized for operation at size 3 or less.
  45.  * <li>Still works well once size 3 exceeded.
  46.  * <li>Gets at size 3 or less are about 0-10% faster than HashMap,
  47.  * <li>Puts at size 3 or less are over 4 times faster than HashMap.
  48.  * <li>Performance 5% slower than HashMap once size 3 exceeded once.
  49.  * </ul>
  50.  * <p>
  51.  * The design uses two distinct modes of operation - flat and delegate.
  52.  * While the map is size 3 or less, operations map straight onto fields using
  53.  * switch statements. Once size 4 is reached, the map switches to delegate mode
  54.  * and only switches back when cleared. In delegate mode, all operations are
  55.  * forwarded straight to a HashMap resulting in the 5% performance loss.
  56.  * </p>
  57.  * <p>
  58.  * The performance gains on puts are due to not needing to create a Map Entry
  59.  * object. This is a large saving not only in performance but in garbage collection.
  60.  * </p>
  61.  * <p>
  62.  * Whilst in flat mode this map is also easy for the garbage collector to dispatch.
  63.  * This is because it contains no complex objects or arrays which slow the progress.
  64.  * </p>
  65.  * <p>
  66.  * Do not use {@code Flat3Map} if the size is likely to grow beyond 3.
  67.  * </p>
  68.  * <p>
  69.  * <strong>Note that Flat3Map is not synchronized and is not thread-safe.</strong>
  70.  * If you wish to use this map from multiple threads concurrently, you must use
  71.  * appropriate synchronization. The simplest approach is to wrap this map
  72.  * using {@link java.util.Collections#synchronizedMap(Map)}. This class may throw
  73.  * exceptions when accessed by concurrent threads without synchronization.
  74.  * </p>
  75.  *
  76.  * @param <K> the type of the keys in this map
  77.  * @param <V> the type of the values in this map
  78.  * @since 3.0
  79.  */
  80. public class Flat3Map<K, V> implements IterableMap<K, V>, Serializable, Cloneable {

  81.     abstract static class EntryIterator<K, V> {
  82.         private final Flat3Map<K, V> parent;
  83.         private int nextIndex;
  84.         private FlatMapEntry<K, V> currentEntry;

  85.         /**
  86.          * Create a new Flat3Map.EntryIterator.
  87.          */
  88.         EntryIterator(final Flat3Map<K, V> parent) {
  89.             this.parent = parent;
  90.         }

  91.         public boolean hasNext() {
  92.             return nextIndex < parent.size;
  93.         }

  94.         public Map.Entry<K, V> nextEntry() {
  95.             if (!hasNext()) {
  96.                 throw new NoSuchElementException(AbstractHashedMap.NO_NEXT_ENTRY);
  97.             }
  98.             currentEntry = new FlatMapEntry<>(parent, ++nextIndex);
  99.             return currentEntry;
  100.         }

  101.         public void remove() {
  102.             if (currentEntry == null) {
  103.                 throw new IllegalStateException(AbstractHashedMap.REMOVE_INVALID);
  104.             }
  105.             parent.remove(currentEntry.getKey());
  106.             currentEntry.setRemoved(true);
  107.             nextIndex--;
  108.             currentEntry = null;
  109.         }

  110.     }

  111.     /**
  112.      * EntrySet
  113.      */
  114.     static class EntrySet<K, V> extends AbstractSet<Map.Entry<K, V>> {
  115.         private final Flat3Map<K, V> parent;

  116.         EntrySet(final Flat3Map<K, V> parent) {
  117.             this.parent = parent;
  118.         }

  119.         @Override
  120.         public void clear() {
  121.             parent.clear();
  122.         }

  123.         @Override
  124.         public Iterator<Map.Entry<K, V>> iterator() {
  125.             if (parent.delegateMap != null) {
  126.                 return parent.delegateMap.entrySet().iterator();
  127.             }
  128.             if (parent.isEmpty()) {
  129.                 return EmptyIterator.<Map.Entry<K, V>>emptyIterator();
  130.             }
  131.             return new EntrySetIterator<>(parent);
  132.         }

  133.         @Override
  134.         public boolean remove(final Object obj) {
  135.             if (!(obj instanceof Map.Entry)) {
  136.                 return false;
  137.             }
  138.             final Map.Entry<?, ?> entry = (Map.Entry<?, ?>) obj;
  139.             final Object key = entry.getKey();
  140.             final boolean result = parent.containsKey(key);
  141.             parent.remove(key);
  142.             return result;
  143.         }

  144.         @Override
  145.         public int size() {
  146.             return parent.size();
  147.         }
  148.     }
  149.     /**
  150.      * EntrySetIterator and MapEntry
  151.      */
  152.     static class EntrySetIterator<K, V> extends EntryIterator<K, V> implements Iterator<Map.Entry<K, V>> {
  153.         EntrySetIterator(final Flat3Map<K, V> parent) {
  154.             super(parent);
  155.         }

  156.         @Override
  157.         public Map.Entry<K, V> next() {
  158.             return nextEntry();
  159.         }
  160.     }
  161.     static class FlatMapEntry<K, V> implements Map.Entry<K, V> {
  162.         private final Flat3Map<K, V> parent;
  163.         private final int index;
  164.         private volatile boolean removed;

  165.         FlatMapEntry(final Flat3Map<K, V> parent, final int index) {
  166.             this.parent = parent;
  167.             this.index = index;
  168.             this.removed = false;
  169.         }

  170.         @Override
  171.         public boolean equals(final Object obj) {
  172.             if (removed) {
  173.                 return false;
  174.             }
  175.             if (!(obj instanceof Map.Entry)) {
  176.                 return false;
  177.             }
  178.             final Map.Entry<?, ?> other = (Map.Entry<?, ?>) obj;
  179.             return Objects.equals(getKey(), other.getKey()) &&
  180.                    Objects.equals(getValue(), other.getValue());
  181.         }

  182.         @Override
  183.         public K getKey() {
  184.             if (removed) {
  185.                 throw new IllegalStateException(AbstractHashedMap.GETKEY_INVALID);
  186.             }
  187.             switch (index) {
  188.             case 3:
  189.                 return parent.key3;
  190.             case 2:
  191.                 return parent.key2;
  192.             case 1:
  193.                 return parent.key1;
  194.             }
  195.             throw new IllegalStateException("Invalid map index: " + index);
  196.         }

  197.         @Override
  198.         public V getValue() {
  199.             if (removed) {
  200.                 throw new IllegalStateException(AbstractHashedMap.GETVALUE_INVALID);
  201.             }
  202.             switch (index) {
  203.             case 3:
  204.                 return parent.value3;
  205.             case 2:
  206.                 return parent.value2;
  207.             case 1:
  208.                 return parent.value1;
  209.             }
  210.             throw new IllegalStateException("Invalid map index: " + index);
  211.         }

  212.         @Override
  213.         public int hashCode() {
  214.             if (removed) {
  215.                 return 0;
  216.             }
  217.             final Object key = getKey();
  218.             final Object value = getValue();
  219.             return (key == null ? 0 : key.hashCode()) ^
  220.                    (value == null ? 0 : value.hashCode());
  221.         }

  222.         /**
  223.          * Used by the iterator that created this entry to indicate that
  224.          * {@link java.util.Iterator#remove()} has been called.
  225.          * <p>
  226.          * As a consequence, all subsequent call to {@link #getKey()},
  227.          * {@link #setValue(Object)} and {@link #getValue()} will fail.
  228.          *
  229.          * @param removed the new value of the removed flag
  230.          */
  231.         void setRemoved(final boolean removed) {
  232.             this.removed = removed;
  233.         }

  234.         @Override
  235.         public V setValue(final V value) {
  236.             if (removed) {
  237.                 throw new IllegalStateException(AbstractHashedMap.SETVALUE_INVALID);
  238.             }
  239.             final V old = getValue();
  240.             switch (index) {
  241.             case 3:
  242.                 parent.value3 = value;
  243.                 break;
  244.             case 2:
  245.                 parent.value2 = value;
  246.                 break;
  247.             case 1:
  248.                 parent.value1 = value;
  249.                 break;
  250.             default:
  251.                 throw new IllegalStateException("Invalid map index: " + index);
  252.             }
  253.             return old;
  254.         }

  255.         @Override
  256.         public String toString() {
  257.             if (!removed) {
  258.                 return getKey() + "=" + getValue();
  259.             }
  260.             return "";
  261.         }

  262.     }

  263.     /**
  264.      * FlatMapIterator
  265.      */
  266.     static class FlatMapIterator<K, V> implements MapIterator<K, V>, ResettableIterator<K> {
  267.         private final Flat3Map<K, V> parent;
  268.         private int nextIndex;
  269.         private boolean canRemove;

  270.         FlatMapIterator(final Flat3Map<K, V> parent) {
  271.             this.parent = parent;
  272.         }

  273.         @Override
  274.         public K getKey() {
  275.             if (!canRemove) {
  276.                 throw new IllegalStateException(AbstractHashedMap.GETKEY_INVALID);
  277.             }
  278.             switch (nextIndex) {
  279.             case 3:
  280.                 return parent.key3;
  281.             case 2:
  282.                 return parent.key2;
  283.             case 1:
  284.                 return parent.key1;
  285.             }
  286.             throw new IllegalStateException("Invalid map index: " + nextIndex);
  287.         }

  288.         @Override
  289.         public V getValue() {
  290.             if (!canRemove) {
  291.                 throw new IllegalStateException(AbstractHashedMap.GETVALUE_INVALID);
  292.             }
  293.             switch (nextIndex) {
  294.             case 3:
  295.                 return parent.value3;
  296.             case 2:
  297.                 return parent.value2;
  298.             case 1:
  299.                 return parent.value1;
  300.             }
  301.             throw new IllegalStateException("Invalid map index: " + nextIndex);
  302.         }

  303.         @Override
  304.         public boolean hasNext() {
  305.             return nextIndex < parent.size;
  306.         }

  307.         @Override
  308.         public K next() {
  309.             if (!hasNext()) {
  310.                 throw new NoSuchElementException(AbstractHashedMap.NO_NEXT_ENTRY);
  311.             }
  312.             canRemove = true;
  313.             nextIndex++;
  314.             return getKey();
  315.         }

  316.         @Override
  317.         public void remove() {
  318.             if (!canRemove) {
  319.                 throw new IllegalStateException(AbstractHashedMap.REMOVE_INVALID);
  320.             }
  321.             parent.remove(getKey());
  322.             nextIndex--;
  323.             canRemove = false;
  324.         }

  325.         @Override
  326.         public void reset() {
  327.             nextIndex = 0;
  328.             canRemove = false;
  329.         }

  330.         @Override
  331.         public V setValue(final V value) {
  332.             if (!canRemove) {
  333.                 throw new IllegalStateException(AbstractHashedMap.SETVALUE_INVALID);
  334.             }
  335.             final V old = getValue();
  336.             switch (nextIndex) {
  337.             case 3:
  338.                 parent.value3 = value;
  339.                 break;
  340.             case 2:
  341.                 parent.value2 = value;
  342.                 break;
  343.             case 1:
  344.                 parent.value1 = value;
  345.                 break;
  346.             default:
  347.                 throw new IllegalStateException("Invalid map index: " + nextIndex);
  348.             }
  349.             return old;
  350.         }

  351.         @Override
  352.         public String toString() {
  353.             if (canRemove) {
  354.                 return "Iterator[" + getKey() + "=" + getValue() + "]";
  355.             }
  356.             return "Iterator[]";
  357.         }
  358.     }

  359.     /**
  360.      * KeySet
  361.      */
  362.     static class KeySet<K> extends AbstractSet<K> {

  363.         private final Flat3Map<K, ?> parent;

  364.         KeySet(final Flat3Map<K, ?> parent) {
  365.             this.parent = parent;
  366.         }

  367.         @Override
  368.         public void clear() {
  369.             parent.clear();
  370.         }

  371.         @Override
  372.         public boolean contains(final Object key) {
  373.             return parent.containsKey(key);
  374.         }

  375.         @Override
  376.         public Iterator<K> iterator() {
  377.             if (parent.delegateMap != null) {
  378.                 return parent.delegateMap.keySet().iterator();
  379.             }
  380.             if (parent.isEmpty()) {
  381.                 return EmptyIterator.<K>emptyIterator();
  382.             }
  383.             return new KeySetIterator<>(parent);
  384.         }

  385.         @Override
  386.         public boolean remove(final Object key) {
  387.             final boolean result = parent.containsKey(key);
  388.             parent.remove(key);
  389.             return result;
  390.         }

  391.         @Override
  392.         public int size() {
  393.             return parent.size();
  394.         }
  395.     }

  396.     /**
  397.      * KeySetIterator
  398.      */
  399.     static class KeySetIterator<K> extends EntryIterator<K, Object> implements Iterator<K> {

  400.         @SuppressWarnings("unchecked")
  401.         KeySetIterator(final Flat3Map<K, ?> parent) {
  402.             super((Flat3Map<K, Object>) parent);
  403.         }

  404.         @Override
  405.         public K next() {
  406.             return nextEntry().getKey();
  407.         }
  408.     }

  409.     /**
  410.      * Values
  411.      */
  412.     static class Values<V> extends AbstractCollection<V> {

  413.         private final Flat3Map<?, V> parent;

  414.         Values(final Flat3Map<?, V> parent) {
  415.             this.parent = parent;
  416.         }

  417.         @Override
  418.         public void clear() {
  419.             parent.clear();
  420.         }

  421.         @Override
  422.         public boolean contains(final Object value) {
  423.             return parent.containsValue(value);
  424.         }

  425.         @Override
  426.         public Iterator<V> iterator() {
  427.             if (parent.delegateMap != null) {
  428.                 return parent.delegateMap.values().iterator();
  429.             }
  430.             if (parent.isEmpty()) {
  431.                 return EmptyIterator.<V>emptyIterator();
  432.             }
  433.             return new ValuesIterator<>(parent);
  434.         }

  435.         @Override
  436.         public int size() {
  437.             return parent.size();
  438.         }
  439.     }

  440.     /**
  441.      * ValuesIterator
  442.      */
  443.     static class ValuesIterator<V> extends EntryIterator<Object, V> implements Iterator<V> {

  444.         @SuppressWarnings("unchecked")
  445.         ValuesIterator(final Flat3Map<?, V> parent) {
  446.             super((Flat3Map<Object, V>) parent);
  447.         }

  448.         @Override
  449.         public V next() {
  450.             return nextEntry().getValue();
  451.         }
  452.     }

  453.     /** Serialization version */
  454.     private static final long serialVersionUID = -6701087419741928296L;

  455.     /** The size of the map, used while in flat mode */
  456.     private transient int size;

  457.     /** Hash, used while in flat mode */
  458.     private transient int hash1;

  459.     /** Hash, used while in flat mode */
  460.     private transient int hash2;

  461.     /** Hash, used while in flat mode */
  462.     private transient int hash3;

  463.     /** Key, used while in flat mode */
  464.     private transient K key1;

  465.     /** Key, used while in flat mode */
  466.     private transient K key2;

  467.     /** Key, used while in flat mode */
  468.     private transient K key3;

  469.     /** Value, used while in flat mode */
  470.     private transient V value1;

  471.     /** Value, used while in flat mode */
  472.     private transient V value2;

  473.     /** Value, used while in flat mode */
  474.     private transient V value3;

  475.     /** Map, used while in delegate mode */
  476.     private transient AbstractHashedMap<K, V> delegateMap;

  477.     /**
  478.      * Constructs a new instance.
  479.      */
  480.     public Flat3Map() {
  481.     }

  482.     /**
  483.      * Constructor copying elements from another map.
  484.      *
  485.      * @param map  the map to copy
  486.      * @throws NullPointerException if the map is null
  487.      */
  488.     public Flat3Map(final Map<? extends K, ? extends V> map) {
  489.         putAll(map);
  490.     }

  491.     /**
  492.      * Clears the map, resetting the size to zero and nullifying references
  493.      * to avoid garbage collection issues.
  494.      */
  495.     @Override
  496.     public void clear() {
  497.         if (delegateMap != null) {
  498.             delegateMap.clear();  // should aid gc
  499.             delegateMap = null;  // switch back to flat mode
  500.         } else {
  501.             size = 0;
  502.             hash1 = hash2 = hash3 = 0;
  503.             key1 = key2 = key3 = null;
  504.             value1 = value2 = value3 = null;
  505.         }
  506.     }

  507.     /**
  508.      * Clones the map without cloning the keys or values.
  509.      *
  510.      * @return a shallow clone
  511.      * @since 3.1
  512.      */
  513.     @Override
  514.     @SuppressWarnings("unchecked")
  515.     public Flat3Map<K, V> clone() {
  516.         try {
  517.             final Flat3Map<K, V> cloned = (Flat3Map<K, V>) super.clone();
  518.             if (cloned.delegateMap != null) {
  519.                 cloned.delegateMap = cloned.delegateMap.clone();
  520.             }
  521.             return cloned;
  522.         } catch (final CloneNotSupportedException ex) {
  523.             throw new UnsupportedOperationException(ex);
  524.         }
  525.     }

  526.     /**
  527.      * Checks whether the map contains the specified key.
  528.      *
  529.      * @param key  the key to search for
  530.      * @return true if the map contains the key
  531.      */
  532.     @Override
  533.     public boolean containsKey(final Object key) {
  534.         if (delegateMap != null) {
  535.             return delegateMap.containsKey(key);
  536.         }
  537.         if (key == null) {
  538.             switch (size) {  // drop through
  539.             case 3:
  540.                 if (key3 == null) {
  541.                     return true;
  542.                 }
  543.             case 2:
  544.                 if (key2 == null) {
  545.                     return true;
  546.                 }
  547.             case 1:
  548.                 if (key1 == null) {
  549.                     return true;
  550.                 }
  551.             }
  552.         } else if (size > 0) {
  553.             final int hashCode = key.hashCode();
  554.             switch (size) {  // drop through
  555.             case 3:
  556.                 if (hash3 == hashCode && key.equals(key3)) {
  557.                     return true;
  558.                 }
  559.             case 2:
  560.                 if (hash2 == hashCode && key.equals(key2)) {
  561.                     return true;
  562.                 }
  563.             case 1:
  564.                 if (hash1 == hashCode && key.equals(key1)) {
  565.                     return true;
  566.                 }
  567.             }
  568.         }
  569.         return false;
  570.     }

  571.     /**
  572.      * Checks whether the map contains the specified value.
  573.      *
  574.      * @param value  the value to search for
  575.      * @return true if the map contains the key
  576.      */
  577.     @Override
  578.     public boolean containsValue(final Object value) {
  579.         if (delegateMap != null) {
  580.             return delegateMap.containsValue(value);
  581.         }
  582.         if (value == null) {  // drop through
  583.             switch (size) {
  584.             case 3:
  585.                 if (value3 == null) {
  586.                     return true;
  587.                 }
  588.             case 2:
  589.                 if (value2 == null) {
  590.                     return true;
  591.                 }
  592.             case 1:
  593.                 if (value1 == null) {
  594.                     return true;
  595.                 }
  596.             }
  597.         } else {
  598.             switch (size) {  // drop through
  599.             case 3:
  600.                 if (value.equals(value3)) {
  601.                     return true;
  602.                 }
  603.             case 2:
  604.                 if (value.equals(value2)) {
  605.                     return true;
  606.                 }
  607.             case 1:
  608.                 if (value.equals(value1)) {
  609.                     return true;
  610.                 }
  611.             }
  612.         }
  613.         return false;
  614.     }

  615.     /**
  616.      * Converts the flat map data to a map.
  617.      */
  618.     private void convertToMap() {
  619.         delegateMap = createDelegateMap();
  620.         switch (size) {  // drop through
  621.         case 3:
  622.             delegateMap.put(key3, value3);
  623.         case 2:
  624.             delegateMap.put(key2, value2);
  625.         case 1:
  626.             delegateMap.put(key1, value1);
  627.         case 0:
  628.             break;
  629.         default:
  630.             throw new IllegalStateException("Invalid map index: " + size);
  631.         }

  632.         size = 0;
  633.         hash1 = hash2 = hash3 = 0;
  634.         key1 = key2 = key3 = null;
  635.         value1 = value2 = value3 = null;
  636.     }

  637.     /**
  638.      * Create an instance of the map used for storage when in delegation mode.
  639.      * <p>
  640.      * This can be overridden by subclasses to provide a different map implementation.
  641.      * Not every AbstractHashedMap is suitable, identity and reference based maps
  642.      * would be poor choices.
  643.      * </p>
  644.      *
  645.      * @return a new AbstractHashedMap or subclass
  646.      * @since 3.1
  647.      */
  648.     protected AbstractHashedMap<K, V> createDelegateMap() {
  649.         return new HashedMap<>();
  650.     }

  651.     /**
  652.      * Gets the entrySet view of the map.
  653.      * Changes made to the view affect this map.
  654.      * <p>
  655.      * NOTE: from 4.0, the returned Map Entry will be an independent object and will
  656.      * not change anymore as the iterator progresses. To avoid this additional object
  657.      * creation and simply iterate through the entries, use {@link #mapIterator()}.
  658.      * </p>
  659.      *
  660.      * @return the entrySet view
  661.      */
  662.     @Override
  663.     public Set<Map.Entry<K, V>> entrySet() {
  664.         if (delegateMap != null) {
  665.             return delegateMap.entrySet();
  666.         }
  667.         return new EntrySet<>(this);
  668.     }

  669.     /**
  670.      * Compares this map with another.
  671.      *
  672.      * @param obj  the object to compare to
  673.      * @return true if equal
  674.      */
  675.     @Override
  676.     public boolean equals(final Object obj) {
  677.         if (obj == this) {
  678.             return true;
  679.         }
  680.         if (delegateMap != null) {
  681.             return delegateMap.equals(obj);
  682.         }
  683.         if (!(obj instanceof Map)) {
  684.             return false;
  685.         }
  686.         final Map<?, ?> other = (Map<?, ?>) obj;
  687.         if (size != other.size()) {
  688.             return false;
  689.         }
  690.         if (size > 0) {
  691.             Object otherValue = null;
  692.             switch (size) {  // drop through
  693.             case 3:
  694.                 if (!other.containsKey(key3)) {
  695.                     return false;
  696.                 }
  697.                 otherValue = other.get(key3);
  698.                 if (!Objects.equals(value3, otherValue)) {
  699.                     return false;
  700.                 }
  701.             case 2:
  702.                 if (!other.containsKey(key2)) {
  703.                     return false;
  704.                 }
  705.                 otherValue = other.get(key2);
  706.                 if (!Objects.equals(value2, otherValue)) {
  707.                     return false;
  708.                 }
  709.             case 1:
  710.                 if (!other.containsKey(key1)) {
  711.                     return false;
  712.                 }
  713.                 otherValue = other.get(key1);
  714.                 if (!Objects.equals(value1, otherValue)) {
  715.                     return false;
  716.                 }
  717.             }
  718.         }
  719.         return true;
  720.     }

  721.     /**
  722.      * Gets the value mapped to the key specified.
  723.      *
  724.      * @param key  the key
  725.      * @return the mapped value, null if no match
  726.      */
  727.     @Override
  728.     public V get(final Object key) {
  729.         if (delegateMap != null) {
  730.             return delegateMap.get(key);
  731.         }
  732.         if (key == null) {
  733.             switch (size) {
  734.             // drop through
  735.             case 3:
  736.                 if (key3 == null) {
  737.                     return value3;
  738.                 }
  739.             case 2:
  740.                 if (key2 == null) {
  741.                     return value2;
  742.                 }
  743.             case 1:
  744.                 if (key1 == null) {
  745.                     return value1;
  746.                 }
  747.             }
  748.         } else if (size > 0) {
  749.             final int hashCode = key.hashCode();
  750.             switch (size) {
  751.             // drop through
  752.             case 3:
  753.                 if (hash3 == hashCode && key.equals(key3)) {
  754.                     return value3;
  755.                 }
  756.             case 2:
  757.                 if (hash2 == hashCode && key.equals(key2)) {
  758.                     return value2;
  759.                 }
  760.             case 1:
  761.                 if (hash1 == hashCode && key.equals(key1)) {
  762.                     return value1;
  763.                 }
  764.             }
  765.         }
  766.         return null;
  767.     }

  768.     /**
  769.      * Gets the standard Map hashCode.
  770.      *
  771.      * @return the hash code defined in the Map interface
  772.      */
  773.     @Override
  774.     public int hashCode() {
  775.         if (delegateMap != null) {
  776.             return delegateMap.hashCode();
  777.         }
  778.         int total = 0;
  779.         switch (size) {  // drop through
  780.         case 3:
  781.             total += hash3 ^ (value3 == null ? 0 : value3.hashCode());
  782.         case 2:
  783.             total += hash2 ^ (value2 == null ? 0 : value2.hashCode());
  784.         case 1:
  785.             total += hash1 ^ (value1 == null ? 0 : value1.hashCode());
  786.         case 0:
  787.             break;
  788.         default:
  789.             throw new IllegalStateException("Invalid map index: " + size);
  790.         }
  791.         return total;
  792.     }

  793.     /**
  794.      * Checks whether the map is currently empty.
  795.      *
  796.      * @return true if the map is currently size zero
  797.      */
  798.     @Override
  799.     public boolean isEmpty() {
  800.         return size() == 0;
  801.     }

  802.     /**
  803.      * Gets the keySet view of the map.
  804.      * Changes made to the view affect this map.
  805.      * To simply iterate through the keys, use {@link #mapIterator()}.
  806.      *
  807.      * @return the keySet view
  808.      */
  809.     @Override
  810.     public Set<K> keySet() {
  811.         if (delegateMap != null) {
  812.             return delegateMap.keySet();
  813.         }
  814.         return new KeySet<>(this);
  815.     }

  816.     /**
  817.      * Gets an iterator over the map.
  818.      * Changes made to the iterator affect this map.
  819.      * <p>
  820.      * A MapIterator returns the keys in the map. It also provides convenient
  821.      * methods to get the key and value, and set the value.
  822.      * It avoids the need to create an entrySet/keySet/values object.
  823.      * It also avoids creating the Map Entry object.
  824.      * </p>
  825.      *
  826.      * @return the map iterator
  827.      */
  828.     @Override
  829.     public MapIterator<K, V> mapIterator() {
  830.         if (delegateMap != null) {
  831.             return delegateMap.mapIterator();
  832.         }
  833.         if (size == 0) {
  834.             return EmptyMapIterator.<K, V>emptyMapIterator();
  835.         }
  836.         return new FlatMapIterator<>(this);
  837.     }

  838.     /**
  839.      * Puts a key-value mapping into this map.
  840.      *
  841.      * @param key  the key to add
  842.      * @param value  the value to add
  843.      * @return the value previously mapped to this key, null if none
  844.      */
  845.     @Override
  846.     public V put(final K key, final V value) {
  847.         if (delegateMap != null) {
  848.             return delegateMap.put(key, value);
  849.         }
  850.         // change existing mapping
  851.         if (key == null) {
  852.             switch (size) {  // drop through
  853.             case 3:
  854.                 if (key3 == null) {
  855.                     final V old = value3;
  856.                     value3 = value;
  857.                     return old;
  858.                 }
  859.             case 2:
  860.                 if (key2 == null) {
  861.                     final V old = value2;
  862.                     value2 = value;
  863.                     return old;
  864.                 }
  865.             case 1:
  866.                 if (key1 == null) {
  867.                     final V old = value1;
  868.                     value1 = value;
  869.                     return old;
  870.                 }
  871.             }
  872.         } else if (size > 0) {
  873.             final int hashCode = key.hashCode();
  874.             switch (size) {  // drop through
  875.             case 3:
  876.                 if (hash3 == hashCode && key.equals(key3)) {
  877.                     final V old = value3;
  878.                     value3 = value;
  879.                     return old;
  880.                 }
  881.             case 2:
  882.                 if (hash2 == hashCode && key.equals(key2)) {
  883.                     final V old = value2;
  884.                     value2 = value;
  885.                     return old;
  886.                 }
  887.             case 1:
  888.                 if (hash1 == hashCode && key.equals(key1)) {
  889.                     final V old = value1;
  890.                     value1 = value;
  891.                     return old;
  892.                 }
  893.             }
  894.         }

  895.         // add new mapping
  896.         switch (size) {
  897.         case 2:
  898.             hash3 = key == null ? 0 : key.hashCode();
  899.             key3 = key;
  900.             value3 = value;
  901.             break;
  902.         case 1:
  903.             hash2 = key == null ? 0 : key.hashCode();
  904.             key2 = key;
  905.             value2 = value;
  906.             break;
  907.         case 0:
  908.             hash1 = key == null ? 0 : key.hashCode();
  909.             key1 = key;
  910.             value1 = value;
  911.             break;
  912.         default:
  913.             convertToMap();
  914.             delegateMap.put(key, value);
  915.             return null;
  916.         }
  917.         size++;
  918.         return null;
  919.     }

  920.     /**
  921.      * Puts all the values from the specified map into this map.
  922.      *
  923.      * @param map  the map to add
  924.      * @throws NullPointerException if the map is null
  925.      */
  926.     @Override
  927.     public void putAll(final Map<? extends K, ? extends V> map) {
  928.         final int size = map.size();
  929.         if (size == 0) {
  930.             return;
  931.         }
  932.         if (delegateMap != null) {
  933.             delegateMap.putAll(map);
  934.             return;
  935.         }
  936.         if (size < 4) {
  937.             for (final Map.Entry<? extends K, ? extends V> entry : map.entrySet()) {
  938.                 put(entry.getKey(), entry.getValue());
  939.             }
  940.         } else {
  941.             convertToMap();
  942.             delegateMap.putAll(map);
  943.         }
  944.     }

  945.     /**
  946.      * Deserializes the map in using a custom routine.
  947.      *
  948.      * @param in the input stream
  949.      * @throws IOException if an error occurs while reading from the stream
  950.      * @throws ClassNotFoundException if an object read from the stream cannot be loaded
  951.      */
  952.     @SuppressWarnings("unchecked")
  953.     private void readObject(final ObjectInputStream in) throws IOException, ClassNotFoundException {
  954.         in.defaultReadObject();
  955.         final int count = in.readInt();
  956.         if (count > 3) {
  957.             delegateMap = createDelegateMap();
  958.         }
  959.         for (int i = count; i > 0; i--) {
  960.             put((K) in.readObject(), (V) in.readObject());
  961.         }
  962.     }

  963.     /**
  964.      * Removes the specified mapping from this map.
  965.      *
  966.      * @param key  the mapping to remove
  967.      * @return the value mapped to the removed key, null if key not in map
  968.      */
  969.     @Override
  970.     public V remove(final Object key) {
  971.         if (delegateMap != null) {
  972.             return delegateMap.remove(key);
  973.         }
  974.         if (size == 0) {
  975.             return null;
  976.         }
  977.         if (key == null) {
  978.             switch (size) {  // drop through
  979.             case 3:
  980.                 if (key3 == null) {
  981.                     final V old = value3;
  982.                     hash3 = 0;
  983.                     key3 = null;
  984.                     value3 = null;
  985.                     size = 2;
  986.                     return old;
  987.                 }
  988.                 if (key2 == null) {
  989.                     final V old = value2;
  990.                     hash2 = hash3;
  991.                     key2 = key3;
  992.                     value2 = value3;
  993.                     hash3 = 0;
  994.                     key3 = null;
  995.                     value3 = null;
  996.                     size = 2;
  997.                     return old;
  998.                 }
  999.                 if (key1 == null) {
  1000.                     final V old = value1;
  1001.                     hash1 = hash3;
  1002.                     key1 = key3;
  1003.                     value1 = value3;
  1004.                     hash3 = 0;
  1005.                     key3 = null;
  1006.                     value3 = null;
  1007.                     size = 2;
  1008.                     return old;
  1009.                 }
  1010.                 return null;
  1011.             case 2:
  1012.                 if (key2 == null) {
  1013.                     final V old = value2;
  1014.                     hash2 = 0;
  1015.                     key2 = null;
  1016.                     value2 = null;
  1017.                     size = 1;
  1018.                     return old;
  1019.                 }
  1020.                 if (key1 == null) {
  1021.                     final V old = value1;
  1022.                     hash1 = hash2;
  1023.                     key1 = key2;
  1024.                     value1 = value2;
  1025.                     hash2 = 0;
  1026.                     key2 = null;
  1027.                     value2 = null;
  1028.                     size = 1;
  1029.                     return old;
  1030.                 }
  1031.                 return null;
  1032.             case 1:
  1033.                 if (key1 == null) {
  1034.                     final V old = value1;
  1035.                     hash1 = 0;
  1036.                     key1 = null;
  1037.                     value1 = null;
  1038.                     size = 0;
  1039.                     return old;
  1040.                 }
  1041.             }
  1042.         } else if (size > 0) {
  1043.             final int hashCode = key.hashCode();
  1044.             switch (size) {  // drop through
  1045.             case 3:
  1046.                 if (hash3 == hashCode && key.equals(key3)) {
  1047.                     final V old = value3;
  1048.                     hash3 = 0;
  1049.                     key3 = null;
  1050.                     value3 = null;
  1051.                     size = 2;
  1052.                     return old;
  1053.                 }
  1054.                 if (hash2 == hashCode && key.equals(key2)) {
  1055.                     final V old = value2;
  1056.                     hash2 = hash3;
  1057.                     key2 = key3;
  1058.                     value2 = value3;
  1059.                     hash3 = 0;
  1060.                     key3 = null;
  1061.                     value3 = null;
  1062.                     size = 2;
  1063.                     return old;
  1064.                 }
  1065.                 if (hash1 == hashCode && key.equals(key1)) {
  1066.                     final V old = value1;
  1067.                     hash1 = hash3;
  1068.                     key1 = key3;
  1069.                     value1 = value3;
  1070.                     hash3 = 0;
  1071.                     key3 = null;
  1072.                     value3 = null;
  1073.                     size = 2;
  1074.                     return old;
  1075.                 }
  1076.                 return null;
  1077.             case 2:
  1078.                 if (hash2 == hashCode && key.equals(key2)) {
  1079.                     final V old = value2;
  1080.                     hash2 = 0;
  1081.                     key2 = null;
  1082.                     value2 = null;
  1083.                     size = 1;
  1084.                     return old;
  1085.                 }
  1086.                 if (hash1 == hashCode && key.equals(key1)) {
  1087.                     final V old = value1;
  1088.                     hash1 = hash2;
  1089.                     key1 = key2;
  1090.                     value1 = value2;
  1091.                     hash2 = 0;
  1092.                     key2 = null;
  1093.                     value2 = null;
  1094.                     size = 1;
  1095.                     return old;
  1096.                 }
  1097.                 return null;
  1098.             case 1:
  1099.                 if (hash1 == hashCode && key.equals(key1)) {
  1100.                     final V old = value1;
  1101.                     hash1 = 0;
  1102.                     key1 = null;
  1103.                     value1 = null;
  1104.                     size = 0;
  1105.                     return old;
  1106.                 }
  1107.             }
  1108.         }
  1109.         return null;
  1110.     }

  1111.     /**
  1112.      * Gets the size of the map.
  1113.      *
  1114.      * @return the size
  1115.      */
  1116.     @Override
  1117.     public int size() {
  1118.         if (delegateMap != null) {
  1119.             return delegateMap.size();
  1120.         }
  1121.         return size;
  1122.     }

  1123.     /**
  1124.      * Gets the map as a String.
  1125.      *
  1126.      * @return a string version of the map
  1127.      */
  1128.     @Override
  1129.     public String toString() {
  1130.         if (delegateMap != null) {
  1131.             return delegateMap.toString();
  1132.         }
  1133.         if (size == 0) {
  1134.             return "{}";
  1135.         }
  1136.         final StringBuilder buf = new StringBuilder(128);
  1137.         buf.append('{');
  1138.         switch (size) {  // drop through
  1139.         case 3:
  1140.             buf.append(key3 == this ? "(this Map)" : key3);
  1141.             buf.append('=');
  1142.             buf.append(value3 == this ? "(this Map)" : value3);
  1143.             buf.append(CollectionUtils.COMMA);
  1144.         case 2:
  1145.             buf.append(key2 == this ? "(this Map)" : key2);
  1146.             buf.append('=');
  1147.             buf.append(value2 == this ? "(this Map)" : value2);
  1148.             buf.append(CollectionUtils.COMMA);
  1149.         case 1:
  1150.             buf.append(key1 == this ? "(this Map)" : key1);
  1151.             buf.append('=');
  1152.             buf.append(value1 == this ? "(this Map)" : value1);
  1153.             break;
  1154.         // case 0: has already been dealt with
  1155.         default:
  1156.             throw new IllegalStateException("Invalid map index: " + size);
  1157.         }
  1158.         buf.append('}');
  1159.         return buf.toString();
  1160.     }

  1161.     /**
  1162.      * Gets the values view of the map.
  1163.      * Changes made to the view affect this map.
  1164.      * To simply iterate through the values, use {@link #mapIterator()}.
  1165.      *
  1166.      * @return the values view
  1167.      */
  1168.     @Override
  1169.     public Collection<V> values() {
  1170.         if (delegateMap != null) {
  1171.             return delegateMap.values();
  1172.         }
  1173.         return new Values<>(this);
  1174.     }

  1175.     /**
  1176.      * Serializes this object to an ObjectOutputStream.
  1177.      *
  1178.      * @param out the target ObjectOutputStream.
  1179.      * @throws IOException thrown when an I/O errors occur writing to the target stream.
  1180.      */
  1181.     private void writeObject(final ObjectOutputStream out) throws IOException {
  1182.         out.defaultWriteObject();
  1183.         out.writeInt(size());
  1184.         for (final MapIterator<?, ?> it = mapIterator(); it.hasNext();) {
  1185.             out.writeObject(it.next());  // key
  1186.             out.writeObject(it.getValue());  // value
  1187.         }
  1188.     }

  1189. }