blob: 5474d8214acba19e588d4fb8b31d75650f6ecefd [file] [log] [blame]
/* 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;
public class EnumMap<K extends Enum<K>, V> extends AbstractMap<K, V> implements
Map<K, V>, Serializable, Cloneable {
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();
}
@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();
isEqual = enumMap.values[ordinal] == null ? null == theValue
: 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());
}
@Override
public KT getKey() {
checkEntryStatus();
return (KT) enumMap.keys[ordinal];
}
@Override
public VT getValue() {
checkEntryStatus();
return (VT) enumMap.values[ordinal];
}
@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("="); //$NON-NLS-1$
result.append(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 (null == object) {
for (int i = 0; i < enumMap.enumSize; i++) {
if (enumMap.hasMapping[i] && null == enumMap.values[i]) {
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);
}
@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);
isEqual = (value == null ? null == enumValue : 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);
}
@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 enum map using the given key type.
*
* @param keyType
* the class object of the key type used by this enum map
* @throws NullPointerException
* if the keyType is null
*/
public EnumMap(Class<K> keyType) {
initialization(keyType);
}
/**
* Constructs an enum map using the same key type as the given enum map and
* initially containing the same mappings.
*
* @param map
* the enum map from which this enum map is initialized
* @throws NullPointerException
* if the map is null
*/
public EnumMap(EnumMap<K, ? extends V> map) {
initialization(map);
}
/**
* Constructs an enum map initialized from the given map. If the given map
* is an EnumMap instance, this constructor behaves in the exactly the same
* way as {@link EnumMap#EnumMap(EnumMap)}}. Otherwise, the given map at
* least should contain one mapping.
*
* @param map
* the map from which this enum map is initialized
* @throws IllegalArgumentException
* if the map is not an enum map instance and does not contain
* any mappings
* @throws NullPointerException
* if the map is null
*/
public EnumMap(Map<K, ? extends V> map) {
if (map instanceof EnumMap) {
initialization((EnumMap<K, V>) map);
} else {
if (0 == map.size()) {
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 mappings in this map.
*/
@Override
public void clear() {
Arrays.fill(values, null);
Arrays.fill(hasMapping, false);
mappingsCount = 0;
}
/**
* Returns a shallow copy of this map.
*
* @return a shallow copy of this map
*/
@Override
public EnumMap<K, V> clone() {
try {
EnumMap<K, V> enumMap = (EnumMap<K, V>) super.clone();
enumMap.initialization(this);
return enumMap;
} catch (CloneNotSupportedException e) {
return null;
}
}
/**
* Returns true if this map has a mapping for the given key.
*
* @param key
* the key whose presence in this map is to be tested
* @return true if this map has a mapping for the given key.
*/
@Override
public boolean containsKey(Object key) {
if (isValidKeyType(key)) {
int keyOrdinal = ((Enum) key).ordinal();
return hasMapping[keyOrdinal];
}
return false;
}
/**
* Returns true if this map has one or more keys mapped to the given value.
*
* @param value
* the value whose presence in this map is to be tested
* @return true if this map has one or more keys mapped to the given value.
*/
@Override
public boolean containsValue(Object value) {
if (null == value) {
for (int i = 0; i < enumSize; i++) {
if (hasMapping[i] && null == values[i]) {
return true;
}
}
} else {
for (int i = 0; i < enumSize; i++) {
if (hasMapping[i] && value.equals(values[i])) {
return true;
}
}
}
return false;
}
/**
* Returns a {@link Set}} view of the mappings contained in this map. The
* returned set complies with the general rule specified in
* {@link Map#entrySet()}}. The set's iterator will return the mappings in
* the their keys' natural order(the enum constants are declared in this
* order)
*
* @return a set view of the mappings contained in this map.
*/
@Override
public Set<Map.Entry<K, V>> entrySet() {
if (null == entrySet) {
entrySet = new EnumMapEntrySet<K, V>(this);
}
return entrySet;
}
/**
* Compares the given object with this map. Returns true if the given object
* is equal to this map.
*
* @param object
* the object to be compared with this map
* @return true if the given object is equal to this map.
*/
@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 which is mapped to the given key in this map, or null
* if this map has no mapping for the given key.
*
* @param key
* the key whose associated value is to be returned
* @return the value to which this map maps the given key, or null if this
* map has no mapping for the given key.
*/
@Override
@SuppressWarnings("unchecked")
public V get(Object key) {
if (!isValidKeyType(key)) {
return null;
}
int keyOrdinal = ((Enum) key).ordinal();
return (V) values[keyOrdinal];
}
/**
* Returns a {@link Set}} view of the keys contained in this map. The
* returned set complies with the general rule specified in
* {@link Map#keySet()}}. The set's iterator will return the keys in the
* their natural order(the enum constants are declared in this order)
*
* @return a set view of the keys contained in this map.
*/
@Override
public Set<K> keySet() {
if (null == keySet) {
keySet = new EnumMapKeySet<K, V>(this);
}
return keySet;
}
/**
* Associates the given value with the given key in this map. If the map
* previously had a mapping for this key, the old value is replaced.
*
* @param key
* the key with which the given value is to be associated value
* @param value
* the value to be associated with the given key
* @return the value to which this map maps the given key, or null if this
* map has no mapping for the given key.
* @throws NullPointerException
* if the given key is null
*/
@Override
@SuppressWarnings("unchecked")
public V put(K key, V value) {
return putImpl(key, value);
}
/**
* Copies all the mappings in the given map to this map. These mappings will
* replace all mappings that this map had for all of the keys currently in
* the given map.
*
* @param map
* the key whose presence in this map is to be tested
* @throws NullPointerException
* if the given map is null, or if one or more keys in the given
* map are null
*/
@Override
@SuppressWarnings("unchecked")
public void putAll(Map<? extends K, ? extends V> map) {
putAllImpl(map);
}
/**
* Removes the mapping for this key from this map if it is present.
*
* @param key
* the key whose mapping is to be removed from this map
* @return the previous value associated with the given key, or null if this
* map has no mapping for this key.
*/
@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 the mappings in this map.
*
* @return the number of the mappings in this map
*/
@Override
public int size() {
return mappingsCount;
}
/**
* Returns a {@link Collection}} view of the values contained in this map.
* The returned collection complys with the general rule specified in
* {@link Map#values()}}. The collection's iterator will return the values
* in the their corresponding keys' natural order(the enum constants are
* declared in this order)
*
* @return a collection view of the mappings contained in this map.
*/
@Override
public Collection<V> values() {
if (null == valuesCollection) {
valuesCollection = new EnumMapValueCollection<K, V>(this);
}
return valuesCollection;
}
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 (null != key && 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 = keyType.getEnumConstants();
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 (null == key) {
throw new NullPointerException();
}
if (!isValidKeyType(key)) {
throw new ClassCastException();
}
int keyOrdinal = key.ordinal();
if (!hasMapping[keyOrdinal]) {
hasMapping[keyOrdinal] = true;
mappingsCount++;
}
V oldValue = (V) values[keyOrdinal];
values[keyOrdinal] = value;
return oldValue;
}
}