java.util.EnumMap Maven / Gradle / Ivy
/* 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, V> extends AbstractMap implements
Serializable, Cloneable, Map {
// BEGIN android-changed
// added implements Map for apicheck
// END android-changed
private static final long serialVersionUID = 458661240069192865L;
private Class keyType;
transient Enum[] keys;
transient Object[] values;
transient boolean[] hasMapping;
private transient int mappingsCount;
transient int enumSize;
private transient EnumMapEntrySet entrySet = null;
private static class Entry, VT> extends
MapEntry {
private final EnumMap enumMap;
private final int ordinal;
Entry(KT theKey, VT theValue, EnumMap 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 entry = (Map.Entry) 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, VT> implements
Iterator {
int position = 0;
int prePosition = -1;
final EnumMap enumMap;
final MapEntry.Type type;
EnumMapIterator(MapEntry.Type value, EnumMap 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 (E) type.get(new MapEntry(enumMap.keys[prePosition], // BugVM note: Added cast to (E) to make this line compile with Oracle's Java8 JDK
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, VT> extends
AbstractSet {
private final EnumMap enumMap;
EnumMapKeySet(EnumMap 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(
new MapEntry.Type() {
public KT get(MapEntry 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, VT>
extends AbstractCollection {
private final EnumMap enumMap;
EnumMapValueCollection(EnumMap 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(
new MapEntry.Type() {
public VT get(MapEntry 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, VT>
extends EnumMapIterator {
EnumMapEntryIterator(MapEntry.Type value, EnumMap em) {
super(value, em);
}
@SuppressWarnings("unchecked")
@Override
public E next() {
if (!hasNext()) {
throw new NoSuchElementException();
}
prePosition = position++;
return type.get(new Entry((KT) enumMap.keys[prePosition],
(VT) enumMap.values[prePosition], enumMap));
}
}
private static class EnumMapEntrySet, VT> extends
AbstractSet> {
private final EnumMap enumMap;
EnumMapEntrySet(EnumMap 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> iterator() {
return new EnumMapEntryIterator, KT, VT>(
new MapEntry.Type, KT, VT>() {
public Map.Entry get(MapEntry 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> iter = iterator();
for (; index < size; index++) {
Map.Entry entry = iter.next();
entryArray[index] = new MapEntry(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 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 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 map) {
if (map instanceof EnumMap) {
initialization((EnumMap) map);
} else {
if (map.isEmpty()) {
throw new IllegalArgumentException("map is empty");
}
Iterator 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 clone() {
try {
EnumMap enumMap = (EnumMap) 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.
*
* 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> entrySet() {
if (entrySet == null) {
entrySet = new EnumMapEntrySet(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 enumMap = (EnumMap) 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.
*
* 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 keySet() {
if (keySet == null) {
keySet = new EnumMapKeySet(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).
*
* 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 values() {
if (valuesCollection == null) {
valuesCollection = new EnumMapValueCollection(this);
}
return valuesCollection;
}
@SuppressWarnings("unchecked")
private void readObject(ObjectInputStream stream) throws IOException,
ClassNotFoundException {
stream.defaultReadObject();
initialization(keyType);
int elementCount = stream.readInt();
Enum enumKey;
Object value;
for (int i = elementCount; i > 0; i--) {
enumKey = (Enum) stream.readObject();
value = stream.readObject();
putImpl((K) enumKey, (V) value);
}
}
private void writeObject(ObjectOutputStream stream) throws IOException {
stream.defaultWriteObject();
stream.writeInt(mappingsCount);
Iterator> iterator = entrySet().iterator();
while (iterator.hasNext()) {
Map.Entry 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 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;
}
}