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