1 /*
2 * Copyright (c) 2003, 2018, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation. Oracle designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Oracle in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22 * or visit www.oracle.com if you need additional information or have any
23 * questions.
24 */
25
26 package java.util;
27
28 import jdk.internal.misc.SharedSecrets;
29
30 /**
31 * A specialized {@link Map} implementation for use with enum type keys. All
32 * of the keys in an enum map must come from a single enum type that is
33 * specified, explicitly or implicitly, when the map is created. Enum maps
34 * are represented internally as arrays. This representation is extremely
35 * compact and efficient.
36 *
37 * <p>Enum maps are maintained in the <i>natural order</i> of their keys
38 * (the order in which the enum constants are declared). This is reflected
39 * in the iterators returned by the collections views ({@link #keySet()},
40 * {@link #entrySet()}, and {@link #values()}).
41 *
42 * <p>Iterators returned by the collection views are <i>weakly consistent</i>:
43 * they will never throw {@link ConcurrentModificationException} and they may
44 * or may not show the effects of any modifications to the map that occur while
45 * the iteration is in progress.
46 *
47 * <p>Null keys are not permitted. Attempts to insert a null key will
48 * throw {@link NullPointerException}. Attempts to test for the
49 * presence of a null key or to remove one will, however, function properly.
50 * Null values are permitted.
51
52 * <P>Like most collection implementations {@code EnumMap} is not
53 * synchronized. If multiple threads access an enum map concurrently, and at
54 * least one of the threads modifies the map, it should be synchronized
55 * externally. This is typically accomplished by synchronizing on some
56 * object that naturally encapsulates the enum map. If no such object exists,
57 * the map should be "wrapped" using the {@link Collections#synchronizedMap}
58 * method. This is best done at creation time, to prevent accidental
59 * unsynchronized access:
60 *
61 * <pre>
62 * Map<EnumKey, V> m
63 * = Collections.synchronizedMap(new EnumMap<EnumKey, V>(...));
64 * </pre>
65 *
66 * <p>Implementation note: All basic operations execute in constant time.
67 * They are likely (though not guaranteed) to be faster than their
68 * {@link HashMap} counterparts.
69 *
70 * <p>This class is a member of the
71 * <a href="{@docRoot}/java.base/java/util/package-summary.html#CollectionsFramework">
72 * Java Collections Framework</a>.
73 *
74 * @author Josh Bloch
75 * @see EnumSet
76 * @since 1.5
77 */
78 public class EnumMap<K extends Enum<K>, V> extends AbstractMap<K, V>
79 implements java.io.Serializable, Cloneable
80 {
81 /**
82 * The {@code Class} object for the enum type of all the keys of this map.
83 *
84 * @serial
85 */
86 private final Class<K> keyType;
87
88 /**
89 * All of the values comprising K. (Cached for performance.)
90 */
91 private transient K[] keyUniverse;
92
93 /**
94 * Array representation of this map. The ith element is the value
95 * to which universe[i] is currently mapped, or null if it isn't
96 * mapped to anything, or NULL if it's mapped to null.
97 */
98 private transient Object[] vals;
99
100 /**
101 * The number of mappings in this map.
102 */
103 private transient int size = 0;
104
105 /**
106 * Distinguished non-null value for representing null values.
107 */
108 private static final Object NULL = new Object() {
109 public int hashCode() {
110 return 0;
111 }
112
113 public String toString() {
114 return "java.util.EnumMap.NULL";
115 }
116 };
117
118 private Object maskNull(Object value) {
119 return (value == null ? NULL : value);
120 }
121
122 @SuppressWarnings("unchecked")
123 private V unmaskNull(Object value) {
124 return (V)(value == NULL ? null : value);
125 }
126
127 /**
128 * Creates an empty enum map with the specified key type.
129 *
130 * @param keyType the class object of the key type for this enum map
131 * @throws NullPointerException if {@code keyType} is null
132 */
133 public EnumMap(Class<K> keyType) {
134 this.keyType = keyType;
135 keyUniverse = getKeyUniverse(keyType);
136 vals = new Object[keyUniverse.length];
137 }
138
139 /**
140 * Creates an enum map with the same key type as the specified enum
141 * map, initially containing the same mappings (if any).
142 *
143 * @param m the enum map from which to initialize this enum map
144 * @throws NullPointerException if {@code m} is null
145 */
146 public EnumMap(EnumMap<K, ? extends V> m) {
147 keyType = m.keyType;
148 keyUniverse = m.keyUniverse;
149 vals = m.vals.clone();
150 size = m.size;
151 }
152
153 /**
154 * Creates an enum map initialized from the specified map. If the
155 * specified map is an {@code EnumMap} instance, this constructor behaves
156 * identically to {@link #EnumMap(EnumMap)}. Otherwise, the specified map
157 * must contain at least one mapping (in order to determine the new
158 * enum map's key type).
159 *
160 * @param m the map from which to initialize this enum map
161 * @throws IllegalArgumentException if {@code m} is not an
162 * {@code EnumMap} instance and contains no mappings
163 * @throws NullPointerException if {@code m} is null
164 */
165 public EnumMap(Map<K, ? extends V> m) {
166 if (m instanceof EnumMap) {
167 EnumMap<K, ? extends V> em = (EnumMap<K, ? extends V>) m;
168 keyType = em.keyType;
169 keyUniverse = em.keyUniverse;
170 vals = em.vals.clone();
171 size = em.size;
172 } else {
173 if (m.isEmpty())
174 throw new IllegalArgumentException("Specified map is empty");
175 keyType = m.keySet().iterator().next().getDeclaringClass();
176 keyUniverse = getKeyUniverse(keyType);
177 vals = new Object[keyUniverse.length];
178 putAll(m);
179 }
180 }
181
182 // Query Operations
183
184 /**
185 * Returns the number of key-value mappings in this map.
186 *
187 * @return the number of key-value mappings in this map
188 */
189 public int size() {
190 return size;
191 }
192
193 /**
194 * Returns {@code true} if this map maps one or more keys to the
195 * specified value.
196 *
197 * @param value the value whose presence in this map is to be tested
198 * @return {@code true} if this map maps one or more keys to this value
199 */
200 public boolean containsValue(Object value) {
201 value = maskNull(value);
202
203 for (Object val : vals)
204 if (value.equals(val))
205 return true;
206
207 return false;
208 }
209
210 /**
211 * Returns {@code true} if this map contains a mapping for the specified
212 * key.
213 *
214 * @param key the key whose presence in this map is to be tested
215 * @return {@code true} if this map contains a mapping for the specified
216 * key
217 */
218 public boolean containsKey(Object key) {
219 return isValidKey(key) && vals[((Enum<?>)key).ordinal()] != null;
220 }
221
222 private boolean containsMapping(Object key, Object value) {
223 return isValidKey(key) &&
224 maskNull(value).equals(vals[((Enum<?>)key).ordinal()]);
225 }
226
227 /**
228 * Returns the value to which the specified key is mapped,
229 * or {@code null} if this map contains no mapping for the key.
230 *
231 * <p>More formally, if this map contains a mapping from a key
232 * {@code k} to a value {@code v} such that {@code (key == k)},
233 * then this method returns {@code v}; otherwise it returns
234 * {@code null}. (There can be at most one such mapping.)
235 *
236 * <p>A return value of {@code null} does not <i>necessarily</i>
237 * indicate that the map contains no mapping for the key; it's also
238 * possible that the map explicitly maps the key to {@code null}.
239 * The {@link #containsKey containsKey} operation may be used to
240 * distinguish these two cases.
241 */
242 public V get(Object key) {
243 return (isValidKey(key) ?
244 unmaskNull(vals[((Enum<?>)key).ordinal()]) : null);
245 }
246
247 // Modification Operations
248
249 /**
250 * Associates the specified value with the specified key in this map.
251 * If the map previously contained a mapping for this key, the old
252 * value is replaced.
253 *
254 * @param key the key with which the specified value is to be associated
255 * @param value the value to be associated with the specified key
256 *
257 * @return the previous value associated with specified key, or
258 * {@code null} if there was no mapping for key. (A {@code null}
259 * return can also indicate that the map previously associated
260 * {@code null} with the specified key.)
261 * @throws NullPointerException if the specified key is null
262 */
263 public V put(K key, V value) {
264 typeCheck(key);
265
266 int index = key.ordinal();
267 Object oldValue = vals[index];
268 vals[index] = maskNull(value);
269 if (oldValue == null)
270 size++;
271 return unmaskNull(oldValue);
272 }
273
274 /**
275 * Removes the mapping for this key from this map if present.
276 *
277 * @param key the key whose mapping is to be removed from the map
278 * @return the previous value associated with specified key, or
279 * {@code null} if there was no entry for key. (A {@code null}
280 * return can also indicate that the map previously associated
281 * {@code null} with the specified key.)
282 */
283 public V remove(Object key) {
284 if (!isValidKey(key))
285 return null;
286 int index = ((Enum<?>)key).ordinal();
287 Object oldValue = vals[index];
288 vals[index] = null;
289 if (oldValue != null)
290 size--;
291 return unmaskNull(oldValue);
292 }
293
294 private boolean removeMapping(Object key, Object value) {
295 if (!isValidKey(key))
296 return false;
297 int index = ((Enum<?>)key).ordinal();
298 if (maskNull(value).equals(vals[index])) {
299 vals[index] = null;
300 size--;
301 return true;
302 }
303 return false;
304 }
305
306 /**
307 * Returns true if key is of the proper type to be a key in this
308 * enum map.
309 */
310 private boolean isValidKey(Object key) {
311 if (key == null)
312 return false;
313
314 // Cheaper than instanceof Enum followed by getDeclaringClass
315 Class<?> keyClass = key.getClass();
316 return keyClass == keyType || keyClass.getSuperclass() == keyType;
317 }
318
319 // Bulk Operations
320
321 /**
322 * Copies all of the mappings from the specified map to this map.
323 * These mappings will replace any mappings that this map had for
324 * any of the keys currently in the specified map.
325 *
326 * @param m the mappings to be stored in this map
327 * @throws NullPointerException the specified map is null, or if
328 * one or more keys in the specified map are null
329 */
330 public void putAll(Map<? extends K, ? extends V> m) {
331 if (m instanceof EnumMap) {
332 EnumMap<?, ?> em = (EnumMap<?, ?>)m;
333 if (em.keyType != keyType) {
334 if (em.isEmpty())
335 return;
336 throw new ClassCastException(em.keyType + " != " + keyType);
337 }
338
339 for (int i = 0; i < keyUniverse.length; i++) {
340 Object emValue = em.vals[i];
341 if (emValue != null) {
342 if (vals[i] == null)
343 size++;
344 vals[i] = emValue;
345 }
346 }
347 } else {
348 super.putAll(m);
349 }
350 }
351
352 /**
353 * Removes all mappings from this map.
354 */
355 public void clear() {
356 Arrays.fill(vals, null);
357 size = 0;
358 }
359
360 // Views
361
362 /**
363 * This field is initialized to contain an instance of the entry set
364 * view the first time this view is requested. The view is stateless,
365 * so there's no reason to create more than one.
366 */
367 private transient Set<Map.Entry<K,V>> entrySet;
368
369 /**
370 * Returns a {@link Set} view of the keys contained in this map.
371 * The returned set obeys the general contract outlined in
372 * {@link Map#keySet()}. The set's iterator will return the keys
373 * in their natural order (the order in which the enum constants
374 * are declared).
375 *
376 * @return a set view of the keys contained in this enum map
377 */
378 public Set<K> keySet() {
379 Set<K> ks = keySet;
380 if (ks == null) {
381 ks = new KeySet();
382 keySet = ks;
383 }
384 return ks;
385 }
386
387 private class KeySet extends AbstractSet<K> {
388 public Iterator<K> iterator() {
389 return new KeyIterator();
390 }
391 public int size() {
392 return size;
393 }
394 public boolean contains(Object o) {
395 return containsKey(o);
396 }
397 public boolean remove(Object o) {
398 int oldSize = size;
399 EnumMap.this.remove(o);
400 return size != oldSize;
401 }
402 public void clear() {
403 EnumMap.this.clear();
404 }
405 }
406
407 /**
408 * Returns a {@link Collection} view of the values contained in this map.
409 * The returned collection obeys the general contract outlined in
410 * {@link Map#values()}. The collection's iterator will return the
411 * values in the order their corresponding keys appear in map,
412 * which is their natural order (the order in which the enum constants
413 * are declared).
414 *
415 * @return a collection view of the values contained in this map
416 */
417 public Collection<V> values() {
418 Collection<V> vs = values;
419 if (vs == null) {
420 vs = new Values();
421 values = vs;
422 }
423 return vs;
424 }
425
426 private class Values extends AbstractCollection<V> {
427 public Iterator<V> iterator() {
428 return new ValueIterator();
429 }
430 public int size() {
431 return size;
432 }
433 public boolean contains(Object o) {
434 return containsValue(o);
435 }
436 public boolean remove(Object o) {
437 o = maskNull(o);
438
439 for (int i = 0; i < vals.length; i++) {
440 if (o.equals(vals[i])) {
441 vals[i] = null;
442 size--;
443 return true;
444 }
445 }
446 return false;
447 }
448 public void clear() {
449 EnumMap.this.clear();
450 }
451 }
452
453 /**
454 * Returns a {@link Set} view of the mappings contained in this map.
455 * The returned set obeys the general contract outlined in
456 * {@link Map#keySet()}. The set's iterator will return the
457 * mappings in the order their keys appear in map, which is their
458 * natural order (the order in which the enum constants are declared).
459 *
460 * @return a set view of the mappings contained in this enum map
461 */
462 public Set<Map.Entry<K,V>> entrySet() {
463 Set<Map.Entry<K,V>> es = entrySet;
464 if (es != null)
465 return es;
466 else
467 return entrySet = new EntrySet();
468 }
469
470 private class EntrySet extends AbstractSet<Map.Entry<K,V>> {
471 public Iterator<Map.Entry<K,V>> iterator() {
472 return new EntryIterator();
473 }
474
475 public boolean contains(Object o) {
476 if (!(o instanceof Map.Entry))
477 return false;
478 Map.Entry<?,?> entry = (Map.Entry<?,?>)o;
479 return containsMapping(entry.getKey(), entry.getValue());
480 }
481 public boolean remove(Object o) {
482 if (!(o instanceof Map.Entry))
483 return false;
484 Map.Entry<?,?> entry = (Map.Entry<?,?>)o;
485 return removeMapping(entry.getKey(), entry.getValue());
486 }
487 public int size() {
488 return size;
489 }
490 public void clear() {
491 EnumMap.this.clear();
492 }
493 public Object[] toArray() {
494 return fillEntryArray(new Object[size]);
495 }
496 @SuppressWarnings("unchecked")
497 public <T> T[] toArray(T[] a) {
498 int size = size();
499 if (a.length < size)
500 a = (T[])java.lang.reflect.Array
501 .newInstance(a.getClass().getComponentType(), size);
502 if (a.length > size)
503 a[size] = null;
504 return (T[]) fillEntryArray(a);
505 }
506 private Object[] fillEntryArray(Object[] a) {
507 int j = 0;
508 for (int i = 0; i < vals.length; i++)
509 if (vals[i] != null)
510 a[j++] = new AbstractMap.SimpleEntry<>(
511 keyUniverse[i], unmaskNull(vals[i]));
512 return a;
513 }
514 }
515
516 private abstract class EnumMapIterator<T> implements Iterator<T> {
517 // Lower bound on index of next element to return
518 int index = 0;
519
520 // Index of last returned element, or -1 if none
521 int lastReturnedIndex = -1;
522
523 public boolean hasNext() {
524 while (index < vals.length && vals[index] == null)
525 index++;
526 return index != vals.length;
527 }
528
529 public void remove() {
530 checkLastReturnedIndex();
531
532 if (vals[lastReturnedIndex] != null) {
533 vals[lastReturnedIndex] = null;
534 size--;
535 }
536 lastReturnedIndex = -1;
537 }
538
539 private void checkLastReturnedIndex() {
540 if (lastReturnedIndex < 0)
541 throw new IllegalStateException();
542 }
543 }
544
545 private class KeyIterator extends EnumMapIterator<K> {
546 public K next() {
547 if (!hasNext())
548 throw new NoSuchElementException();
549 lastReturnedIndex = index++;
550 return keyUniverse[lastReturnedIndex];
551 }
552 }
553
554 private class ValueIterator extends EnumMapIterator<V> {
555 public V next() {
556 if (!hasNext())
557 throw new NoSuchElementException();
558 lastReturnedIndex = index++;
559 return unmaskNull(vals[lastReturnedIndex]);
560 }
561 }
562
563 private class EntryIterator extends EnumMapIterator<Map.Entry<K,V>> {
564 private Entry lastReturnedEntry;
565
566 public Map.Entry<K,V> next() {
567 if (!hasNext())
568 throw new NoSuchElementException();
569 lastReturnedEntry = new Entry(index++);
570 return lastReturnedEntry;
571 }
572
573 public void remove() {
574 lastReturnedIndex =
575 ((null == lastReturnedEntry) ? -1 : lastReturnedEntry.index);
576 super.remove();
577 lastReturnedEntry.index = lastReturnedIndex;
578 lastReturnedEntry = null;
579 }
580
581 private class Entry implements Map.Entry<K,V> {
582 private int index;
583
584 private Entry(int index) {
585 this.index = index;
586 }
587
588 public K getKey() {
589 checkIndexForEntryUse();
590 return keyUniverse[index];
591 }
592
593 public V getValue() {
594 checkIndexForEntryUse();
595 return unmaskNull(vals[index]);
596 }
597
598 public V setValue(V value) {
599 checkIndexForEntryUse();
600 V oldValue = unmaskNull(vals[index]);
601 vals[index] = maskNull(value);
602 return oldValue;
603 }
604
605 public boolean equals(Object o) {
606 if (index < 0)
607 return o == this;
608
609 if (!(o instanceof Map.Entry))
610 return false;
611
612 Map.Entry<?,?> e = (Map.Entry<?,?>)o;
613 V ourValue = unmaskNull(vals[index]);
614 Object hisValue = e.getValue();
615 return (e.getKey() == keyUniverse[index] &&
616 (ourValue == hisValue ||
617 (ourValue != null && ourValue.equals(hisValue))));
618 }
619
620 public int hashCode() {
621 if (index < 0)
622 return super.hashCode();
623
624 return entryHashCode(index);
625 }
626
627 public String toString() {
628 if (index < 0)
629 return super.toString();
630
631 return keyUniverse[index] + "="
632 + unmaskNull(vals[index]);
633 }
634
635 private void checkIndexForEntryUse() {
636 if (index < 0)
637 throw new IllegalStateException("Entry was removed");
638 }
639 }
640 }
641
642 // Comparison and hashing
643
644 /**
645 * Compares the specified object with this map for equality. Returns
646 * {@code true} if the given object is also a map and the two maps
647 * represent the same mappings, as specified in the {@link
648 * Map#equals(Object)} contract.
649 *
650 * @param o the object to be compared for equality with this map
651 * @return {@code true} if the specified object is equal to this map
652 */
653 public boolean equals(Object o) {
654 if (this == o)
655 return true;
656 if (o instanceof EnumMap)
657 return equals((EnumMap<?,?>)o);
658 if (!(o instanceof Map))
659 return false;
660
661 Map<?,?> m = (Map<?,?>)o;
662 if (size != m.size())
663 return false;
664
665 for (int i = 0; i < keyUniverse.length; i++) {
666 if (null != vals[i]) {
667 K key = keyUniverse[i];
668 V value = unmaskNull(vals[i]);
669 if (null == value) {
670 if (!((null == m.get(key)) && m.containsKey(key)))
671 return false;
672 } else {
673 if (!value.equals(m.get(key)))
674 return false;
675 }
676 }
677 }
678
679 return true;
680 }
681
682 private boolean equals(EnumMap<?,?> em) {
683 if (em.size != size)
684 return false;
685
686 if (em.keyType != keyType)
687 return size == 0;
688
689 // Key types match, compare each value
690 for (int i = 0; i < keyUniverse.length; i++) {
691 Object ourValue = vals[i];
692 Object hisValue = em.vals[i];
693 if (hisValue != ourValue &&
694 (hisValue == null || !hisValue.equals(ourValue)))
695 return false;
696 }
697 return true;
698 }
699
700 /**
701 * Returns the hash code value for this map. The hash code of a map is
702 * defined to be the sum of the hash codes of each entry in the map.
703 */
704 public int hashCode() {
705 int h = 0;
706
707 for (int i = 0; i < keyUniverse.length; i++) {
708 if (null != vals[i]) {
709 h += entryHashCode(i);
710 }
711 }
712
713 return h;
714 }
715
716 private int entryHashCode(int index) {
717 return (keyUniverse[index].hashCode() ^ vals[index].hashCode());
718 }
719
720 /**
721 * Returns a shallow copy of this enum map. The values themselves
722 * are not cloned.
723 *
724 * @return a shallow copy of this enum map
725 */
726 @SuppressWarnings("unchecked")
727 public EnumMap<K, V> clone() {
728 EnumMap<K, V> result = null;
729 try {
730 result = (EnumMap<K, V>) super.clone();
731 } catch(CloneNotSupportedException e) {
732 throw new AssertionError();
733 }
734 result.vals = result.vals.clone();
735 result.entrySet = null;
736 return result;
737 }
738
739 /**
740 * Throws an exception if e is not of the correct type for this enum set.
741 */
742 private void typeCheck(K key) {
743 Class<?> keyClass = key.getClass();
744 if (keyClass != keyType && keyClass.getSuperclass() != keyType)
745 throw new ClassCastException(keyClass + " != " + keyType);
746 }
747
748 /**
749 * Returns all of the values comprising K.
750 * The result is uncloned, cached, and shared by all callers.
751 */
752 private static <K extends Enum<K>> K[] getKeyUniverse(Class<K> keyType) {
753 return SharedSecrets.getJavaLangAccess()
754 .getEnumConstantsShared(keyType);
755 }
756
757 private static final long serialVersionUID = 458661240069192865L;
758
759 /**
760 * Save the state of the {@code EnumMap} instance to a stream (i.e.,
761 * serialize it).
762 *
763 * @serialData The <i>size</i> of the enum map (the number of key-value
764 * mappings) is emitted (int), followed by the key (Object)
765 * and value (Object) for each key-value mapping represented
766 * by the enum map.
767 */
768 private void writeObject(java.io.ObjectOutputStream s)
769 throws java.io.IOException
770 {
771 // Write out the key type and any hidden stuff
772 s.defaultWriteObject();
773
774 // Write out size (number of Mappings)
775 s.writeInt(size);
776
777 // Write out keys and values (alternating)
778 int entriesToBeWritten = size;
779 for (int i = 0; entriesToBeWritten > 0; i++) {
780 if (null != vals[i]) {
781 s.writeObject(keyUniverse[i]);
782 s.writeObject(unmaskNull(vals[i]));
783 entriesToBeWritten--;
784 }
785 }
786 }
787
788 /**
789 * Reconstitute the {@code EnumMap} instance from a stream (i.e.,
790 * deserialize it).
791 */
792 @SuppressWarnings("unchecked")
793 private void readObject(java.io.ObjectInputStream s)
794 throws java.io.IOException, ClassNotFoundException
795 {
796 // Read in the key type and any hidden stuff
797 s.defaultReadObject();
798
799 keyUniverse = getKeyUniverse(keyType);
800 vals = new Object[keyUniverse.length];
801
802 // Read in size (number of Mappings)
803 int size = s.readInt();
804
805 // Read the keys and values, and put the mappings in the HashMap
806 for (int i = 0; i < size; i++) {
807 K key = (K) s.readObject();
808 V value = (V) s.readObject();
809 put(key, value);
810 }
811 }
812 }
813