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