Path: blob/aarch64-shenandoah-jdk8u272-b10/jdk/src/share/classes/java/util/EnumMap.java
38829 views
/*1* Copyright (c) 2003, 2012, Oracle and/or its affiliates. All rights reserved.2* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.3*4* This code is free software; you can redistribute it and/or modify it5* under the terms of the GNU General Public License version 2 only, as6* published by the Free Software Foundation. Oracle designates this7* particular file as subject to the "Classpath" exception as provided8* by Oracle in the LICENSE file that accompanied this code.9*10* This code is distributed in the hope that it will be useful, but WITHOUT11* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or12* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License13* version 2 for more details (a copy is included in the LICENSE file that14* accompanied this code).15*16* You should have received a copy of the GNU General Public License version17* 2 along with this work; if not, write to the Free Software Foundation,18* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.19*20* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA21* or visit www.oracle.com if you need additional information or have any22* questions.23*/2425package java.util;2627import java.util.Map.Entry;28import sun.misc.SharedSecrets;2930/**31* A specialized {@link Map} implementation for use with enum type keys. All32* of the keys in an enum map must come from a single enum type that is33* specified, explicitly or implicitly, when the map is created. Enum maps34* are represented internally as arrays. This representation is extremely35* compact and efficient.36*37* <p>Enum maps are maintained in the <i>natural order</i> of their keys38* (the order in which the enum constants are declared). This is reflected39* 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 may44* or may not show the effects of any modifications to the map that occur while45* the iteration is in progress.46*47* <p>Null keys are not permitted. Attempts to insert a null key will48* throw {@link NullPointerException}. Attempts to test for the49* presence of a null key or to remove one will, however, function properly.50* Null values are permitted.5152* <P>Like most collection implementations <tt>EnumMap</tt> is not53* synchronized. If multiple threads access an enum map concurrently, and at54* least one of the threads modifies the map, it should be synchronized55* externally. This is typically accomplished by synchronizing on some56* 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 accidental59* unsynchronized access:60*61* <pre>62* Map<EnumKey, V> m63* = 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 their68* {@link HashMap} counterparts.69*70* <p>This class is a member of the71* <a href="{@docRoot}/../technotes/guides/collections/index.html">72* Java Collections Framework</a>.73*74* @author Josh Bloch75* @see EnumSet76* @since 1.577*/78public class EnumMap<K extends Enum<K>, V> extends AbstractMap<K, V>79implements java.io.Serializable, Cloneable80{81/**82* The <tt>Class</tt> object for the enum type of all the keys of this map.83*84* @serial85*/86private final Class<K> keyType;8788/**89* All of the values comprising K. (Cached for performance.)90*/91private transient K[] keyUniverse;9293/**94* Array representation of this map. The ith element is the value95* to which universe[i] is currently mapped, or null if it isn't96* mapped to anything, or NULL if it's mapped to null.97*/98private transient Object[] vals;99100/**101* The number of mappings in this map.102*/103private transient int size = 0;104105/**106* Distinguished non-null value for representing null values.107*/108private static final Object NULL = new Object() {109public int hashCode() {110return 0;111}112113public String toString() {114return "java.util.EnumMap.NULL";115}116};117118private Object maskNull(Object value) {119return (value == null ? NULL : value);120}121122@SuppressWarnings("unchecked")123private V unmaskNull(Object value) {124return (V)(value == NULL ? null : value);125}126127private static final Enum<?>[] ZERO_LENGTH_ENUM_ARRAY = new Enum<?>[0];128129/**130* Creates an empty enum map with the specified key type.131*132* @param keyType the class object of the key type for this enum map133* @throws NullPointerException if <tt>keyType</tt> is null134*/135public EnumMap(Class<K> keyType) {136this.keyType = keyType;137keyUniverse = getKeyUniverse(keyType);138vals = new Object[keyUniverse.length];139}140141/**142* Creates an enum map with the same key type as the specified enum143* map, initially containing the same mappings (if any).144*145* @param m the enum map from which to initialize this enum map146* @throws NullPointerException if <tt>m</tt> is null147*/148public EnumMap(EnumMap<K, ? extends V> m) {149keyType = m.keyType;150keyUniverse = m.keyUniverse;151vals = m.vals.clone();152size = m.size;153}154155/**156* Creates an enum map initialized from the specified map. If the157* specified map is an <tt>EnumMap</tt> instance, this constructor behaves158* identically to {@link #EnumMap(EnumMap)}. Otherwise, the specified map159* must contain at least one mapping (in order to determine the new160* enum map's key type).161*162* @param m the map from which to initialize this enum map163* @throws IllegalArgumentException if <tt>m</tt> is not an164* <tt>EnumMap</tt> instance and contains no mappings165* @throws NullPointerException if <tt>m</tt> is null166*/167public EnumMap(Map<K, ? extends V> m) {168if (m instanceof EnumMap) {169EnumMap<K, ? extends V> em = (EnumMap<K, ? extends V>) m;170keyType = em.keyType;171keyUniverse = em.keyUniverse;172vals = em.vals.clone();173size = em.size;174} else {175if (m.isEmpty())176throw new IllegalArgumentException("Specified map is empty");177keyType = m.keySet().iterator().next().getDeclaringClass();178keyUniverse = getKeyUniverse(keyType);179vals = new Object[keyUniverse.length];180putAll(m);181}182}183184// Query Operations185186/**187* Returns the number of key-value mappings in this map.188*189* @return the number of key-value mappings in this map190*/191public int size() {192return size;193}194195/**196* Returns <tt>true</tt> if this map maps one or more keys to the197* specified value.198*199* @param value the value whose presence in this map is to be tested200* @return <tt>true</tt> if this map maps one or more keys to this value201*/202public boolean containsValue(Object value) {203value = maskNull(value);204205for (Object val : vals)206if (value.equals(val))207return true;208209return false;210}211212/**213* Returns <tt>true</tt> if this map contains a mapping for the specified214* key.215*216* @param key the key whose presence in this map is to be tested217* @return <tt>true</tt> if this map contains a mapping for the specified218* key219*/220public boolean containsKey(Object key) {221return isValidKey(key) && vals[((Enum<?>)key).ordinal()] != null;222}223224private boolean containsMapping(Object key, Object value) {225return isValidKey(key) &&226maskNull(value).equals(vals[((Enum<?>)key).ordinal()]);227}228229/**230* Returns the value to which the specified key is mapped,231* or {@code null} if this map contains no mapping for the key.232*233* <p>More formally, if this map contains a mapping from a key234* {@code k} to a value {@code v} such that {@code (key == k)},235* then this method returns {@code v}; otherwise it returns236* {@code null}. (There can be at most one such mapping.)237*238* <p>A return value of {@code null} does not <i>necessarily</i>239* indicate that the map contains no mapping for the key; it's also240* possible that the map explicitly maps the key to {@code null}.241* The {@link #containsKey containsKey} operation may be used to242* distinguish these two cases.243*/244public V get(Object key) {245return (isValidKey(key) ?246unmaskNull(vals[((Enum<?>)key).ordinal()]) : null);247}248249// Modification Operations250251/**252* Associates the specified value with the specified key in this map.253* If the map previously contained a mapping for this key, the old254* value is replaced.255*256* @param key the key with which the specified value is to be associated257* @param value the value to be associated with the specified key258*259* @return the previous value associated with specified key, or260* <tt>null</tt> if there was no mapping for key. (A <tt>null</tt>261* return can also indicate that the map previously associated262* <tt>null</tt> with the specified key.)263* @throws NullPointerException if the specified key is null264*/265public V put(K key, V value) {266typeCheck(key);267268int index = key.ordinal();269Object oldValue = vals[index];270vals[index] = maskNull(value);271if (oldValue == null)272size++;273return unmaskNull(oldValue);274}275276/**277* Removes the mapping for this key from this map if present.278*279* @param key the key whose mapping is to be removed from the map280* @return the previous value associated with specified key, or281* <tt>null</tt> if there was no entry for key. (A <tt>null</tt>282* return can also indicate that the map previously associated283* <tt>null</tt> with the specified key.)284*/285public V remove(Object key) {286if (!isValidKey(key))287return null;288int index = ((Enum<?>)key).ordinal();289Object oldValue = vals[index];290vals[index] = null;291if (oldValue != null)292size--;293return unmaskNull(oldValue);294}295296private boolean removeMapping(Object key, Object value) {297if (!isValidKey(key))298return false;299int index = ((Enum<?>)key).ordinal();300if (maskNull(value).equals(vals[index])) {301vals[index] = null;302size--;303return true;304}305return false;306}307308/**309* Returns true if key is of the proper type to be a key in this310* enum map.311*/312private boolean isValidKey(Object key) {313if (key == null)314return false;315316// Cheaper than instanceof Enum followed by getDeclaringClass317Class<?> keyClass = key.getClass();318return keyClass == keyType || keyClass.getSuperclass() == keyType;319}320321// Bulk Operations322323/**324* Copies all of the mappings from the specified map to this map.325* These mappings will replace any mappings that this map had for326* any of the keys currently in the specified map.327*328* @param m the mappings to be stored in this map329* @throws NullPointerException the specified map is null, or if330* one or more keys in the specified map are null331*/332public void putAll(Map<? extends K, ? extends V> m) {333if (m instanceof EnumMap) {334EnumMap<?, ?> em = (EnumMap<?, ?>)m;335if (em.keyType != keyType) {336if (em.isEmpty())337return;338throw new ClassCastException(em.keyType + " != " + keyType);339}340341for (int i = 0; i < keyUniverse.length; i++) {342Object emValue = em.vals[i];343if (emValue != null) {344if (vals[i] == null)345size++;346vals[i] = emValue;347}348}349} else {350super.putAll(m);351}352}353354/**355* Removes all mappings from this map.356*/357public void clear() {358Arrays.fill(vals, null);359size = 0;360}361362// Views363364/**365* This field is initialized to contain an instance of the entry set366* view the first time this view is requested. The view is stateless,367* so there's no reason to create more than one.368*/369private transient Set<Map.Entry<K,V>> entrySet;370371/**372* Returns a {@link Set} view of the keys contained in this map.373* The returned set obeys the general contract outlined in374* {@link Map#keySet()}. The set's iterator will return the keys375* in their natural order (the order in which the enum constants376* are declared).377*378* @return a set view of the keys contained in this enum map379*/380public Set<K> keySet() {381Set<K> ks = keySet;382if (ks == null) {383ks = new KeySet();384keySet = ks;385}386return ks;387}388389private class KeySet extends AbstractSet<K> {390public Iterator<K> iterator() {391return new KeyIterator();392}393public int size() {394return size;395}396public boolean contains(Object o) {397return containsKey(o);398}399public boolean remove(Object o) {400int oldSize = size;401EnumMap.this.remove(o);402return size != oldSize;403}404public void clear() {405EnumMap.this.clear();406}407}408409/**410* Returns a {@link Collection} view of the values contained in this map.411* The returned collection obeys the general contract outlined in412* {@link Map#values()}. The collection's iterator will return the413* values in the order their corresponding keys appear in map,414* which is their natural order (the order in which the enum constants415* are declared).416*417* @return a collection view of the values contained in this map418*/419public Collection<V> values() {420Collection<V> vs = values;421if (vs == null) {422vs = new Values();423values = vs;424}425return vs;426}427428private class Values extends AbstractCollection<V> {429public Iterator<V> iterator() {430return new ValueIterator();431}432public int size() {433return size;434}435public boolean contains(Object o) {436return containsValue(o);437}438public boolean remove(Object o) {439o = maskNull(o);440441for (int i = 0; i < vals.length; i++) {442if (o.equals(vals[i])) {443vals[i] = null;444size--;445return true;446}447}448return false;449}450public void clear() {451EnumMap.this.clear();452}453}454455/**456* Returns a {@link Set} view of the mappings contained in this map.457* The returned set obeys the general contract outlined in458* {@link Map#keySet()}. The set's iterator will return the459* mappings in the order their keys appear in map, which is their460* natural order (the order in which the enum constants are declared).461*462* @return a set view of the mappings contained in this enum map463*/464public Set<Map.Entry<K,V>> entrySet() {465Set<Map.Entry<K,V>> es = entrySet;466if (es != null)467return es;468else469return entrySet = new EntrySet();470}471472private class EntrySet extends AbstractSet<Map.Entry<K,V>> {473public Iterator<Map.Entry<K,V>> iterator() {474return new EntryIterator();475}476477public boolean contains(Object o) {478if (!(o instanceof Map.Entry))479return false;480Map.Entry<?,?> entry = (Map.Entry<?,?>)o;481return containsMapping(entry.getKey(), entry.getValue());482}483public boolean remove(Object o) {484if (!(o instanceof Map.Entry))485return false;486Map.Entry<?,?> entry = (Map.Entry<?,?>)o;487return removeMapping(entry.getKey(), entry.getValue());488}489public int size() {490return size;491}492public void clear() {493EnumMap.this.clear();494}495public Object[] toArray() {496return fillEntryArray(new Object[size]);497}498@SuppressWarnings("unchecked")499public <T> T[] toArray(T[] a) {500int size = size();501if (a.length < size)502a = (T[])java.lang.reflect.Array503.newInstance(a.getClass().getComponentType(), size);504if (a.length > size)505a[size] = null;506return (T[]) fillEntryArray(a);507}508private Object[] fillEntryArray(Object[] a) {509int j = 0;510for (int i = 0; i < vals.length; i++)511if (vals[i] != null)512a[j++] = new AbstractMap.SimpleEntry<>(513keyUniverse[i], unmaskNull(vals[i]));514return a;515}516}517518private abstract class EnumMapIterator<T> implements Iterator<T> {519// Lower bound on index of next element to return520int index = 0;521522// Index of last returned element, or -1 if none523int lastReturnedIndex = -1;524525public boolean hasNext() {526while (index < vals.length && vals[index] == null)527index++;528return index != vals.length;529}530531public void remove() {532checkLastReturnedIndex();533534if (vals[lastReturnedIndex] != null) {535vals[lastReturnedIndex] = null;536size--;537}538lastReturnedIndex = -1;539}540541private void checkLastReturnedIndex() {542if (lastReturnedIndex < 0)543throw new IllegalStateException();544}545}546547private class KeyIterator extends EnumMapIterator<K> {548public K next() {549if (!hasNext())550throw new NoSuchElementException();551lastReturnedIndex = index++;552return keyUniverse[lastReturnedIndex];553}554}555556private class ValueIterator extends EnumMapIterator<V> {557public V next() {558if (!hasNext())559throw new NoSuchElementException();560lastReturnedIndex = index++;561return unmaskNull(vals[lastReturnedIndex]);562}563}564565private class EntryIterator extends EnumMapIterator<Map.Entry<K,V>> {566private Entry lastReturnedEntry;567568public Map.Entry<K,V> next() {569if (!hasNext())570throw new NoSuchElementException();571lastReturnedEntry = new Entry(index++);572return lastReturnedEntry;573}574575public void remove() {576lastReturnedIndex =577((null == lastReturnedEntry) ? -1 : lastReturnedEntry.index);578super.remove();579lastReturnedEntry.index = lastReturnedIndex;580lastReturnedEntry = null;581}582583private class Entry implements Map.Entry<K,V> {584private int index;585586private Entry(int index) {587this.index = index;588}589590public K getKey() {591checkIndexForEntryUse();592return keyUniverse[index];593}594595public V getValue() {596checkIndexForEntryUse();597return unmaskNull(vals[index]);598}599600public V setValue(V value) {601checkIndexForEntryUse();602V oldValue = unmaskNull(vals[index]);603vals[index] = maskNull(value);604return oldValue;605}606607public boolean equals(Object o) {608if (index < 0)609return o == this;610611if (!(o instanceof Map.Entry))612return false;613614Map.Entry<?,?> e = (Map.Entry<?,?>)o;615V ourValue = unmaskNull(vals[index]);616Object hisValue = e.getValue();617return (e.getKey() == keyUniverse[index] &&618(ourValue == hisValue ||619(ourValue != null && ourValue.equals(hisValue))));620}621622public int hashCode() {623if (index < 0)624return super.hashCode();625626return entryHashCode(index);627}628629public String toString() {630if (index < 0)631return super.toString();632633return keyUniverse[index] + "="634+ unmaskNull(vals[index]);635}636637private void checkIndexForEntryUse() {638if (index < 0)639throw new IllegalStateException("Entry was removed");640}641}642}643644// Comparison and hashing645646/**647* Compares the specified object with this map for equality. Returns648* <tt>true</tt> if the given object is also a map and the two maps649* represent the same mappings, as specified in the {@link650* Map#equals(Object)} contract.651*652* @param o the object to be compared for equality with this map653* @return <tt>true</tt> if the specified object is equal to this map654*/655public boolean equals(Object o) {656if (this == o)657return true;658if (o instanceof EnumMap)659return equals((EnumMap<?,?>)o);660if (!(o instanceof Map))661return false;662663Map<?,?> m = (Map<?,?>)o;664if (size != m.size())665return false;666667for (int i = 0; i < keyUniverse.length; i++) {668if (null != vals[i]) {669K key = keyUniverse[i];670V value = unmaskNull(vals[i]);671if (null == value) {672if (!((null == m.get(key)) && m.containsKey(key)))673return false;674} else {675if (!value.equals(m.get(key)))676return false;677}678}679}680681return true;682}683684private boolean equals(EnumMap<?,?> em) {685if (em.keyType != keyType)686return size == 0 && em.size == 0;687688// Key types match, compare each value689for (int i = 0; i < keyUniverse.length; i++) {690Object ourValue = vals[i];691Object hisValue = em.vals[i];692if (hisValue != ourValue &&693(hisValue == null || !hisValue.equals(ourValue)))694return false;695}696return true;697}698699/**700* Returns the hash code value for this map. The hash code of a map is701* defined to be the sum of the hash codes of each entry in the map.702*/703public int hashCode() {704int h = 0;705706for (int i = 0; i < keyUniverse.length; i++) {707if (null != vals[i]) {708h += entryHashCode(i);709}710}711712return h;713}714715private int entryHashCode(int index) {716return (keyUniverse[index].hashCode() ^ vals[index].hashCode());717}718719/**720* Returns a shallow copy of this enum map. (The values themselves721* are not cloned.722*723* @return a shallow copy of this enum map724*/725@SuppressWarnings("unchecked")726public EnumMap<K, V> clone() {727EnumMap<K, V> result = null;728try {729result = (EnumMap<K, V>) super.clone();730} catch(CloneNotSupportedException e) {731throw new AssertionError();732}733result.vals = result.vals.clone();734result.entrySet = null;735return result;736}737738/**739* Throws an exception if e is not of the correct type for this enum set.740*/741private void typeCheck(K key) {742Class<?> keyClass = key.getClass();743if (keyClass != keyType && keyClass.getSuperclass() != keyType)744throw new ClassCastException(keyClass + " != " + keyType);745}746747/**748* Returns all of the values comprising K.749* The result is uncloned, cached, and shared by all callers.750*/751private static <K extends Enum<K>> K[] getKeyUniverse(Class<K> keyType) {752return SharedSecrets.getJavaLangAccess()753.getEnumConstantsShared(keyType);754}755756private static final long serialVersionUID = 458661240069192865L;757758/**759* Save the state of the <tt>EnumMap</tt> instance to a stream (i.e.,760* serialize it).761*762* @serialData The <i>size</i> of the enum map (the number of key-value763* mappings) is emitted (int), followed by the key (Object)764* and value (Object) for each key-value mapping represented765* by the enum map.766*/767private void writeObject(java.io.ObjectOutputStream s)768throws java.io.IOException769{770// Write out the key type and any hidden stuff771s.defaultWriteObject();772773// Write out size (number of Mappings)774s.writeInt(size);775776// Write out keys and values (alternating)777int entriesToBeWritten = size;778for (int i = 0; entriesToBeWritten > 0; i++) {779if (null != vals[i]) {780s.writeObject(keyUniverse[i]);781s.writeObject(unmaskNull(vals[i]));782entriesToBeWritten--;783}784}785}786787/**788* Reconstitute the <tt>EnumMap</tt> instance from a stream (i.e.,789* deserialize it).790*/791@SuppressWarnings("unchecked")792private void readObject(java.io.ObjectInputStream s)793throws java.io.IOException, ClassNotFoundException794{795// Read in the key type and any hidden stuff796s.defaultReadObject();797798keyUniverse = getKeyUniverse(keyType);799vals = new Object[keyUniverse.length];800801// Read in size (number of Mappings)802int size = s.readInt();803804// Read the keys and values, and put the mappings in the HashMap805for (int i = 0; i < size; i++) {806K key = (K) s.readObject();807V value = (V) s.readObject();808put(key, value);809}810}811}812813814