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