1/* Licensed to the Apache Software Foundation (ASF) under one or more
2 * contributor license agreements.  See the NOTICE file distributed with
3 * this work for additional information regarding copyright ownership.
4 * The ASF licenses this file to You under the Apache License, Version 2.0
5 * (the "License"); you may not use this file except in compliance with
6 * the License.  You may obtain a copy of the License at
7 *
8 *     http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package java.util;
18
19import java.io.IOException;
20import java.io.ObjectInputStream;
21import java.io.ObjectOutputStream;
22import java.io.Serializable;
23import java.lang.reflect.Array;
24
25/**
26 * An {@code Map} specialized for use with {@code Enum} types as keys.
27 */
28public class EnumMap<K extends Enum<K>, V> extends AbstractMap<K, V> implements
29        Serializable, Cloneable, Map<K, V> {
30
31    // BEGIN android-changed
32    // added implements Map<K, V> for apicheck
33    // END android-changed
34
35    private static final long serialVersionUID = 458661240069192865L;
36
37    private Class<K> keyType;
38
39    transient Enum[] keys;
40
41    transient Object[] values;
42
43    transient boolean[] hasMapping;
44
45    private transient int mappingsCount;
46
47    transient int enumSize;
48
49    private transient EnumMapEntrySet<K, V> entrySet = null;
50
51    private static class Entry<KT extends Enum<KT>, VT> extends
52            MapEntry<KT, VT> {
53        private final EnumMap<KT, VT> enumMap;
54
55        private final int ordinal;
56
57        Entry(KT theKey, VT theValue, EnumMap<KT, VT> em) {
58            super(theKey, theValue);
59            enumMap = em;
60            ordinal = ((Enum) theKey).ordinal();
61        }
62
63        @SuppressWarnings("unchecked")
64        @Override
65        public boolean equals(Object object) {
66            if (!enumMap.hasMapping[ordinal]) {
67                return false;
68            }
69            boolean isEqual = false;
70            if (object instanceof Map.Entry) {
71                Map.Entry<KT, VT> entry = (Map.Entry<KT, VT>) object;
72                Object enumKey = entry.getKey();
73                if (key.equals(enumKey)) {
74                    Object theValue = entry.getValue();
75                    if (enumMap.values[ordinal] == null) {
76                        isEqual = (theValue == null);
77                    } else {
78                        isEqual = enumMap.values[ordinal].equals(theValue);
79                    }
80                }
81            }
82            return isEqual;
83        }
84
85        @Override
86        public int hashCode() {
87            return (enumMap.keys[ordinal] == null ? 0 : enumMap.keys[ordinal]
88                    .hashCode())
89                    ^ (enumMap.values[ordinal] == null ? 0
90                            : enumMap.values[ordinal].hashCode());
91        }
92
93        @SuppressWarnings("unchecked")
94        @Override
95        public KT getKey() {
96            checkEntryStatus();
97            return (KT) enumMap.keys[ordinal];
98        }
99
100        @SuppressWarnings("unchecked")
101        @Override
102        public VT getValue() {
103            checkEntryStatus();
104            return (VT) enumMap.values[ordinal];
105        }
106
107        @SuppressWarnings("unchecked")
108        @Override
109        public VT setValue(VT value) {
110            checkEntryStatus();
111            return enumMap.put((KT) enumMap.keys[ordinal], value);
112        }
113
114        @Override
115        public String toString() {
116            StringBuilder result = new StringBuilder(enumMap.keys[ordinal]
117                    .toString());
118            result.append("=");
119            result.append(enumMap.values[ordinal] == null
120                    ? "null" : enumMap.values[ordinal].toString());
121            return result.toString();
122        }
123
124        private void checkEntryStatus() {
125            if (!enumMap.hasMapping[ordinal]) {
126                throw new IllegalStateException();
127            }
128        }
129    }
130
131    private static class EnumMapIterator<E, KT extends Enum<KT>, VT> implements
132            Iterator<E> {
133        int position = 0;
134
135        int prePosition = -1;
136
137        final EnumMap<KT, VT> enumMap;
138
139        final MapEntry.Type<E, KT, VT> type;
140
141        EnumMapIterator(MapEntry.Type<E, KT, VT> value, EnumMap<KT, VT> em) {
142            enumMap = em;
143            type = value;
144        }
145
146        public boolean hasNext() {
147            int length = enumMap.enumSize;
148            for (; position < length; position++) {
149                if (enumMap.hasMapping[position]) {
150                    break;
151                }
152            }
153            return position != length;
154        }
155
156        @SuppressWarnings("unchecked")
157        public E next() {
158            if (!hasNext()) {
159                throw new NoSuchElementException();
160            }
161            prePosition = position++;
162            return type.get(new MapEntry(enumMap.keys[prePosition],
163                    enumMap.values[prePosition]));
164        }
165
166        public void remove() {
167            checkStatus();
168            if (enumMap.hasMapping[prePosition]) {
169                enumMap.remove(enumMap.keys[prePosition]);
170            }
171            prePosition = -1;
172        }
173
174        @Override
175        @SuppressWarnings("unchecked")
176        public String toString() {
177            if (-1 == prePosition) {
178                return super.toString();
179            }
180            return type.get(
181                    new MapEntry(enumMap.keys[prePosition],
182                            enumMap.values[prePosition])).toString();
183        }
184
185        private void checkStatus() {
186            if (-1 == prePosition) {
187                throw new IllegalStateException();
188            }
189        }
190    }
191
192    private static class EnumMapKeySet<KT extends Enum<KT>, VT> extends
193            AbstractSet<KT> {
194        private final EnumMap<KT, VT> enumMap;
195
196        EnumMapKeySet(EnumMap<KT, VT> em) {
197            enumMap = em;
198        }
199
200        @Override
201        public void clear() {
202            enumMap.clear();
203        }
204
205        @Override
206        public boolean contains(Object object) {
207            return enumMap.containsKey(object);
208        }
209
210        @Override
211        @SuppressWarnings("unchecked")
212        public Iterator iterator() {
213            return new EnumMapIterator<KT, KT, VT>(
214                    new MapEntry.Type<KT, KT, VT>() {
215                        public KT get(MapEntry<KT, VT> entry) {
216                            return entry.key;
217                        }
218                    }, enumMap);
219        }
220
221        @Override
222        @SuppressWarnings("unchecked")
223        public boolean remove(Object object) {
224            if (contains(object)) {
225                enumMap.remove(object);
226                return true;
227            }
228            return false;
229        }
230
231        @Override
232        public int size() {
233            return enumMap.size();
234        }
235    }
236
237    private static class EnumMapValueCollection<KT extends Enum<KT>, VT>
238            extends AbstractCollection<VT> {
239        private final EnumMap<KT, VT> enumMap;
240
241        EnumMapValueCollection(EnumMap<KT, VT> em) {
242            enumMap = em;
243        }
244
245        @Override
246        public void clear() {
247            enumMap.clear();
248        }
249
250        @Override
251        public boolean contains(Object object) {
252            return enumMap.containsValue(object);
253        }
254
255        @SuppressWarnings("unchecked")
256        @Override
257        public Iterator iterator() {
258            return new EnumMapIterator<VT, KT, VT>(
259                    new MapEntry.Type<VT, KT, VT>() {
260                        public VT get(MapEntry<KT, VT> entry) {
261                            return entry.value;
262                        }
263                    }, enumMap);
264        }
265
266        @Override
267        public boolean remove(Object object) {
268            if (object == null) {
269                for (int i = 0; i < enumMap.enumSize; i++) {
270                    if (enumMap.hasMapping[i] && enumMap.values[i] == null) {
271                        enumMap.remove(enumMap.keys[i]);
272                        return true;
273                    }
274                }
275            } else {
276                for (int i = 0; i < enumMap.enumSize; i++) {
277                    if (enumMap.hasMapping[i]
278                            && object.equals(enumMap.values[i])) {
279                        enumMap.remove(enumMap.keys[i]);
280                        return true;
281                    }
282                }
283            }
284            return false;
285        }
286
287        @Override
288        public int size() {
289            return enumMap.size();
290        }
291    }
292
293    private static class EnumMapEntryIterator<E, KT extends Enum<KT>, VT>
294            extends EnumMapIterator<E, KT, VT> {
295        EnumMapEntryIterator(MapEntry.Type<E, KT, VT> value, EnumMap<KT, VT> em) {
296            super(value, em);
297        }
298
299        @SuppressWarnings("unchecked")
300        @Override
301        public E next() {
302            if (!hasNext()) {
303                throw new NoSuchElementException();
304            }
305            prePosition = position++;
306            return type.get(new Entry<KT, VT>((KT) enumMap.keys[prePosition],
307                    (VT) enumMap.values[prePosition], enumMap));
308        }
309    }
310
311    private static class EnumMapEntrySet<KT extends Enum<KT>, VT> extends
312            AbstractSet<Map.Entry<KT, VT>> {
313        private final EnumMap<KT, VT> enumMap;
314
315        EnumMapEntrySet(EnumMap<KT, VT> em) {
316            enumMap = em;
317        }
318
319        @Override
320        public void clear() {
321            enumMap.clear();
322        }
323
324        @Override
325        public boolean contains(Object object) {
326            boolean isEqual = false;
327            if (object instanceof Map.Entry) {
328                Object enumKey = ((Map.Entry) object).getKey();
329                Object enumValue = ((Map.Entry) object).getValue();
330                if (enumMap.containsKey(enumKey)) {
331                    VT value = enumMap.get(enumKey);
332                    if (value == null) {
333                        isEqual = enumValue == null;
334                    } else {
335                        isEqual = value.equals(enumValue);
336                    }
337                }
338            }
339            return isEqual;
340        }
341
342        @Override
343        public Iterator<Map.Entry<KT, VT>> iterator() {
344            return new EnumMapEntryIterator<Map.Entry<KT, VT>, KT, VT>(
345                    new MapEntry.Type<Map.Entry<KT, VT>, KT, VT>() {
346                        public Map.Entry<KT, VT> get(MapEntry<KT, VT> entry) {
347                            return entry;
348                        }
349                    }, enumMap);
350        }
351
352        @Override
353        public boolean remove(Object object) {
354            if (contains(object)) {
355                enumMap.remove(((Map.Entry) object).getKey());
356                return true;
357            }
358            return false;
359        }
360
361        @Override
362        public int size() {
363            return enumMap.size();
364        }
365
366        @Override
367        public Object[] toArray() {
368            Object[] entryArray = new Object[enumMap.size()];
369            return toArray(entryArray);
370        }
371
372        @SuppressWarnings("unchecked")
373        @Override
374        public Object[] toArray(Object[] array) {
375            int size = enumMap.size();
376            int index = 0;
377            Object[] entryArray = array;
378            if (size > array.length) {
379                Class<?> clazz = array.getClass().getComponentType();
380                entryArray = (Object[]) Array.newInstance(clazz, size);
381            }
382            Iterator<Map.Entry<KT, VT>> iter = iterator();
383            for (; index < size; index++) {
384                Map.Entry<KT, VT> entry = iter.next();
385                entryArray[index] = new MapEntry<KT, VT>(entry.getKey(), entry
386                        .getValue());
387            }
388            if (index < array.length) {
389                entryArray[index] = null;
390            }
391            return entryArray;
392        }
393    }
394
395    /**
396     * Constructs an empty {@code EnumMap} using the given key type.
397     *
398     * @param keyType
399     *            the class object giving the type of the keys used by this {@code EnumMap}.
400     * @throws NullPointerException
401     *             if {@code keyType} is {@code null}.
402     */
403    public EnumMap(Class<K> keyType) {
404        initialization(keyType);
405    }
406
407    /**
408     * Constructs an {@code EnumMap} using the same key type as the given {@code EnumMap} and
409     * initially containing the same mappings.
410     *
411     * @param map
412     *            the {@code EnumMap} from which this {@code EnumMap} is initialized.
413     * @throws NullPointerException
414     *             if {@code map} is {@code null}.
415     */
416    public EnumMap(EnumMap<K, ? extends V> map) {
417        initialization(map);
418    }
419
420    /**
421     * Constructs an {@code EnumMap} initialized from the given map. If the given map
422     * is an {@code EnumMap} instance, this constructor behaves in the exactly the same
423     * way as {@link EnumMap#EnumMap(EnumMap)}}. Otherwise, the given map
424     * should contain at least one mapping.
425     *
426     * @param map
427     *            the map from which this {@code EnumMap} is initialized.
428     * @throws IllegalArgumentException
429     *             if {@code map} is not an {@code EnumMap} instance and does not contain
430     *             any mappings.
431     * @throws NullPointerException
432     *             if {@code map} is {@code null}.
433     */
434    @SuppressWarnings("unchecked")
435    public EnumMap(Map<K, ? extends V> map) {
436        if (map instanceof EnumMap) {
437            initialization((EnumMap<K, V>) map);
438        } else {
439            if (map.size() == 0) {
440                throw new IllegalArgumentException();
441            }
442            Iterator<K> iter = map.keySet().iterator();
443            K enumKey = iter.next();
444            Class clazz = enumKey.getClass();
445            if (clazz.isEnum()) {
446                initialization(clazz);
447            } else {
448                initialization(clazz.getSuperclass());
449            }
450            putAllImpl(map);
451        }
452    }
453
454    /**
455     * Removes all elements from this {@code EnumMap}, leaving it empty.
456     *
457     * @see #isEmpty()
458     * @see #size()
459     */
460    @Override
461    public void clear() {
462        Arrays.fill(values, null);
463        Arrays.fill(hasMapping, false);
464        mappingsCount = 0;
465    }
466
467    /**
468     * Returns a shallow copy of this {@code EnumMap}.
469     *
470     * @return a shallow copy of this {@code EnumMap}.
471     */
472    @SuppressWarnings("unchecked")
473    @Override
474    public EnumMap<K, V> clone() {
475        try {
476            EnumMap<K, V> enumMap = (EnumMap<K, V>) super.clone();
477            enumMap.initialization(this);
478            return enumMap;
479        } catch (CloneNotSupportedException e) {
480            throw new AssertionError(e);
481        }
482    }
483
484    /**
485     * Returns whether this {@code EnumMap} contains the specified key.
486     *
487     * @param key
488     *            the key to search for.
489     * @return {@code true} if this {@code EnumMap} contains the specified key,
490     *         {@code false} otherwise.
491     */
492    @Override
493    public boolean containsKey(Object key) {
494        if (isValidKeyType(key)) {
495            int keyOrdinal = ((Enum) key).ordinal();
496            return hasMapping[keyOrdinal];
497        }
498        return false;
499    }
500
501    /**
502     * Returns whether this {@code EnumMap} contains the specified value.
503     *
504     * @param value
505     *            the value to search for.
506     * @return {@code true} if this {@code EnumMap} contains the specified value,
507     *         {@code false} otherwise.
508     */
509    @Override
510    public boolean containsValue(Object value) {
511        if (value == null) {
512            for (int i = 0; i < enumSize; i++) {
513                if (hasMapping[i] && values[i] == null) {
514                    return true;
515                }
516            }
517        } else {
518            for (int i = 0; i < enumSize; i++) {
519                if (hasMapping[i] && value.equals(values[i])) {
520                    return true;
521                }
522            }
523        }
524        return false;
525    }
526
527    /**
528     * Returns a {@code Set} containing all of the mappings in this {@code EnumMap}. Each mapping is
529     * an instance of {@link Map.Entry}. As the {@code Set} is backed by this {@code EnumMap},
530     * changes in one will be reflected in the other.
531     * <p>
532     * The order of the entries in the set will be the order that the enum keys
533     * were declared in.
534     *
535     * @return a {@code Set} of the mappings.
536     */
537    @Override
538    public Set<Map.Entry<K, V>> entrySet() {
539        if (entrySet == null) {
540            entrySet = new EnumMapEntrySet<K, V>(this);
541        }
542        return entrySet;
543    }
544
545    /**
546     * Compares the argument to the receiver, and returns {@code true} if the
547     * specified {@code Object} is an {@code EnumMap} and both {@code EnumMap}s contain the same mappings.
548     *
549     * @param object
550     *            the {@code Object} to compare with this {@code EnumMap}.
551     * @return boolean {@code true} if {@code object} is the same as this {@code EnumMap},
552     *         {@code false} otherwise.
553     * @see #hashCode()
554     * @see #entrySet()
555     */
556    @SuppressWarnings("unchecked")
557    @Override
558    public boolean equals(Object object) {
559        if (this == object) {
560            return true;
561        }
562        if (!(object instanceof EnumMap)) {
563            return super.equals(object);
564        }
565        EnumMap<K, V> enumMap = (EnumMap<K, V>) object;
566        if (keyType != enumMap.keyType || size() != enumMap.size()) {
567            return false;
568        }
569        return Arrays.equals(hasMapping, enumMap.hasMapping)
570                && Arrays.equals(values, enumMap.values);
571    }
572
573    /**
574     * Returns the value of the mapping with the specified key.
575     *
576     * @param key
577     *            the key.
578     * @return the value of the mapping with the specified key, or {@code null}
579     *         if no mapping for the specified key is found.
580     */
581    @Override
582    @SuppressWarnings("unchecked")
583    public V get(Object key) {
584        if (!isValidKeyType(key)) {
585            return null;
586        }
587        int keyOrdinal = ((Enum) key).ordinal();
588        return (V) values[keyOrdinal];
589    }
590
591    /**
592     * Returns a set of the keys contained in this {@code EnumMap}. The {@code Set} is backed by
593     * this {@code EnumMap} so changes to one are reflected in the other. The {@code Set} does not
594     * support adding.
595     * <p>
596     * The order of the set will be the order that the enum keys were declared
597     * in.
598     *
599     * @return a {@code Set} of the keys.
600     */
601    @Override
602    public Set<K> keySet() {
603        if (keySet == null) {
604            keySet = new EnumMapKeySet<K, V>(this);
605        }
606        return keySet;
607    }
608
609    /**
610     * Maps the specified key to the specified value.
611     *
612     * @param key
613     *            the key.
614     * @param value
615     *            the value.
616     * @return the value of any previous mapping with the specified key or
617     *         {@code null} if there was no mapping.
618     * @throws UnsupportedOperationException
619     *                if adding to this map is not supported.
620     * @throws ClassCastException
621     *                if the class of the key or value is inappropriate for this
622     *                map.
623     * @throws IllegalArgumentException
624     *                if the key or value cannot be added to this map.
625     * @throws NullPointerException
626     *                if the key or value is {@code null} and this {@code EnumMap} does not
627     *                support {@code null} keys or values.
628     */
629    @Override
630    @SuppressWarnings("unchecked")
631    public V put(K key, V value) {
632        return putImpl(key, value);
633    }
634
635    /**
636     * Copies every mapping in the specified {@code Map} to this {@code EnumMap}.
637     *
638     * @param map
639     *            the {@code Map} to copy mappings from.
640     * @throws UnsupportedOperationException
641     *                if adding to this {@code EnumMap} is not supported.
642     * @throws ClassCastException
643     *                if the class of a key or value is inappropriate for this
644     *                {@code EnumMap}.
645     * @throws IllegalArgumentException
646     *                if a key or value cannot be added to this map.
647     * @throws NullPointerException
648     *                if a key or value is {@code null} and this {@code EnumMap} does not
649     *                support {@code null} keys or values.
650     */
651    @Override
652    @SuppressWarnings("unchecked")
653    public void putAll(Map<? extends K, ? extends V> map) {
654        putAllImpl(map);
655    }
656
657    /**
658     * Removes a mapping with the specified key from this {@code EnumMap}.
659     *
660     * @param key
661     *            the key of the mapping to remove.
662     * @return the value of the removed mapping or {@code null} if no mapping
663     *         for the specified key was found.
664     * @throws UnsupportedOperationException
665     *                if removing from this {@code EnumMap} is not supported.
666     */
667    @Override
668    @SuppressWarnings("unchecked")
669    public V remove(Object key) {
670        if (!isValidKeyType(key)) {
671            return null;
672        }
673        int keyOrdinal = ((Enum) key).ordinal();
674        if (hasMapping[keyOrdinal]) {
675            hasMapping[keyOrdinal] = false;
676            mappingsCount--;
677        }
678        V oldValue = (V) values[keyOrdinal];
679        values[keyOrdinal] = null;
680        return oldValue;
681    }
682
683    /**
684     * Returns the number of elements in this {@code EnumMap}.
685     *
686     * @return the number of elements in this {@code EnumMap}.
687     */
688    @Override
689    public int size() {
690        return mappingsCount;
691    }
692
693    /**
694     * Returns a {@code Collection} of the values contained in this {@code EnumMap}. The returned
695     * {@code Collection} complies with the general rule specified in
696     * {@link Map#values()}. The {@code Collection}'s {@code Iterator} will return the values
697     * in the their corresponding keys' natural order (the {@code Enum} constants are
698     * declared in this order).
699     * <p>
700     * The order of the values in the collection will be the order that their
701     * corresponding enum keys were declared in.
702     *
703     * @return a collection of the values contained in this {@code EnumMap}.
704     */
705    @Override
706    public Collection<V> values() {
707        if (valuesCollection == null) {
708            valuesCollection = new EnumMapValueCollection<K, V>(this);
709        }
710        return valuesCollection;
711    }
712
713    @SuppressWarnings("unchecked")
714    private void readObject(ObjectInputStream stream) throws IOException,
715            ClassNotFoundException {
716        stream.defaultReadObject();
717        initialization(keyType);
718        int elementCount = stream.readInt();
719        Enum<K> enumKey;
720        Object value;
721        for (int i = elementCount; i > 0; i--) {
722            enumKey = (Enum<K>) stream.readObject();
723            value = stream.readObject();
724            putImpl((K) enumKey, (V) value);
725        }
726    }
727
728    private void writeObject(ObjectOutputStream stream) throws IOException {
729        stream.defaultWriteObject();
730        stream.writeInt(mappingsCount);
731        Iterator<Map.Entry<K, V>> iterator = entrySet().iterator();
732        while (iterator.hasNext()) {
733            Map.Entry<K, V> entry = iterator.next();
734            stream.writeObject(entry.getKey());
735            stream.writeObject(entry.getValue());
736        }
737    }
738
739    private boolean isValidKeyType(Object key) {
740        if (key != null && keyType.isInstance(key)) {
741            return true;
742        }
743        return false;
744    }
745
746    @SuppressWarnings("unchecked")
747    private void initialization(EnumMap enumMap) {
748        keyType = enumMap.keyType;
749        keys = enumMap.keys;
750        enumSize = enumMap.enumSize;
751        values = enumMap.values.clone();
752        hasMapping = enumMap.hasMapping.clone();
753        mappingsCount = enumMap.mappingsCount;
754    }
755
756    private void initialization(Class<K> type) {
757        keyType = type;
758        keys = Enum.getSharedConstants(keyType);
759        enumSize = keys.length;
760        values = new Object[enumSize];
761        hasMapping = new boolean[enumSize];
762    }
763
764    @SuppressWarnings("unchecked")
765    private void putAllImpl(Map map) {
766        Iterator iter = map.entrySet().iterator();
767        while (iter.hasNext()) {
768            Map.Entry entry = (Map.Entry) iter.next();
769            putImpl((K) entry.getKey(), (V) entry.getValue());
770        }
771    }
772
773    @SuppressWarnings("unchecked")
774    private V putImpl(K key, V value) {
775        if (key == null) {
776            throw new NullPointerException("key == null");
777        }
778        keyType.cast(key); // Called to throw ClassCastException.
779        int keyOrdinal = key.ordinal();
780        if (!hasMapping[keyOrdinal]) {
781            hasMapping[keyOrdinal] = true;
782            mappingsCount++;
783        }
784        V oldValue = (V) values[keyOrdinal];
785        values[keyOrdinal] = value;
786        return oldValue;
787    }
788
789}
790