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