/*******************************************************************************
* Copyright (c) 2004, 2014 IBM Corporation and others.
* 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:
* IBM Corporation - initial API and implementation
*******************************************************************************/
package org.eclipse.cdt.core.parser.util;
import java.util.Arrays;
import java.util.Comparator;
/**
* @author ddaoust
* @noextend This class is not intended to be subclassed by clients.
*/
public class HashTable implements Cloneable {
// Prime numbers from http://planetmath.org/goodhashtableprimes
private static final int[] PRIMES = {
17,
29,
53,
97,
193,
389,
769,
1543,
3079,
6151,
12289,
24593,
49157,
98317,
196613,
393241,
786433,
1572869,
3145739,
6291469,
12582917,
25165843,
50331653,
100663319,
201326611,
402653189,
805306457,
1610612741
};
private static final int MIN_HASH_SIZE = 9;
/** @deprecated Don't depend on this implementation detail. @noreference This field is not intended to be referenced by clients. */
@Deprecated
protected static final int minHashSize = MIN_HASH_SIZE - 1;
protected int currEntry = -1;
protected int[] hashTable;
protected int[] nextTable;
public boolean isEmpty() {
return currEntry < 0;
}
public final int size() {
return currEntry + 1;
}
public HashTable(int initialSize) {
if (initialSize >= MIN_HASH_SIZE) {
hashTable = new int[getSuitableHashTableSize(initialSize)];
nextTable = new int[initialSize];
} else {
hashTable = null;
nextTable = null;
}
}
@Override
public Object clone() {
HashTable newTable = null;
try {
newTable = (HashTable) super.clone();
} catch (CloneNotSupportedException e) {
// Shouldn't happen because object supports clone.
return null;
}
int size = capacity();
if (hashTable != null) {
newTable.hashTable = new int[getSuitableHashTableSize(size)];
newTable.nextTable = new int[size];
System.arraycopy(hashTable, 0, newTable.hashTable, 0, hashTable.length);
System.arraycopy(nextTable, 0, newTable.nextTable, 0, nextTable.length);
}
newTable.currEntry = currEntry;
return newTable;
}
protected void resize() {
resize(capacity() << 1);
}
public void clear() {
currEntry = -1;
// Clear the table.
if (hashTable == null)
return;
Arrays.fill(hashTable, 0);
Arrays.fill(nextTable, 0);
}
protected void rehash() {
if (nextTable == null)
return;
// Clear the table (don't call clear() or else the subclasses stuff will be cleared too).
Arrays.fill(hashTable, 0);
Arrays.fill(nextTable, 0);
// Need to rehash everything.
for (int i = 0; i <= currEntry; ++i) {
linkIntoHashTable(i, hash(i));
}
}
protected void resize(int size) {
if (size >= MIN_HASH_SIZE) {
hashTable = new int[getSuitableHashTableSize(size)];
nextTable = new int[size];
// Need to rehash everything.
for (int i = 0; i <= currEntry; ++i) {
linkIntoHashTable(i, hash(i));
}
}
}
private static int getSuitableHashTableSize(int size) {
size += (size + 2) / 3;
if (size < 0)
return Integer.MAX_VALUE; // Integer overflow. Return the max possible size.
int low = 0;
int high = PRIMES.length;
while (low < high) {
int mid = (low + high) >>> 1;
int p = PRIMES[mid];
if (p < size) {
low = mid + 1;
} else if (p > size) {
high = mid;
} else {
return p;
}
}
if (low == PRIMES.length)
return Integer.MAX_VALUE; // Largest prime is not sufficient. Return the max possible size.
return PRIMES[low];
}
protected int hash(int pos) {
// Return the hash value of the element in the key table.
throw new UnsupportedOperationException();
}
final int hashToOffset(int hash) {
int offset = hash % hashTable.length;
if (offset < 0)
offset += hashTable.length - 1;
return offset;
}
protected final void linkIntoHashTable(int i, int hash) {
if (nextTable == null)
return;
int j = hashTable[hash];
if (j == 0) {
hashTable[hash] = i + 1;
} else {
// Need to link.
j--;
int maxIterationsLeft = nextTable.length;
for (int k; (k = nextTable[j]) != 0 && k != j + 1; j = k - 1) {
if (--maxIterationsLeft < 0) {
throw new IllegalStateException("i = " + i + " hash = " + hash + " j = " + j //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
+ "\n nextTable = " + Arrays.toString(nextTable) //$NON-NLS-1$
+ "\n hashTable = " + Arrays.toString(hashTable)); //$NON-NLS-1$
}
}
nextTable[j] = i + 1;
}
}
public final int capacity() {
if (nextTable == null)
return MIN_HASH_SIZE - 1;
return nextTable.length;
}
protected void removeEntry(int i, int hash) {
if (nextTable == null) {
--currEntry;
return;
}
// Remove the hash entry.
if (hashTable[hash] == i + 1) {
hashTable[hash] = nextTable[i];
} else {
// Find entry pointing to me.
int j = hashTable[hash] - 1;
int k;
while ((k = nextTable[j]) != 0 && k != i + 1) {
j = k - 1;
}
nextTable[j] = nextTable[i];
}
if (i < currEntry) {
// Shift everything over.
System.arraycopy(nextTable, i + 1, nextTable, i, currEntry - i);
// Adjust hash and next entries for things that moved.
for (int j = 0; j < hashTable.length; ++j) {
int k = hashTable[j] - 1;
if (k > i)
hashTable[j] = k;
}
for (int j = 0; j < nextTable.length; ++j) {
int k = nextTable[j] - 1;
if (k > i)
nextTable[j] = k;
}
}
// Last entry is now free.
nextTable[currEntry] = 0;
--currEntry;
}
public final void sort(Comparator<Object> c) {
if (size() > 1) {
quickSort(c, 0, size() - 1);
rehash();
}
}
private void quickSort(Comparator<Object> c, int p, int r) {
if (p < r) {
int q = partition(c, p, r);
if (p < q)
quickSort(c, p, q);
if (++q < r)
quickSort(c, q, r);
}
}
protected int partition(Comparator<Object> c, int p, int r) {
throw new UnsupportedOperationException();
}
/**
* For debugging only.
* @noreference This method is not intended to be referenced by clients.
*/
public void dumpNexts() {
if (nextTable == null)
return;
for (int i = 0; i < nextTable.length; ++i) {
if (nextTable[i] == 0)
continue;
System.out.print(i);
for (int j = nextTable[i] - 1; j >= 0; j = nextTable[j] - 1)
System.out.print(" -> " + j); //$NON-NLS-1$
System.out.println(""); //$NON-NLS-1$
}
}
/**
* Returns the number of collisions.
* For debugging only.
* @noreference This method is not intended to be referenced by clients.
*/
public int countCollisions() {
if (nextTable == null)
return 0;
int numCollisions = 0;
for (int i = 0; i < nextTable.length; ++i) {
if (nextTable[i] != 0)
numCollisions++;
}
return numCollisions;
}
}