package edu.stanford.nlp.util;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.util.AbstractSet;
import java.util.Iterator;
import java.util.NoSuchElementException;
import java.util.Set;
/**
* <p>
* Base implementation of {@link CoreMap} backed by Java Arrays.
* </p>
*
* <p>
* Reasonable care has been put into ensuring that this class is both fast and
* has a light memory footprint.
* </p>
*
* <p>
* Note that like the base classes in the Collections API, this implementation
* is <em>not thread-safe</em>. For speed reasons, these methods are not
* synchronized. A synchronized wrapper could be developed by anyone so
* inclined.
* </p>
*
* <p>
* Equality is defined over the complete set of keys and values currently
* stored in the map. Because this class is mutable, it should not be used
* as a key in a HashMap.
* </p>
*
* @author dramage
* @author rafferty
*/
public class ArrayCoreMap implements CoreMap, Serializable {
/** Initial capacity of the array */
private static final int INITIAL_CAPACITY = 4;
/** Array of keys */
private Class<?>[] keys;
/** Array of values */
private Object[] values;
/** Total number of elements actually in keys,values */
private int size; // = 0;
/**
* Default constructor - initializes with default initial annotation
* capacity of 4.
*/
public ArrayCoreMap() {
this(INITIAL_CAPACITY);
}
/**
* Initializes this ArrayCoreMap, pre-allocating arrays to hold
* up to capacity key,value pairs. This array will grow if necessary.
*
* @param capacity Initial capacity of object in key,value pairs
*/
public ArrayCoreMap(int capacity) {
keys = new Class<?>[capacity];
values = new Object[capacity];
}
/**
* Copy constructor.
* @param other The ArrayCoreMap to copy. It may not be null.
*/
public ArrayCoreMap(ArrayCoreMap other) {
size = other.size;
keys = new Class<?>[size];
values = new Object[size];
for (int i = 0; i < size; i++) {
this.keys[i] = other.keys[i];
this.values[i] = other.values[i];
}
}
/**
* Copy constructor.
* @param other The ArrayCoreMap to copy. It may not be null.
*/
@SuppressWarnings("unchecked")
public ArrayCoreMap(CoreMap other) {
Set<Class<?>> otherKeys = other.keySet();
size = otherKeys.size();
keys = new Class<?>[size];
values = new Object[size];
int i = 0;
for (Class<?> key : otherKeys) {
this.keys[i] = key;
this.values[i] = other.get((Class)key);
i++;
}
}
/**
* {@inheritDoc}
*/
@SuppressWarnings("unchecked")
public <VALUE, KEY extends Key<CoreMap, VALUE>>
VALUE get(Class<KEY> key) {
for (int i = size; i > 0; ) {
if (keys[--i] == key) {
return (VALUE)values[i];
}
}
return null;
}
/**
* {@inheritDoc}
*/
public <VALUE, KEY extends Key<CoreMap, VALUE>>
boolean has(Class<KEY> key) {
for (int i = 0; i < size; i++) {
if (keys[i] == key) {
return true;
}
}
return false;
}
/**
* {@inheritDoc}
*/
@SuppressWarnings("unchecked")
public <VALUEBASE, VALUE extends VALUEBASE, KEY extends Key<CoreMap, VALUEBASE>>
VALUE set(Class<KEY> key, VALUE value) {
// search array for existing value to replace
for (int i = 0; i < size; i++) {
if (keys[i] == key) {
VALUE rv = (VALUE)values[i];
values[i] = value;
return rv;
}
}
// not found in arrays, add to end ...
// increment capacity of arrays if necessary
if (size >= keys.length) {
int capacity = keys.length + (keys.length < 16 ? 4: 8);
Class<?>[] newKeys = new Class<?>[capacity];
Object[] newVals = new Object[capacity];
System.arraycopy(keys, 0, newKeys, 0, size);
System.arraycopy(values, 0, newVals, 0, size);
keys = newKeys;
values = newVals;
}
// store value
keys[size] = key;
values[size] = value;
size++;
return null;
}
/**
* {@inheritDoc}
*/
public Set<Class<?>> keySet() {
return new AbstractSet<Class<?>>() {
@Override
public Iterator<Class<?>> iterator() {
return new Iterator<Class<?>>() {
private int i; // = 0;
public boolean hasNext() {
return i < size;
}
public Class<?> next() {
try {
return keys[i++];
} catch (ArrayIndexOutOfBoundsException aioobe) {
throw new NoSuchElementException("ArrayCoreMap keySet iterator exhausted");
}
}
@SuppressWarnings("unchecked")
public void remove() {
ArrayCoreMap.this.remove((Class)keys[i]);
}
};
}
@Override
public int size() {
return size;
}
};
}
/**
* {@inheritDoc}
*/
@SuppressWarnings("unchecked")
public <VALUE, KEY extends Key<CoreMap, VALUE>>
VALUE remove(Class<KEY> key) {
Object rv = null;
for (int i = 0; i < size; i++) {
if (keys[i] == key) {
rv = values[i];
if (i < size - 1) {
System.arraycopy(keys, i+1, keys, i, size-(i+1));
System.arraycopy(values, i+1, values, i, size-(i+1));
}
size--;
break;
}
}
return (VALUE)rv;
}
/**
* {@inheritDoc}
*/
public <VALUE, KEY extends Key<CoreMap, VALUE>>
boolean containsKey(Class<KEY> key) {
for (int i = 0; i < size; i++) {
if (keys[i] == key) {
return true;
}
}
return false;
}
/**
* Reduces memory consumption to the minimum for representing the values
* currently stored stored in this object.
*/
public void compact() {
if (keys.length > size) {
Class<?>[] newKeys = new Class<?>[size];
Object[] newVals = new Object[size];
System.arraycopy(keys, 0, newKeys, 0, size);
System.arraycopy(values, 0, newVals, 0, size);
keys = newKeys;
values = newVals;
}
}
public void setCapacity(int newSize) {
if (size > newSize) { throw new RuntimeException("You cannot set capacity to smaller than the current size."); }
Class<?>[] newKeys = new Class<?>[size];
Object[] newVals = new Object[size];
System.arraycopy(keys, 0, newKeys, 0, size);
System.arraycopy(values, 0, newVals, 0, size);
keys = newKeys;
values = newVals;
}
/**
* Returns the number of elements in this map.
* @return The number of elements in this map.
*/
public int size() {
return size;
}
@Override
public String toString() {
StringBuilder s = new StringBuilder("[");
for (int i = 0; i < size; i++) {
s.append(keys[i].getSimpleName());
s.append('=');
s.append(values[i]);
if (i < size-1) {
s.append(' ');
}
}
s.append(']');
return s.toString();
}
public String toShorterString(String... what) {
StringBuilder s = new StringBuilder("[");
for (int i = 0; i < size; i++) {
String name = keys[i].getSimpleName();
int annoIdx = name.lastIndexOf("Annotation");
if (annoIdx >= 0) {
name = name.substring(0, annoIdx);
}
boolean include;
if (what.length > 0) {
include = false;
for (String item : what) {
if (item.equals(name)) {
include = true;
}
}
} else {
include = true;
}
if (include) {
if (s.length() > 1) {
s.append(' ');
}
s.append(name);
s.append('=');
s.append(values[i]);
}
}
s.append(']');
return s.toString();
}
/**
* Two CoreMaps are equal iff all keys and values are .equal.
*/
@SuppressWarnings("unchecked")
@Override
public boolean equals(Object obj) {
if (!(obj instanceof CoreMap)) {
return false;
}
if (obj instanceof HashableCoreMap) {
// overridden behavior for HashableCoreMap
return obj.equals(this);
}
if (obj instanceof ArrayCoreMap) {
// specialized equals for ArrayCoreMap
return equals((ArrayCoreMap)obj);
}
// general equality
CoreMap other = (CoreMap)obj;
if ( ! this.keySet().equals(other.keySet())) {
return false;
}
for (Class key : this.keySet()) {
if (!other.has(key)) {
return false;
}
Object thisV = this.get(key), otherV = other.get(key);
if (thisV == otherV) {
continue;
}
// the two values must be unequal, so if either is null, the other isn't
if (thisV == null || otherV == null) {
return false;
}
if ( ! thisV.equals(otherV)) {
return false;
}
}
return true;
}
private boolean equals(ArrayCoreMap other) {
if (this.size != other.size) {
return false;
}
for (int i = 0; i < this.size; i++) {
// test if other contains this key,value pair
boolean matched = false;
for (int j = 0; j < other.size; j++) {
if (this.keys[i] == other.keys[j]) {
if ((this.values[i] == null && other.values[j] != null) ||
(this.values[i] != null && other.values[j] == null)) {
matched = false;
break;
}
if ((this.values[i] == null && other.values[j] == null) ||
(this.values[i].equals(other.values[j]))) {
matched = true;
break;
}
}
}
if (!matched) {
return false;
}
}
return true;
}
/**
* Returns a composite hashCode over all the keys and values currently
* stored in the map. Because they may change over time, this class
* is not appropriate for use as map keys.
*/
@Override
public int hashCode() {
int keyscode = 0;
int valuescode = 0;
for (int i = 0; i < size; i++) {
keyscode += keys[i].hashCode();
valuescode += (values[i] != null ? values[i].hashCode() : 0);
}
return keyscode * 37 + valuescode;
}
//
// serialization magic
//
/** Serialization version id */
private static final long serialVersionUID = 1L;
/**
* Overridden serialization method: compacts our map before writing.
*
* @param out Stream to write to
* @throws IOException If IO error
*/
private void writeObject(ObjectOutputStream out) throws IOException {
compact();
out.defaultWriteObject();
}
}