/* Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package java.util; import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.io.Serializable; import java.lang.reflect.Array; /** * An {@code Map} specialized for use with {@code Enum} types as keys. */ public class EnumMap<K extends Enum<K>, V> extends AbstractMap<K, V> implements Serializable, Cloneable, Map<K, V> { // BEGIN android-changed // added implements Map<K, V> for apicheck // END android-changed private static final long serialVersionUID = 458661240069192865L; private Class<K> keyType; transient Enum[] keys; transient Object[] values; transient boolean[] hasMapping; private transient int mappingsCount; transient int enumSize; private transient EnumMapEntrySet<K, V> entrySet = null; private static class Entry<KT extends Enum<KT>, VT> extends MapEntry<KT, VT> { private final EnumMap<KT, VT> enumMap; private final int ordinal; Entry(KT theKey, VT theValue, EnumMap<KT, VT> em) { super(theKey, theValue); enumMap = em; ordinal = ((Enum) theKey).ordinal(); } @SuppressWarnings("unchecked") @Override public boolean equals(Object object) { if (!enumMap.hasMapping[ordinal]) { return false; } boolean isEqual = false; if (object instanceof Map.Entry) { Map.Entry<KT, VT> entry = (Map.Entry<KT, VT>) object; Object enumKey = entry.getKey(); if (key.equals(enumKey)) { Object theValue = entry.getValue(); if (enumMap.values[ordinal] == null) { isEqual = (theValue == null); } else { isEqual = enumMap.values[ordinal].equals(theValue); } } } return isEqual; } @Override public int hashCode() { return (enumMap.keys[ordinal] == null ? 0 : enumMap.keys[ordinal] .hashCode()) ^ (enumMap.values[ordinal] == null ? 0 : enumMap.values[ordinal].hashCode()); } @SuppressWarnings("unchecked") @Override public KT getKey() { checkEntryStatus(); return (KT) enumMap.keys[ordinal]; } @SuppressWarnings("unchecked") @Override public VT getValue() { checkEntryStatus(); return (VT) enumMap.values[ordinal]; } @SuppressWarnings("unchecked") @Override public VT setValue(VT value) { checkEntryStatus(); return enumMap.put((KT) enumMap.keys[ordinal], value); } @Override public String toString() { StringBuilder result = new StringBuilder(enumMap.keys[ordinal] .toString()); result.append("="); result.append(enumMap.values[ordinal] == null ? "null" : enumMap.values[ordinal].toString()); return result.toString(); } private void checkEntryStatus() { if (!enumMap.hasMapping[ordinal]) { throw new IllegalStateException(); } } } private static class EnumMapIterator<E, KT extends Enum<KT>, VT> implements Iterator<E> { int position = 0; int prePosition = -1; final EnumMap<KT, VT> enumMap; final MapEntry.Type<E, KT, VT> type; EnumMapIterator(MapEntry.Type<E, KT, VT> value, EnumMap<KT, VT> em) { enumMap = em; type = value; } public boolean hasNext() { int length = enumMap.enumSize; for (; position < length; position++) { if (enumMap.hasMapping[position]) { break; } } return position != length; } @SuppressWarnings("unchecked") public E next() { if (!hasNext()) { throw new NoSuchElementException(); } prePosition = position++; return type.get(new MapEntry(enumMap.keys[prePosition], enumMap.values[prePosition])); } public void remove() { checkStatus(); if (enumMap.hasMapping[prePosition]) { enumMap.remove(enumMap.keys[prePosition]); } prePosition = -1; } @Override @SuppressWarnings("unchecked") public String toString() { if (-1 == prePosition) { return super.toString(); } return type.get( new MapEntry(enumMap.keys[prePosition], enumMap.values[prePosition])).toString(); } private void checkStatus() { if (-1 == prePosition) { throw new IllegalStateException(); } } } private static class EnumMapKeySet<KT extends Enum<KT>, VT> extends AbstractSet<KT> { private final EnumMap<KT, VT> enumMap; EnumMapKeySet(EnumMap<KT, VT> em) { enumMap = em; } @Override public void clear() { enumMap.clear(); } @Override public boolean contains(Object object) { return enumMap.containsKey(object); } @Override @SuppressWarnings("unchecked") public Iterator iterator() { return new EnumMapIterator<KT, KT, VT>( new MapEntry.Type<KT, KT, VT>() { public KT get(MapEntry<KT, VT> entry) { return entry.key; } }, enumMap); } @Override @SuppressWarnings("unchecked") public boolean remove(Object object) { if (contains(object)) { enumMap.remove(object); return true; } return false; } @Override public int size() { return enumMap.size(); } } private static class EnumMapValueCollection<KT extends Enum<KT>, VT> extends AbstractCollection<VT> { private final EnumMap<KT, VT> enumMap; EnumMapValueCollection(EnumMap<KT, VT> em) { enumMap = em; } @Override public void clear() { enumMap.clear(); } @Override public boolean contains(Object object) { return enumMap.containsValue(object); } @SuppressWarnings("unchecked") @Override public Iterator iterator() { return new EnumMapIterator<VT, KT, VT>( new MapEntry.Type<VT, KT, VT>() { public VT get(MapEntry<KT, VT> entry) { return entry.value; } }, enumMap); } @Override public boolean remove(Object object) { if (object == null) { for (int i = 0; i < enumMap.enumSize; i++) { if (enumMap.hasMapping[i] && enumMap.values[i] == null) { enumMap.remove(enumMap.keys[i]); return true; } } } else { for (int i = 0; i < enumMap.enumSize; i++) { if (enumMap.hasMapping[i] && object.equals(enumMap.values[i])) { enumMap.remove(enumMap.keys[i]); return true; } } } return false; } @Override public int size() { return enumMap.size(); } } private static class EnumMapEntryIterator<E, KT extends Enum<KT>, VT> extends EnumMapIterator<E, KT, VT> { EnumMapEntryIterator(MapEntry.Type<E, KT, VT> value, EnumMap<KT, VT> em) { super(value, em); } @SuppressWarnings("unchecked") @Override public E next() { if (!hasNext()) { throw new NoSuchElementException(); } prePosition = position++; return type.get(new Entry<KT, VT>((KT) enumMap.keys[prePosition], (VT) enumMap.values[prePosition], enumMap)); } } private static class EnumMapEntrySet<KT extends Enum<KT>, VT> extends AbstractSet<Map.Entry<KT, VT>> { private final EnumMap<KT, VT> enumMap; EnumMapEntrySet(EnumMap<KT, VT> em) { enumMap = em; } @Override public void clear() { enumMap.clear(); } @Override public boolean contains(Object object) { boolean isEqual = false; if (object instanceof Map.Entry) { Object enumKey = ((Map.Entry) object).getKey(); Object enumValue = ((Map.Entry) object).getValue(); if (enumMap.containsKey(enumKey)) { VT value = enumMap.get(enumKey); if (value == null) { isEqual = enumValue == null; } else { isEqual = value.equals(enumValue); } } } return isEqual; } @Override public Iterator<Map.Entry<KT, VT>> iterator() { return new EnumMapEntryIterator<Map.Entry<KT, VT>, KT, VT>( new MapEntry.Type<Map.Entry<KT, VT>, KT, VT>() { public Map.Entry<KT, VT> get(MapEntry<KT, VT> entry) { return entry; } }, enumMap); } @Override public boolean remove(Object object) { if (contains(object)) { enumMap.remove(((Map.Entry) object).getKey()); return true; } return false; } @Override public int size() { return enumMap.size(); } @Override public Object[] toArray() { Object[] entryArray = new Object[enumMap.size()]; return toArray(entryArray); } @SuppressWarnings("unchecked") @Override public Object[] toArray(Object[] array) { int size = enumMap.size(); int index = 0; Object[] entryArray = array; if (size > array.length) { Class<?> clazz = array.getClass().getComponentType(); entryArray = (Object[]) Array.newInstance(clazz, size); } Iterator<Map.Entry<KT, VT>> iter = iterator(); for (; index < size; index++) { Map.Entry<KT, VT> entry = iter.next(); entryArray[index] = new MapEntry<KT, VT>(entry.getKey(), entry .getValue()); } if (index < array.length) { entryArray[index] = null; } return entryArray; } } /** * Constructs an empty {@code EnumMap} using the given key type. * * @param keyType * the class object giving the type of the keys used by this {@code EnumMap}. * @throws NullPointerException * if {@code keyType} is {@code null}. */ public EnumMap(Class<K> keyType) { initialization(keyType); } /** * Constructs an {@code EnumMap} using the same key type as the given {@code EnumMap} and * initially containing the same mappings. * * @param map * the {@code EnumMap} from which this {@code EnumMap} is initialized. * @throws NullPointerException * if {@code map} is {@code null}. */ public EnumMap(EnumMap<K, ? extends V> map) { initialization(map); } /** * Constructs an {@code EnumMap} initialized from the given map. If the given map * is an {@code EnumMap} instance, this constructor behaves in the exactly the same * way as {@link EnumMap#EnumMap(EnumMap)}}. Otherwise, the given map * should contain at least one mapping. * * @param map * the map from which this {@code EnumMap} is initialized. * @throws IllegalArgumentException * if {@code map} is not an {@code EnumMap} instance and does not contain * any mappings. * @throws NullPointerException * if {@code map} is {@code null}. */ @SuppressWarnings("unchecked") public EnumMap(Map<K, ? extends V> map) { if (map instanceof EnumMap) { initialization((EnumMap<K, V>) map); } else { if (map.size() == 0) { throw new IllegalArgumentException(); } Iterator<K> iter = map.keySet().iterator(); K enumKey = iter.next(); Class clazz = enumKey.getClass(); if (clazz.isEnum()) { initialization(clazz); } else { initialization(clazz.getSuperclass()); } putAllImpl(map); } } /** * Removes all elements from this {@code EnumMap}, leaving it empty. * * @see #isEmpty() * @see #size() */ @Override public void clear() { Arrays.fill(values, null); Arrays.fill(hasMapping, false); mappingsCount = 0; } /** * Returns a shallow copy of this {@code EnumMap}. * * @return a shallow copy of this {@code EnumMap}. */ @SuppressWarnings("unchecked") @Override public EnumMap<K, V> clone() { try { EnumMap<K, V> enumMap = (EnumMap<K, V>) super.clone(); enumMap.initialization(this); return enumMap; } catch (CloneNotSupportedException e) { throw new AssertionError(e); } } /** * Returns whether this {@code EnumMap} contains the specified key. * * @param key * the key to search for. * @return {@code true} if this {@code EnumMap} contains the specified key, * {@code false} otherwise. */ @Override public boolean containsKey(Object key) { if (isValidKeyType(key)) { int keyOrdinal = ((Enum) key).ordinal(); return hasMapping[keyOrdinal]; } return false; } /** * Returns whether this {@code EnumMap} contains the specified value. * * @param value * the value to search for. * @return {@code true} if this {@code EnumMap} contains the specified value, * {@code false} otherwise. */ @Override public boolean containsValue(Object value) { if (value == null) { for (int i = 0; i < enumSize; i++) { if (hasMapping[i] && values[i] == null) { return true; } } } else { for (int i = 0; i < enumSize; i++) { if (hasMapping[i] && value.equals(values[i])) { return true; } } } return false; } /** * Returns a {@code Set} containing all of the mappings in this {@code EnumMap}. Each mapping is * an instance of {@link Map.Entry}. As the {@code Set} is backed by this {@code EnumMap}, * changes in one will be reflected in the other. * <p> * The order of the entries in the set will be the order that the enum keys * were declared in. * * @return a {@code Set} of the mappings. */ @Override public Set<Map.Entry<K, V>> entrySet() { if (entrySet == null) { entrySet = new EnumMapEntrySet<K, V>(this); } return entrySet; } /** * Compares the argument to the receiver, and returns {@code true} if the * specified {@code Object} is an {@code EnumMap} and both {@code EnumMap}s contain the same mappings. * * @param object * the {@code Object} to compare with this {@code EnumMap}. * @return boolean {@code true} if {@code object} is the same as this {@code EnumMap}, * {@code false} otherwise. * @see #hashCode() * @see #entrySet() */ @SuppressWarnings("unchecked") @Override public boolean equals(Object object) { if (this == object) { return true; } if (!(object instanceof EnumMap)) { return super.equals(object); } EnumMap<K, V> enumMap = (EnumMap<K, V>) object; if (keyType != enumMap.keyType || size() != enumMap.size()) { return false; } return Arrays.equals(hasMapping, enumMap.hasMapping) && Arrays.equals(values, enumMap.values); } /** * Returns the value of the mapping with the specified key. * * @param key * the key. * @return the value of the mapping with the specified key, or {@code null} * if no mapping for the specified key is found. */ @Override @SuppressWarnings("unchecked") public V get(Object key) { if (!isValidKeyType(key)) { return null; } int keyOrdinal = ((Enum) key).ordinal(); return (V) values[keyOrdinal]; } /** * Returns a set of the keys contained in this {@code EnumMap}. The {@code Set} is backed by * this {@code EnumMap} so changes to one are reflected in the other. The {@code Set} does not * support adding. * <p> * The order of the set will be the order that the enum keys were declared * in. * * @return a {@code Set} of the keys. */ @Override public Set<K> keySet() { if (keySet == null) { keySet = new EnumMapKeySet<K, V>(this); } return keySet; } /** * Maps the specified key to the specified value. * * @param key * the key. * @param value * the value. * @return the value of any previous mapping with the specified key or * {@code null} if there was no mapping. * @throws UnsupportedOperationException * if adding to this map is not supported. * @throws ClassCastException * if the class of the key or value is inappropriate for this * map. * @throws IllegalArgumentException * if the key or value cannot be added to this map. * @throws NullPointerException * if the key or value is {@code null} and this {@code EnumMap} does not * support {@code null} keys or values. */ @Override @SuppressWarnings("unchecked") public V put(K key, V value) { return putImpl(key, value); } /** * Copies every mapping in the specified {@code Map} to this {@code EnumMap}. * * @param map * the {@code Map} to copy mappings from. * @throws UnsupportedOperationException * if adding to this {@code EnumMap} is not supported. * @throws ClassCastException * if the class of a key or value is inappropriate for this * {@code EnumMap}. * @throws IllegalArgumentException * if a key or value cannot be added to this map. * @throws NullPointerException * if a key or value is {@code null} and this {@code EnumMap} does not * support {@code null} keys or values. */ @Override @SuppressWarnings("unchecked") public void putAll(Map<? extends K, ? extends V> map) { putAllImpl(map); } /** * Removes a mapping with the specified key from this {@code EnumMap}. * * @param key * the key of the mapping to remove. * @return the value of the removed mapping or {@code null} if no mapping * for the specified key was found. * @throws UnsupportedOperationException * if removing from this {@code EnumMap} is not supported. */ @Override @SuppressWarnings("unchecked") public V remove(Object key) { if (!isValidKeyType(key)) { return null; } int keyOrdinal = ((Enum) key).ordinal(); if (hasMapping[keyOrdinal]) { hasMapping[keyOrdinal] = false; mappingsCount--; } V oldValue = (V) values[keyOrdinal]; values[keyOrdinal] = null; return oldValue; } /** * Returns the number of elements in this {@code EnumMap}. * * @return the number of elements in this {@code EnumMap}. */ @Override public int size() { return mappingsCount; } /** * Returns a {@code Collection} of the values contained in this {@code EnumMap}. The returned * {@code Collection} complies with the general rule specified in * {@link Map#values()}. The {@code Collection}'s {@code Iterator} will return the values * in the their corresponding keys' natural order (the {@code Enum} constants are * declared in this order). * <p> * The order of the values in the collection will be the order that their * corresponding enum keys were declared in. * * @return a collection of the values contained in this {@code EnumMap}. */ @Override public Collection<V> values() { if (valuesCollection == null) { valuesCollection = new EnumMapValueCollection<K, V>(this); } return valuesCollection; } @SuppressWarnings("unchecked") private void readObject(ObjectInputStream stream) throws IOException, ClassNotFoundException { stream.defaultReadObject(); initialization(keyType); int elementCount = stream.readInt(); Enum<K> enumKey; Object value; for (int i = elementCount; i > 0; i--) { enumKey = (Enum<K>) stream.readObject(); value = stream.readObject(); putImpl((K) enumKey, (V) value); } } private void writeObject(ObjectOutputStream stream) throws IOException { stream.defaultWriteObject(); stream.writeInt(mappingsCount); Iterator<Map.Entry<K, V>> iterator = entrySet().iterator(); while (iterator.hasNext()) { Map.Entry<K, V> entry = iterator.next(); stream.writeObject(entry.getKey()); stream.writeObject(entry.getValue()); } } private boolean isValidKeyType(Object key) { if (key != null && keyType.isInstance(key)) { return true; } return false; } @SuppressWarnings("unchecked") private void initialization(EnumMap enumMap) { keyType = enumMap.keyType; keys = enumMap.keys; enumSize = enumMap.enumSize; values = enumMap.values.clone(); hasMapping = enumMap.hasMapping.clone(); mappingsCount = enumMap.mappingsCount; } private void initialization(Class<K> type) { keyType = type; keys = Enum.getSharedConstants(keyType); enumSize = keys.length; values = new Object[enumSize]; hasMapping = new boolean[enumSize]; } @SuppressWarnings("unchecked") private void putAllImpl(Map map) { Iterator iter = map.entrySet().iterator(); while (iter.hasNext()) { Map.Entry entry = (Map.Entry) iter.next(); putImpl((K) entry.getKey(), (V) entry.getValue()); } } @SuppressWarnings("unchecked") private V putImpl(K key, V value) { if (key == null) { throw new NullPointerException("key == null"); } keyType.cast(key); // Called to throw ClassCastException. int keyOrdinal = key.ordinal(); if (!hasMapping[keyOrdinal]) { hasMapping[keyOrdinal] = true; mappingsCount++; } V oldValue = (V) values[keyOrdinal]; values[keyOrdinal] = value; return oldValue; } }