/* * 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; } }