/* * @(#)EnumMap.java 1.11 05/09/02 * * Copyright 2004 Sun Microsystems, Inc. All rights reserved. * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms. */ package java.util; import java.util.Map.Entry; /** * A specialized {@link Map} implementation for use with enum type keys. All * of the keys in an enum map must come from a single enum type that is * specified, explicitly or implicitly, when the map is created. Enum maps * are represented internally as arrays. This representation is extremely * compact and efficient. * *

Enum maps are maintained in the natural order of their keys * (the order in which the enum constants are declared). This is reflected * in the iterators returned by the collections views ({@link #keySet()}, * {@link #entrySet()}, and {@link #values()}). * *

Iterators returned by the collection views are weakly consistent: * they will never throw {@link ConcurrentModificationException} and they may * or may not show the effects of any modifications to the map that occur while * the iteration is in progress. * *

Null keys are not permitted. Attempts to insert a null key will * throw {@link NullPointerException}. Attempts to test for the * presence of a null key or to remove one will, however, function properly. * Null values are permitted. *

Like most collection implementations EnumMap is not * synchronized. If multiple threads access an enum map concurrently, and at * least one of the threads modifies the map, it should be synchronized * externally. This is typically accomplished by synchronizing on some * object that naturally encapsulates the enum map. If no such object exists, * the map should be "wrapped" using the {@link Collections#synchronizedMap} * method. This is best done at creation time, to prevent accidental * unsynchronized access: * *

 *     Map<EnumKey, V> m = Collections.synchronizedMap(new EnumMap(...));
 * 
* *

Implementation note: All basic operations execute in constant time. * They are likely (though not guaranteed) to be faster than their * {@link HashMap} counterparts. * *

This class is a member of the * * Java Collections Framework. * * @author Josh Bloch * @version 1.11, 09/02/05 * @see EnumSet * @since 1.5 */ public class EnumMap, V> extends AbstractMap implements java.io.Serializable, Cloneable { /** * The Class object for the enum type of all the keys of this map. * * @serial */ private final Class keyType; /** * All of the values comprising K. (Cached for performance.) */ private transient K[] keyUniverse; /** * Array representation of this map. The ith element is the value * to which universe[i] is currently mapped, or null if it isn't * mapped to anything, or NULL if it's mapped to null. */ private transient Object[] vals; /** * The number of mappings in this map. */ private transient int size = 0; /** * Distinguished non-null value for representing null values. */ private static final Object NULL = new Object(); private Object maskNull(Object value) { return (value == null ? NULL : value); } private V unmaskNull(Object value) { return (V) (value == NULL ? null : value); } private static Enum[] ZERO_LENGTH_ENUM_ARRAY = new Enum[0]; /** * Creates an empty enum map with the specified key type. * * @param keyType the class object of the key type for this enum map * @throws NullPointerException if keyType is null */ public EnumMap(Class keyType) { this.keyType = keyType; keyUniverse = keyType.getEnumConstants(); vals = new Object[keyUniverse.length]; } /** * Creates an enum map with the same key type as the specified enum * map, initially containing the same mappings (if any). * * @param m the enum map from which to initialize this enum map * @throws NullPointerException if m is null */ public EnumMap(EnumMap m) { keyType = m.keyType; keyUniverse = m.keyUniverse; vals = (Object[]) m.vals.clone(); size = m.size; } /** * Creates an enum map initialized from the specified map. If the * specified map is an EnumMap instance, this constructor behaves * identically to {@link #EnumMap(EnumMap)}. Otherwise, the specified map * must contain at least one mapping (in order to determine the new * enum map's key type). * * @param m the map from which to initialize this enum map * @throws IllegalArgumentException if m is not an * EnumMap instance and contains no mappings * @throws NullPointerException if m is null */ public EnumMap(Map m) { if (m instanceof EnumMap) { EnumMap em = (EnumMap) m; keyType = em.keyType; keyUniverse = em.keyUniverse; vals = (Object[]) em.vals.clone(); size = em.size; } else { if (m.isEmpty()) throw new IllegalArgumentException("Specified map is empty"); keyType = m.keySet().iterator().next().getDeclaringClass(); keyUniverse = keyType.getEnumConstants(); vals = new Object[keyUniverse.length]; putAll(m); } } // Query Operations /** * Returns the number of key-value mappings in this map. * * @return the number of key-value mappings in this map */ public int size() { return size; } /** * Returns true if this map maps one or more keys to the * specified value. * * @param value the value whose presence in this map is to be tested * @return true if this map maps one or more keys to this value */ public boolean containsValue(Object value) { value = maskNull(value); for (Object val : vals) if (value.equals(val)) return true; return false; } /** * Returns true if this map contains a mapping for the specified * key. * * @param key the key whose presence in this map is to be tested * @return true if this map contains a mapping for the specified * key */ public boolean containsKey(Object key) { return isValidKey(key) && vals[((Enum)key).ordinal()] != null; } private boolean containsMapping(Object key, Object value) { return isValidKey(key) && maskNull(value).equals(vals[((Enum)key).ordinal()]); } /** * Returns the value to which this map maps the specified key, or null * if this map contains no mapping for the specified key. * * @param key the key whose associated value is to be returned * @return the value to which this map maps the specified key, or null * if this map contains no mapping for the specified key */ public V get(Object key) { return (isValidKey(key) ? unmaskNull(vals[((Enum)key).ordinal()]) : null); } // Modification Operations /** * Associates the specified value with the specified key in this map. * If the map previously contained a mapping for this key, the old * value is replaced. * * @param key the key with which the specified value is to be associated * @param value the value to be associated with the specified key * * @return the previous value associated with specified key, or * null if there was no mapping for key. (A null * return can also indicate that the map previously associated * null with the specified key.) * @throws NullPointerException if the specified key is null */ public V put(K key, V value) { typeCheck(key); int index = ((Enum)key).ordinal(); Object oldValue = vals[index]; vals[index] = maskNull(value); if (oldValue == null) size++; return unmaskNull(oldValue); } /** * Removes the mapping for this key from this map if present. * * @param key the key whose mapping is to be removed from the map * @return the previous value associated with specified key, or * null if there was no entry for key. (A null * return can also indicate that the map previously associated * null with the specified key.) */ public V remove(Object key) { if (!isValidKey(key)) return null; int index = ((Enum)key).ordinal(); Object oldValue = vals[index]; vals[index] = null; if (oldValue != null) size--; return unmaskNull(oldValue); } private boolean removeMapping(Object key, Object value) { if (!isValidKey(key)) return false; int index = ((Enum)key).ordinal(); if (maskNull(value).equals(vals[index])) { vals[index] = null; size--; return true; } return false; } /** * Returns true if key is of the proper type to be a key in this * enum map. */ private boolean isValidKey(Object key) { if (key == null) return false; // Cheaper than instanceof Enum followed by getDeclaringClass Class keyClass = key.getClass(); return keyClass == keyType || keyClass.getSuperclass() == keyType; } // Bulk Operations /** * Copies all of the mappings from the specified map to this map. * These mappings will replace any mappings that this map had for * any of the keys currently in the specified map. * * @param m the mappings to be stored in this map * @throws NullPointerException the specified map is null, or if * one or more keys in the specified map are null */ public void putAll(Map m) { if (m instanceof EnumMap) { EnumMap em = (EnumMap)m; if (em.keyType != keyType) { if (em.isEmpty()) return; throw new ClassCastException(em.keyType + " != " + keyType); } for (int i = 0; i < keyUniverse.length; i++) { Object emValue = em.vals[i]; if (emValue != null) { if (vals[i] == null) size++; vals[i] = emValue; } } } else { super.putAll(m); } } /** * Removes all mappings from this map. */ public void clear() { Arrays.fill(vals, null); size = 0; } // Views /** * This field is initialized to contain an instance of the entry set * view the first time this view is requested. The view is stateless, * so there's no reason to create more than one. */ private transient Set> entrySet = null; /** * Returns a {@link Set} view of the keys contained in this map. * The returned set obeys the general contract outlined in * {@link Map#keySet()}. The set's iterator will return the keys * in their natural order (the order in which the enum constants * are declared). * * @return a set view of the keys contained in this enum map */ public Set keySet() { Set ks = keySet; if (ks != null) return ks; else return keySet = new KeySet(); } private class KeySet extends AbstractSet { public Iterator iterator() { return new KeyIterator(); } public int size() { return size; } public boolean contains(Object o) { return containsKey(o); } public boolean remove(Object o) { int oldSize = size; EnumMap.this.remove(o); return size != oldSize; } public void clear() { EnumMap.this.clear(); } } /** * Returns a {@link Collection} view of the values contained in this map. * The returned collection obeys the general contract outlined in * {@link Map#values()}. The collection's iterator will return the * values in the order their corresponding keys appear in map, * which is their natural order (the order in which the enum constants * are declared). * * @return a collection view of the values contained in this map */ public Collection values() { Collection vs = values; if (vs != null) return vs; else return values = new Values(); } private class Values extends AbstractCollection { public Iterator iterator() { return new ValueIterator(); } public int size() { return size; } public boolean contains(Object o) { return containsValue(o); } public boolean remove(Object o) { o = maskNull(o); for (int i = 0; i < vals.length; i++) { if (o.equals(vals[i])) { vals[i] = null; size--; return true; } } return false; } public void clear() { EnumMap.this.clear(); } } /** * Returns a {@link Set} view of the mappings contained in this map. * The returned set obeys the general contract outlined in * {@link Map#keySet()}. The set's iterator will return the * mappings in the order their keys appear in map, which is their * natural order (the order in which the enum constants are declared). * * @return a set view of the mappings contained in this enum map */ public Set> entrySet() { Set> es = entrySet; if (es != null) return es; else return entrySet = new EntrySet(); } private class EntrySet extends AbstractSet> { public Iterator> iterator() { return new EntryIterator(); } public boolean contains(Object o) { if (!(o instanceof Map.Entry)) return false; Map.Entry entry = (Map.Entry)o; return containsMapping(entry.getKey(), entry.getValue()); } public boolean remove(Object o) { if (!(o instanceof Map.Entry)) return false; Map.Entry entry = (Map.Entry)o; return removeMapping(entry.getKey(), entry.getValue()); } public int size() { return size; } public void clear() { EnumMap.this.clear(); } public Object[] toArray() { return fillEntryArray(new Object[size]); } public T[] toArray(T[] a) { int size = size(); if (a.length < size) a = (T[])java.lang.reflect.Array .newInstance(a.getClass().getComponentType(), size); if (a.length > size) a[size] = null; return (T[]) fillEntryArray(a); } private Object[] fillEntryArray(Object[] a) { int j = 0; for (int i = 0; i < vals.length; i++) if (vals[i] != null) a[j++] = new AbstractMap.SimpleEntry( keyUniverse[i], unmaskNull(vals[i])); return a; } } private abstract class EnumMapIterator implements Iterator { // Lower bound on index of next element to return int index = 0; // Index of last returned element, or -1 if none int lastReturnedIndex = -1; public boolean hasNext() { while (index < vals.length && vals[index] == null) index++; return index != vals.length; } public void remove() { checkLastReturnedIndex(); if (vals[lastReturnedIndex] != null) { vals[lastReturnedIndex] = null; size--; } lastReturnedIndex = -1; } private void checkLastReturnedIndex() { if (lastReturnedIndex < 0) throw new IllegalStateException(); } } private class KeyIterator extends EnumMapIterator { public K next() { if (!hasNext()) throw new NoSuchElementException(); lastReturnedIndex = index++; return keyUniverse[lastReturnedIndex]; } } private class ValueIterator extends EnumMapIterator { public V next() { if (!hasNext()) throw new NoSuchElementException(); lastReturnedIndex = index++; return unmaskNull(vals[lastReturnedIndex]); } } /** * Since we don't use Entry objects, we use the Iterator itself as entry. */ private class EntryIterator extends EnumMapIterator> implements Map.Entry { public Map.Entry next() { if (!hasNext()) throw new NoSuchElementException(); lastReturnedIndex = index++; return this; } public K getKey() { checkLastReturnedIndexForEntryUse(); return keyUniverse[lastReturnedIndex]; } public V getValue() { checkLastReturnedIndexForEntryUse(); return unmaskNull(vals[lastReturnedIndex]); } public V setValue(V value) { checkLastReturnedIndexForEntryUse(); V oldValue = unmaskNull(vals[lastReturnedIndex]); vals[lastReturnedIndex] = maskNull(value); return oldValue; } public boolean equals(Object o) { if (lastReturnedIndex < 0) return o == this; if (!(o instanceof Map.Entry)) return false; Map.Entry e = (Map.Entry)o; V ourValue = unmaskNull(vals[lastReturnedIndex]); Object hisValue = e.getValue(); return e.getKey() == keyUniverse[lastReturnedIndex] && (ourValue == hisValue || (ourValue != null && ourValue.equals(hisValue))); } public int hashCode() { if (lastReturnedIndex < 0) return super.hashCode(); Object value = vals[lastReturnedIndex]; return keyUniverse[lastReturnedIndex].hashCode() ^ (value == NULL ? 0 : value.hashCode()); } public String toString() { if (lastReturnedIndex < 0) return super.toString(); return keyUniverse[lastReturnedIndex] + "=" + unmaskNull(vals[lastReturnedIndex]); } private void checkLastReturnedIndexForEntryUse() { if (lastReturnedIndex < 0) throw new IllegalStateException("Entry was removed"); } } // Comparison and hashing /** * Compares the specified object with this map for equality. Returns * true if the given object is also a map and the two maps * represent the same mappings, as specified in the {@link * Map#equals(Object)} contract. * * @param o the object to be compared for equality with this map * @return true if the specified object is equal to this map */ public boolean equals(Object o) { if (!(o instanceof EnumMap)) return super.equals(o); EnumMap em = (EnumMap)o; if (em.keyType != keyType) return size == 0 && em.size == 0; // Key types match, compare each value for (int i = 0; i < keyUniverse.length; i++) { Object ourValue = vals[i]; Object hisValue = em.vals[i]; if (hisValue != ourValue && (hisValue == null || !hisValue.equals(ourValue))) return false; } return true; } /** * Returns a shallow copy of this enum map. (The values themselves * are not cloned. * * @return a shallow copy of this enum map */ public EnumMap clone() { EnumMap result = null; try { result = (EnumMap) super.clone(); } catch(CloneNotSupportedException e) { throw new AssertionError(); } result.vals = (Object[]) result.vals.clone(); return result; } /** * Throws an exception if e is not of the correct type for this enum set. */ private void typeCheck(K key) { Class keyClass = key.getClass(); if (keyClass != keyType && keyClass.getSuperclass() != keyType) throw new ClassCastException(keyClass + " != " + keyType); } private static final long serialVersionUID = 458661240069192865L; /** * Save the state of the EnumMap instance to a stream (i.e., * serialize it). * * @serialData The size of the enum map (the number of key-value * mappings) is emitted (int), followed by the key (Object) * and value (Object) for each key-value mapping represented * by the enum map. */ private void writeObject(java.io.ObjectOutputStream s) throws java.io.IOException { // Write out the key type and any hidden stuff s.defaultWriteObject(); // Write out size (number of Mappings) s.writeInt(size); // Write out keys and values (alternating) for (Map.Entry e : entrySet()) { s.writeObject(e.getKey()); s.writeObject(e.getValue()); } } /** * Reconstitute the EnumMap instance from a stream (i.e., * deserialize it). */ private void readObject(java.io.ObjectInputStream s) throws java.io.IOException, ClassNotFoundException { // Read in the key type and any hidden stuff s.defaultReadObject(); keyUniverse = keyType.getEnumConstants(); vals = new Object[keyUniverse.length]; // Read in size (number of Mappings) int size = s.readInt(); // Read the keys and values, and put the mappings in the HashMap for (int i = 0; i < size; i++) { K key = (K) s.readObject(); V value = (V) s.readObject(); put(key, value); } } }