/**
* A HashMap with a fixed size of entries and static allocation behaviour.
*
* @author Kun Wei, Frank Zeyda
*/
package scjlibs.examples.hijac.cdx;
import scjlibs.examples.hijac.cdx.Error;
import scjlibs.util.HashSet;
import scjlibs.util.Set;
//import java.util.HashSet;
//import java.util.Set;
/**
* We note that the key and value of a map entry must not be null.
*/
public class CHashMap {
/* The maximal capacity of this HashMap. */
private int capacity;
/* The actual size of the map; this is the number of entries. */
private int size = 0;
/* An array containing the key-value mappings. */
private HashEntry[] table;
/* An array that is used for the store of pre-allocated entry objects. */
private HashEntry[] store;
/* Mutable entry class; we access its fields directly. */
static class HashEntry {
private Object key;
private Object value;
private HashEntry next;
private boolean valid;
HashEntry() {
key = null;
value = null;
next = null;
valid = false;
}
}
/**
* Constructs a new CHashMap with a specific initial capacity.
*
* This pre-allocates memory for a respective number of entries.
*
* @param initialCapacity
* the initial capacity of this CHashMap
*/
public CHashMap(int initialCapacity) {
/* Set the capacity of the table; note that it cannot increase. */
capacity = initialCapacity;
/* Initially the table is empty. */
size = 0;
/* Initialise the table array. */
table = new HashEntry[initialCapacity];
/* Initialise the store array. */
store = new HashEntry[initialCapacity];
/* Pre-allocate HashEntry object for potential entries. */
for (int i = 0; i < initialCapacity; i++) {
store[i] = new HashEntry();
}
}
/**
* Helper method that returns an index into the buckets array for `key'
* based on its hashCode().
*
* @param key
* the key
* @return the bucket number
*/
private final int hash(Object key) {
return key == null ? 0 : Math.abs(key.hashCode() % capacity);
}
/**
* Returns the value in this CHashMap associated with the supplied key, or
* <code>null</code> if no mapping for the key exists in the map.
*
* @param key
* the key for which to fetch an associated value
* @return value that the key maps to, if present
*/
public Object get(Object key) {
int idx = hash(key);
HashEntry entry = table[idx];
while (entry != null) {
if (key.equals(entry.key)) {
/* Only return a value if the entry is valid. */
if (entry.valid) {
return entry.value;
} else {
return null;
}
}
/* Sift through elements in the bucket. */
entry = entry.next;
}
return null;
}
/**
* Puts the supplied value into the Map, stored under the supplied key.
*
* @param key
* the key under which the value is stored
* @param value
* the value to be stored in the HashMap
*/
public void put(Object key, Object value) {
int idx = hash(key);
if (table[idx] == null) {
/* Use a pre-allocated HashEntry from the store. */
if (size == capacity) {
Error.abort("Exceeding storage capacity in CHashMap.");
}
HashEntry entry = store[size++];
table[idx] = entry;
/* Initialise the new entry. */
entry.key = key;
entry.value = value;
entry.next = null;
entry.valid = true;
} else {
HashEntry entry = table[idx];
HashEntry prev = null;
while (entry != null) {
if (key.equals(entry.key)) {
/* If an entry for the key exists just update the value. */
entry.value = value;
entry.valid = true;
return;
} else {
prev = entry;
entry = entry.next;
}
}
/* If no entry with a matching key is found, create a new entry. */
if (size == capacity) {
Error.abort("Exceeding storage capacity in CHashMap.");
}
entry = store[size++];
/* Initialise the new entry. */
entry.key = key;
entry.value = value;
entry.next = null;
entry.valid = true;
/* Added by Frank Zeyda */
prev.next = entry;
/* End of Addition */
}
}
/**
* Removes an value from the map that is associated with a given key.
*
* @param key
* the key of the value to be remove from the map
* @param the
* removed value
*/
public Object remove(Object key) {
int idx = hash(key);
HashEntry entry = table[idx];
while (entry != null) {
if (key.equals(entry.key)) {
/*
* We cannot just remove the entry as this results in a memory
* leak. Instead, we mark the entry as invalid so that it can be
* reused.
*/
entry.valid = false;
return entry.value;
}
/* Sift through elements in the bucket. */
entry = entry.next;
}
return null;
}
/**
* Returns all keys within the hash map as a Set.
*
* The implementation is not very efficient here as it traverses the table
* array index by index. However, for the sake of our example this may be
* sufficient; a more efficient implementation would back the map by a key
* and value set. We note that this method allocates memory in whatever
* scope it is called, namely for the Set object returned.
*
* @return keys within the hash map viewed as a Set
*/
//TODO: illegal references
public Set<Object> keySet() {
HashSet<Object> set = new HashSet<Object>();
for (int idx = 0; idx < table.length; idx++) {
HashEntry entry = table[idx];
while (entry != null) {
set.add(entry.key);
entry = entry.next;
}
}
return set;
}
}