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}