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