/*******************************************************************************
* Copyright (c) 2009-2013 CWI
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* * Jurgen J. Vinju - Jurgen.Vinju@cwi.nl - CWI
* * Arnold Lankamp - Arnold.Lankamp@cwi.nl
*******************************************************************************/
package org.rascalmpl.parser.gtd.util;
@SuppressWarnings({"unchecked", "cast"})
public class PointerKeyedHashMap<K, V>{
private final static int DEFAULT_INITIAL_LOG_SIZE = 5;
private int hashMask;
private Entry<K, V>[] data;
private int threshold;
private int load;
public PointerKeyedHashMap(){
super();
int tableSize = 1 << DEFAULT_INITIAL_LOG_SIZE;
hashMask = tableSize - 1;
data = (Entry<K, V>[]) new Entry[tableSize];
threshold = tableSize;
load = 0;
}
public PointerKeyedHashMap(int initialLogSize){
super();
int tableSize = 1 << initialLogSize;
hashMask = tableSize - 1;
data = (Entry<K, V>[]) new Entry[tableSize];
threshold = tableSize;
load = 0;
}
public PointerKeyedHashMap(int initialLogSize, float loadFactor){
super();
int tableSize = 1 << initialLogSize;
hashMask = tableSize - 1;
data = (Entry<K, V>[]) new Entry[tableSize];
threshold = (int) (tableSize * loadFactor);
load = 0;
}
private void rehash(){
Entry<K, V>[] oldData = data;
int tableSize = data.length << 1;
int newHashMask = tableSize - 1;
Entry<K, V>[] newData = (Entry<K, V>[]) new Entry[tableSize];
// Construct temporary entries that function as roots for the entries that remain in the current bucket
// and those that are being shifted.
Entry<K, V> currentEntryRoot = new Entry<K, V>(0, null, null, null);
Entry<K, V> shiftedEntryRoot = new Entry<K, V>(0, null, null, null);
int oldSize = oldData.length;
for(int i = oldSize - 1; i >= 0; i--){
Entry<K, V> e = oldData[i];
if(e != null){
Entry<K, V> lastCurrentEntry = currentEntryRoot;
Entry<K, V> lastShiftedEntry = shiftedEntryRoot;
int lastPosition = -1;
do{
int position = e.hash & newHashMask;
if(position == i){
if(position != lastPosition) lastCurrentEntry.next = e;
lastCurrentEntry = e;
}else{
if(position != lastPosition) lastShiftedEntry.next = e;
lastShiftedEntry = e;
}
e = e.next;
}while(e != null);
// Set the next pointers of the last entries in the buckets to null.
lastCurrentEntry.next = null;
lastShiftedEntry.next = null;
newData[i] = currentEntryRoot.next;
newData[i | oldSize] = shiftedEntryRoot.next; // The entries got shifted by the size of the old table.
}
}
threshold <<= 1;
data = newData;
hashMask = newHashMask;
}
private void ensureCapacity(){
if(load > threshold){
rehash();
}
}
public V store(K key, V value){
int hash = key.hashCode();
int position = hash & hashMask;
// Check if the key is already in here.
Entry<K, V> startEntry = data[position];
if(startEntry != null){
Entry<K, V> entry = startEntry;
do{
if(key == entry.key) return entry.value; // The key is already present.
entry = entry.next;
}while(entry != null);
}
// Couldn't find the key.
ensureCapacity();
data[position] = new Entry<K, V>(hash, key, value, startEntry); // Insert the new entry.
load++;
return value;
}
public V get(K key){
int hash = key.hashCode();
int position = hash & hashMask;
Entry<K, V> entry = data[position];
while(entry != null){
if(key == entry.key) return entry.value;
entry = entry.next;
}
return null;
}
public void putUnsafe(K key, V value){
int hash = key.hashCode();
int position = hash & hashMask;
ensureCapacity();
data[position] = new Entry<K, V>(hash, key, value, data[position]);
load++;
}
public int size(){
return load;
}
private static class Entry<K, V>{
public final int hash;
public final K key;
public final V value;
public Entry<K, V> next;
public Entry(int hash, K key, V value, Entry<K, V> next){
super();
this.hash = hash;
this.key = key;
this.value = value;
this.next = next;
}
}
public void clear(){
data = (Entry<K, V>[]) new Entry[data.length];
load = 0;
}
}