/*
* This code is distributed under The GNU Lesser General Public License (LGPLv3)
* Please visit GNU site for LGPLv3 http://www.gnu.org/copyleft/lesser.html
*
* Copyright Denis Pavlov 2009
* Web: http://www.genericdtoassembler.org
* SVN: https://svn.code.sf.net/p/geda-genericdto/code/trunk/
* SVN (mirror): http://geda-genericdto.googlecode.com/svn/trunk/
*/
package com.inspiresoftware.lib.dto.geda.assembler.extension.impl;
/**
* This is an adoption of java.util.HashMap implementation to better support caching
* mechanisms of GeDA.
*
* Basic principles to cache against hashCode, so we want to refrain from using
* Integer since we use primitive int.
*
* @param <V> type of value
*
* @since 2.1.0
*/
public class IntHashTable<V>{
/**
* The default initial capacity - MUST be a power of two.
*/
static final int DEFAULT_INITIAL_CAPACITY = 16;
/**
* The maximum capacity, used if a higher value is implicitly specified
* by either of the constructors with arguments.
* MUST be a power of two <= 1<<30.
*/
static final int MAXIMUM_CAPACITY = 1 << 30;
/**
* The load factor used when none specified in constructor.
*/
static final float DEFAULT_LOAD_FACTOR = 0.75f;
/**
* The table, resized as necessary. Length MUST Always be a power of two.
*/
transient Entry[] table;
/**
* The number of key-value mappings contained in this map.
*/
transient int size;
/**
* The next size value at which to resize (capacity * load factor).
* @serial
*/
int threshold;
/**
* The load factor for the hash table.
*
* @serial
*/
final float loadFactor;
/**
* The number of times this HashMap has been structurally modified
* Structural modifications are those that change the number of mappings in
* the HashMap or otherwise modify its internal structure (e.g.,
* rehash). This field is used to make iterators on Collection-views of
* the HashMap fail-fast. (See ConcurrentModificationException).
*/
transient volatile int modCount;
/**
* Constructs an empty <tt>HashMap</tt> with the default initial capacity
* (16) and the default load factor (0.75).
*/
public IntHashTable() {
this.loadFactor = DEFAULT_LOAD_FACTOR;
threshold = (int)(DEFAULT_INITIAL_CAPACITY * DEFAULT_LOAD_FACTOR);
table = new Entry[DEFAULT_INITIAL_CAPACITY];
}
/**
* Returns index for hash code h.
*/
static int indexFor(int h, int length) {
return h & (length-1);
}
/**
* Returns the number of key-value mappings in this map.
*
* @return the number of key-value mappings in this map
*/
public int size() {
return size;
}
/**
* Returns <tt>true</tt> if this map contains no key-value mappings.
*
* @return <tt>true</tt> if this map contains no key-value mappings
*/
public boolean isEmpty() {
return size == 0;
}
/**
* @param key The key whose presence in this map is to be tested
* @return Value for given key
*/
public V get(int key) {
final Entry<V> val = getEntry(key);
if (val != null) {
return val.value;
}
return null;
}
/**
* @param key The key whose presence in this map is to be tested
* @return <tt>true</tt> if this map contains a mapping for the specified
* key.
*/
public boolean containsKey(int key) {
return getEntry(key) != null;
}
/**
* @param key The key whose presence in this map is to be tested
* @return Value entry for given key
*/
final Entry<V> getEntry(int key) {
int hash = key;
for (Entry<V> e = table[indexFor(hash, table.length)];
e != null;
e = e.next) {
int k;
if (e.hash == hash &&
((k = e.key) == key || (key == k)))
return e;
}
return null;
}
/**
* Associates the specified value with the specified key in this map.
* If the map previously contained a mapping for the key, the old
* value is replaced.
*
* @param key key with which the specified value is to be associated
* @param value value to be associated with the specified key
* @return the previous value associated with <tt>key</tt>, or
* <tt>null</tt> if there was no mapping for <tt>key</tt>.
* (A <tt>null</tt> return can also indicate that the map
* previously associated <tt>null</tt> with <tt>key</tt>.)
*/
public V put(int key, V value) {
int hash = key;
int i = indexFor(hash, table.length);
for (Entry<V> e = table[i]; e != null; e = e.next) {
int k;
if (e.hash == hash && ((k = e.key) == key || key == k)) {
V oldValue = e.value;
e.value = value;
return oldValue;
}
}
modCount++;
addEntry(hash, key, value, i);
return null;
}
/**
* Rehashes the contents of this map into a new array with a
* larger capacity. This method is called automatically when the
* number of keys in this map reaches its threshold.
*
* If current capacity is MAXIMUM_CAPACITY, this method does not
* resize the map, but sets threshold to Integer.MAX_VALUE.
* This has the effect of preventing future calls.
*
* @param newCapacity the new capacity, MUST be a power of two;
* must be greater than current capacity unless current
* capacity is MAXIMUM_CAPACITY (in which case value
* is irrelevant).
*/
void resize(int newCapacity) {
Entry[] oldTable = table;
int oldCapacity = oldTable.length;
if (oldCapacity == MAXIMUM_CAPACITY) {
threshold = Integer.MAX_VALUE;
return;
}
Entry[] newTable = new Entry[newCapacity];
transfer(newTable);
table = newTable;
threshold = (int)(newCapacity * loadFactor);
}
/**
* Transfers all entries from current table to newTable.
*/
void transfer(Entry[] newTable) {
Entry[] src = table;
int newCapacity = newTable.length;
for (int j = 0; j < src.length; j++) {
Entry<V> e = src[j];
if (e != null) {
src[j] = null;
do {
Entry<V> next = e.next;
int i = indexFor(e.hash, newCapacity);
e.next = newTable[i];
newTable[i] = e;
e = next;
} while (e != null);
}
}
}
/**
* Removes the mapping for the specified key from this map if present.
*
* @param key key whose mapping is to be removed from the map
* @return the previous value associated with <tt>key</tt>, or
* <tt>null</tt> if there was no mapping for <tt>key</tt>.
* (A <tt>null</tt> return can also indicate that the map
* previously associated <tt>null</tt> with <tt>key</tt>.)
*/
public V remove(int key) {
Entry<V> e = removeEntryForKey(key);
return (e == null ? null : e.value);
}
/**
* Removes and returns the entry associated with the specified key
* in the HashMap. Returns null if the HashMap contains no mapping
* for this key.
*/
final Entry<V> removeEntryForKey(int key) {
int hash = key;
int i = indexFor(hash, table.length);
Entry<V> prev = table[i];
Entry<V> e = prev;
while (e != null) {
Entry<V> next = e.next;
int k;
if (e.hash == hash &&
((k = e.key) == key || (key == k))) {
modCount++;
size--;
if (prev == e)
table[i] = next;
else
prev.next = next;
return e;
}
prev = e;
e = next;
}
return e;
}
/**
* Removes all of the mappings from this map.
* The map will be empty after this call returns.
*/
public void clear() {
modCount++;
Entry[] tab = table;
for (int i = 0; i < tab.length; i++)
tab[i] = null;
size = 0;
}
static class Entry<V> {
final int key;
V value;
Entry<V> next;
final int hash;
/**
* Creates new entry.
*/
Entry(int h, int k, V v, Entry<V> n) {
value = v;
next = n;
key = k;
hash = h;
}
public final int getKey() {
return key;
}
public final V getValue() {
return value;
}
public final V setValue(V newValue) {
V oldValue = value;
value = newValue;
return oldValue;
}
public final boolean equals(Object o) {
if (!(o instanceof IntHashTable.Entry))
return false;
IntHashTable.Entry e = (IntHashTable.Entry)o;
int k1 = getKey();
int k2 = e.getKey();
if (k1 == k2) {
Object v1 = getValue();
Object v2 = e.getValue();
if (v1 == v2 || (v1 != null && v1.equals(v2)))
return true;
}
return false;
}
public final int hashCode() {
return (key) ^
(value==null ? 0 : value.hashCode());
}
public final String toString() {
return getKey() + "=" + getValue();
}
}
/**
* Adds a new entry with the specified key, value and hash code to
* the specified bucket. It is the responsibility of this
* method to resize the table if appropriate.
*
* Subclass overrides this to alter the behavior of put method.
*/
void addEntry(int hash, int key, V value, int bucketIndex) {
Entry<V> e = table[bucketIndex];
table[bucketIndex] = new Entry<V>(hash, key, value, e);
if (size++ >= threshold)
resize(2 * table.length);
}
/**
* Returns all keys for this map.
*
* @return key array
*/
public int[] keysArray() {
int[] keys = new int[size];
int keyIndex = 0;
for (int bucketIndex = 0; bucketIndex < table.length && keyIndex < keys.length; bucketIndex++) {
Entry<V> e = table[bucketIndex];
do {
keys[keyIndex++] = e != null ? e.getKey() : 0;
} while (keyIndex < keys.length && e != null && ((e = e.next) != null));
}
return keys;
}
}