package edu.stanford.nlp.util;
import java.util.Map;
import java.util.Set;
/**
* An extension of {@link ArrayCoreMap} with an immutable set of key,value
* pairs that is used for equality and hashcode comparisons.
*
* @author dramage
*/
public class HashableCoreMap extends ArrayCoreMap {
/** Set of immutable keys */
private final Set<Class<? extends TypesafeMap.Key<?>>> immutableKeys;
/** Pre-computed hashcode */
private final int hashcode;
/**
* Creates an instance of HashableCoreMap with initial key,value pairs
* for the immutable, hashable keys as provided in the given map.
*/
@SuppressWarnings("unchecked")
public HashableCoreMap(Map<Class<? extends TypesafeMap.Key<?>>,Object> hashkey) {
int keyHashcode = 0;
int valueHashcode = 0;
for (Map.Entry<Class<? extends TypesafeMap.Key<?>>,Object> entry : hashkey.entrySet()) {
// NB it is important to compose these hashcodes in an order-independent
// way, so we just add them all here.
keyHashcode += entry.getKey().hashCode();
valueHashcode += entry.getValue().hashCode();
super.set((Class) entry.getKey(), entry.getValue());
}
this.immutableKeys = hashkey.keySet();
this.hashcode = keyHashcode * 31 + valueHashcode;
}
/**
* Creates an instance by copying values from the given other CoreMap,
* using the values it associates with the given set of hashkeys for
* the immutable, hashable keys used by hashcode and equals.
*/
@SuppressWarnings("unchecked")
public HashableCoreMap(ArrayCoreMap other, Set<Class<? extends TypesafeMap.Key<?>>> hashkey) {
super(other);
int keyHashcode = 0;
int valueHashcode = 0;
for (Class<? extends TypesafeMap.Key<?>> key : hashkey) {
// NB it is important to compose these hashcodes in an order-independent
// way, so we just add them all here.
keyHashcode += key.hashCode();
valueHashcode += super.get((Class)key).hashCode();
}
this.immutableKeys = hashkey;
this.hashcode = keyHashcode * 31 + valueHashcode;
}
/**
* Sets the value associated with the given key; if the the key is one
* of the hashable keys, throws an exception.
*
* @throws HashableCoreMapException Attempting to set the value for an
* immutable, hashable key.
*/
@Override
public <VALUE> VALUE set(Class<? extends Key<VALUE>> key, VALUE value) {
if (immutableKeys.contains(key)) {
throw new HashableCoreMapException("Attempt to change value " +
"of immutable field "+key.getSimpleName());
}
return super.set(key, value);
}
/**
* Provides a hash code based on the immutable keys and values provided
* to the constructor.
*/
@Override
public int hashCode() {
return hashcode;
}
/**
* If the provided object is a HashableCoreMap, equality is based only
* upon the values of the immutable hashkeys; otherwise, defaults to
* behavior of the superclass's equals method.
*/
@SuppressWarnings("unchecked")
@Override
public boolean equals(Object o) {
if (o instanceof HashableCoreMap) {
HashableCoreMap other = (HashableCoreMap) o;
if (!other.immutableKeys.equals(this.immutableKeys)) {
return false;
}
for (Class<? extends TypesafeMap.Key<?>> key : immutableKeys) {
if (!this.get((Class)key).equals(other.get((Class)key))) {
return false;
}
}
return true;
} else {
return super.equals(o);
}
}
private static final long serialVersionUID = 1L;
//
// Exception type
//
/**
* An exception thrown when attempting to change the value associated
* with an (immutable) hash key in a HashableCoreMap.
*
* @author dramage
*/
public static class HashableCoreMapException extends RuntimeException {
public HashableCoreMapException(String message) {
super(message);
}
/**
*
*/
private static final long serialVersionUID = 1L;
}
}