package org.eclipse.jdt.internal.core.util; | |
/* | |
* (c) Copyright IBM Corp. 2000, 2001. | |
* All Rights Reserved. | |
*/ | |
import java.util.*; | |
/** | |
* A lookup table whose keys and values are Objects. | |
* A lookup table is like a Hashtable, but uses linear probing to | |
* resolve collisions rather than a linked list of hash table entries. | |
*/ | |
public class LookupTable extends Dictionary implements Cloneable { | |
protected int count; | |
protected float loadFactor; | |
protected int threshold; | |
protected Object[] keys; | |
protected Object[] values; | |
protected int modCount; | |
// Types of Enumeration | |
protected static final int KEYS = 0; | |
protected static final int VALUES = 1; | |
/** | |
* A lookup table enumerator class. | |
*/ | |
protected class Enumerator implements Enumeration { | |
protected int type; | |
protected Object[] table; | |
protected int index; | |
protected Object entry; | |
/** | |
* The modCount value that the enumeration believes that the backing | |
* lookup table should have. If this expectation is violated, the enumeration | |
* has detected concurrent modification. | |
*/ | |
protected int expectedModCount = modCount; | |
Enumerator(int type) { | |
this.type = type; | |
table = (type == KEYS ? keys : values); | |
index = table.length; | |
} | |
public boolean hasMoreElements() { | |
while (entry == null && index > 0) | |
entry = table[--index]; | |
return entry != null; | |
} | |
public Object nextElement() { | |
if (modCount != expectedModCount) | |
throw new IllegalStateException(); | |
while (entry == null && index > 0) | |
entry = table[--index]; | |
if (entry != null) { | |
Object next = entry; | |
entry = null; | |
return next; | |
} | |
throw new NoSuchElementException("LookupTable Enumerator"/*nonNLS*/); | |
} | |
} | |
/** | |
* Constructs a new, empty LookupTable with a default capacity (11) and load | |
* factor (0.5). | |
*/ | |
public LookupTable() { | |
this(11, 0.5f); | |
} | |
/** | |
* Constructs a new, empty LookupTable with the specified initial capacity | |
* and default load factor (0.5). | |
* | |
* @param initialCapacity the initial capacity of the lookup table. | |
* @exception IllegalArgumentException if the initial capacity is less | |
* than zero. | |
*/ | |
public LookupTable(int initialCapacity) { | |
this(initialCapacity, 0.5f); | |
} | |
/** | |
* Constructs a new, empty LookupTable with the specified initial | |
* capacity and the specified load factor. | |
* | |
* @param initialCapacity the initial capacity of the lookup table. | |
* @param loadFactor the load factor of the lookup table. | |
* @exception IllegalArgumentException if the initial capacity is less | |
* than zero, or if the load factor is nonpositive. | |
*/ | |
public LookupTable(int initialCapacity, float loadFactor) { | |
if (initialCapacity < 0) | |
throw new IllegalArgumentException("Illegal Capacity: "/*nonNLS*/+ | |
initialCapacity); | |
if (loadFactor <= 0) | |
throw new IllegalArgumentException("Illegal Load: "/*nonNLS*/+loadFactor); | |
if (initialCapacity==0) | |
initialCapacity = 1; | |
this.loadFactor = loadFactor; | |
keys = new Object[initialCapacity]; | |
values = new Object[initialCapacity]; | |
threshold = (int)(initialCapacity * loadFactor); | |
} | |
/** | |
* Clears this lookup table so that it contains no keys. | |
*/ | |
public synchronized void clear() { | |
Object tab1[] = keys; | |
Object tab2[] = values; | |
modCount++; | |
for (int index = keys.length; --index >= 0; ) { | |
tab1[index] = null; | |
tab2[index] = null; | |
} | |
count = 0; | |
} | |
/** | |
* Creates a shallow copy of this lookup table. All the structure of the | |
* lookup table itself is copied, but the keys and values are not cloned. | |
* This is a relatively expensive operation. | |
* | |
* @return a clone of the lookup table. | |
*/ | |
public synchronized Object clone() { | |
try { | |
LookupTable t = (LookupTable) super.clone(); | |
int size = keys.length; | |
t.keys = new Object[size]; | |
t.values = new Object[size]; | |
System.arraycopy(keys, 0, t.keys, 0, size); | |
System.arraycopy(values, 0, t.values, 0, size); | |
t.modCount = 0; | |
return t; | |
} catch (CloneNotSupportedException e) { | |
// this shouldn't happen, since we are Cloneable | |
throw new InternalError(); | |
} | |
} | |
/** | |
* Tests if some key maps into the specified value in this lookup table. | |
* This operation is more expensive than the <code>containsKey</code> | |
* method.<p> | |
* | |
* Note that this method is identical in functionality to containsValue, | |
* (which is part of the Map interface in the collections framework). | |
* | |
* @param value a value to search for. | |
* @return <code>true</code> if and only if some key maps to the | |
* <code>value</code> argument in this lookup table as | |
* determined by the <tt>equals</tt> method; | |
* <code>false</code> otherwise. | |
* @exception NullPointerException if the value is <code>null</code>. | |
* @see #containsKey(Object) | |
* @see #containsValue(Object) | |
* @see Map | |
*/ | |
public synchronized boolean contains(Object value) { | |
if (value == null) { | |
throw new NullPointerException(); | |
} | |
Object tab[] = values; | |
for (int i = tab.length ; i-- > 0 ;) { | |
if (tab[i].equals(value)) { | |
return true; | |
} | |
} | |
return false; | |
} | |
/** | |
* Tests if the specified object is a key in this lookup table. | |
* | |
* @param key possible key. | |
* @return <code>true</code> if and only if the specified object | |
* is a key in this lookup table, as determined by the | |
* <tt>equals</tt> method; <code>false</code> otherwise. | |
* @see #contains(Object) | |
*/ | |
public synchronized boolean containsKey(Object key) { | |
Object[] tab = keys; | |
int size = tab.length; | |
int index = (key.hashCode() & 0x7FFFFFFF) % size; | |
for (int i = index; i < size; ++i) { | |
Object testKey = tab[i]; | |
if (testKey == null) return false; | |
if (testKey.equals(key)) return true; | |
} | |
for (int i = 0; i < index; ++i) { | |
Object testKey = tab[i]; | |
if (testKey == null) return false; | |
if (testKey.equals(key)) return true; | |
} | |
return false; | |
} | |
/** | |
* Returns an enumeration of the values in this lookup table. | |
* Use the Enumeration methods on the returned object to fetch the elements | |
* sequentially. | |
* | |
* @return an enumeration of the values in this lookup table. | |
* @see java.util.Enumeration | |
* @see #keys() | |
* @see #values() | |
* @see Map | |
*/ | |
public synchronized Enumeration elements() { | |
return new Enumerator(VALUES); | |
} | |
/** | |
* Increases the capacity of and internally reorganizes this | |
* lookup table, in order to accommodate and access its entries more | |
* efficiently. This method is called automatically when the | |
* number of keys in the lookup table exceeds this lookup table's capacity | |
* and load factor. | |
*/ | |
protected void expand() { | |
rehash(keys.length * 2 + 1); | |
} | |
/** | |
* Returns the value to which the specified key is mapped in this lookup table. | |
* | |
* @param key a key in the lookup table. | |
* @return the value to which the key is mapped in this lookup table; | |
* <code>null</code> if the key is not mapped to any value in | |
* this lookup table. | |
* @see #put(Object, Object) | |
*/ | |
public synchronized Object get(Object key) { | |
Object[] tab = keys; | |
int size = tab.length; | |
int index = (key.hashCode() & 0x7FFFFFFF) % size; | |
for (int i = index; i < size; ++i) { | |
Object testKey = tab[i]; | |
if (testKey == null) return null; | |
if (testKey.equals(key)) return values[i]; | |
} | |
for (int i = 0; i < index; ++i) { | |
Object testKey = tab[i]; | |
if (testKey == null) return null; | |
if (testKey.equals(key)) return values[i]; | |
} | |
return null; | |
} | |
/** | |
* Tests if this lookup table maps no keys to values. | |
* | |
* @return <code>true</code> if this lookup table maps no keys to values; | |
* <code>false</code> otherwise. | |
*/ | |
public boolean isEmpty() { | |
return count == 0; | |
} | |
/** | |
* Returns an enumeration of the keys in this lookup table. | |
* | |
* @return an enumeration of the keys in this lookup table. | |
* @see Enumeration | |
* @see #elements() | |
* @see #keySet() | |
* @see Map | |
*/ | |
public synchronized Enumeration keys() { | |
return new Enumerator(KEYS); | |
} | |
/** | |
* Maps the specified <code>key</code> to the specified | |
* <code>value</code> in this lookup table. Neither the key nor the | |
* value can be <code>null</code>. <p> | |
* | |
* The value can be retrieved by calling the <code>get</code> method | |
* with a key that is equal to the original key. | |
* | |
* @param key the lookup table key. | |
* @param value the value. | |
* @return the previous value of the specified key in this lookup table, | |
* or <code>null</code> if it did not have one. | |
* @exception NullPointerException if the key or value is | |
* <code>null</code>. | |
* @see Object#equals(Object) | |
* @see #get(Object) | |
*/ | |
public synchronized Object put(Object key, Object value) { | |
// Make sure the value is not null | |
if (value == null) { | |
throw new NullPointerException(); | |
} | |
Object[] tab = keys; | |
int size = tab.length; | |
int hashIndex = (key.hashCode() & 0x7FFFFFFF) % size; | |
for (int i = hashIndex; i < size; ++i) { | |
Object element = tab[i]; | |
if (element == null) { | |
keys[i] = key; | |
values[i] = value; | |
if (++count > threshold) { | |
expand(); | |
} | |
++modCount; | |
return null; | |
} | |
if (element.equals(key)) { | |
Object oldValue = values[i]; | |
values[i] = value; | |
return oldValue; | |
} | |
} | |
for (int i = 0; i < hashIndex; ++i) { | |
Object element = tab[i]; | |
if (element == null) { | |
keys[i] = key; | |
values[i] = value; | |
if (++count > threshold) { | |
expand(); | |
} | |
++modCount; | |
return null; | |
} | |
if (element.equals(key)) { | |
Object oldValue = values[i]; | |
values[i] = value; | |
return oldValue; | |
} | |
} | |
expand(); | |
return put(key, value); | |
} | |
protected void rehash(int newSize) { | |
/* | |
System.out.println("LookupTable.rehash()"); | |
System.out.println(" oldSize= " + keys.length); | |
System.out.println(" count= " + count); | |
System.out.println(" threshold= " + threshold); | |
System.out.println(" newSize= " + newSize); | |
*/ | |
Object[] newKeys = new Object[newSize]; | |
Object[] newValues = new Object[newSize]; | |
if (count > 0) { | |
Object[] oldKeys = keys; | |
Object[] oldValues = values; | |
for (int i = keys.length; i-- > 0; ) { | |
Object key = oldKeys[i]; | |
if (key != null) { | |
int hashIndex = (key.hashCode() & 0x7FFFFFFF) % newSize; | |
while (newKeys[hashIndex] != null) { | |
if (++hashIndex >= newSize) | |
hashIndex = 0; | |
} | |
newKeys[hashIndex] = key; | |
newValues[hashIndex] = oldValues[i]; | |
} | |
} | |
} | |
keys = newKeys; | |
values = newValues; | |
threshold = (int) (newSize * loadFactor); | |
// sanityCheck("rehash"); | |
} | |
/** | |
* The element at target has been removed. Move the elements | |
* to keep the receiver properly hashed. | |
*/ | |
protected void rehashTo(int index) { | |
int target = index; | |
Object[] tab = keys; | |
int size = tab.length; | |
for (;;) { | |
if (++index >= size) index = 0; | |
Object element = tab[index]; | |
if (element == null) break; | |
int hashIndex = (element.hashCode() & 0x7FFFFFFF) % size; | |
if (index < target | |
? hashIndex <= target && hashIndex > index | |
: hashIndex <= target || hashIndex > index) { | |
keys[target] = element; | |
values[target] = values[index]; | |
target = index; | |
} | |
} | |
keys[target] = null; | |
values[target] = null; | |
} | |
/** | |
* Removes the key (and its corresponding value) from this | |
* lookup table. This method does nothing if the key is not in the lookup table. | |
* | |
* @param key the key that needs to be removed. | |
* @return the value to which the key had been mapped in this lookup table, | |
* or <code>null</code> if the key did not have a mapping. | |
*/ | |
public synchronized Object remove(Object key) { | |
Object[] tab = keys; | |
int size = tab.length; | |
int hashIndex = (key.hashCode() & 0x7FFFFFFF) % size; | |
for (int i = hashIndex; i < size; ++i) { | |
Object element = tab[i]; | |
if (element == null) return null; | |
if (element.equals(key)) { | |
Object oldValue = values[i]; | |
rehashTo(i); | |
--count; | |
// sanityCheck("remove"); | |
++modCount; | |
return oldValue; | |
} | |
} | |
for (int i = 0; i < hashIndex; ++i) { | |
Object element = tab[i]; | |
if (element == null) return null; | |
if (element.equals(key)) { | |
Object oldValue = values[i]; | |
rehashTo(i); | |
--count; | |
// sanityCheck("remove"); | |
++modCount; | |
return oldValue; | |
} | |
} | |
return null; | |
} | |
protected void sanityCheck(String where) { | |
int n = 0; | |
for (int i = 0; i < keys.length; ++i) { | |
if (keys[i] == null) { | |
if (values[i] != null) { | |
System.err.println("LookupTable sanity check in "/*nonNLS*/ + where + ": key is null, but value isn't at index "/*nonNLS*/ + i); | |
throw new Error(); | |
} | |
} | |
else { | |
if (values[i] == null) { | |
System.err.println("LookupTable sanity check in "/*nonNLS*/ + where + ": value is null, but key isn't at index "/*nonNLS*/ + i); | |
throw new Error(); | |
} | |
else { | |
++n; | |
Object value = get(keys[i]); | |
if (value == null || value != values[i]) { | |
System.err.println("LookupTable sanity check in "/*nonNLS*/ + where + ": key doesn't hash to proper value: "/*nonNLS*/ + keys[i]); | |
throw new Error(); | |
} | |
} | |
} | |
} | |
if (n != count) { | |
System.err.println("LookupTable sanity check in "/*nonNLS*/ + where + ": count is "/*nonNLS*/ + count + " but there are "/*nonNLS*/ + n + " entries"/*nonNLS*/); | |
throw new Error(); | |
} | |
} | |
/** | |
* Returns the number of keys in this LookupTable. | |
* | |
* @return the number of keys in this lookup table. | |
*/ | |
public int size() { | |
return count; | |
} | |
/** | |
* Returns a string representation of this <tt>LookupTable</tt> object | |
* in the form of a set of entries, enclosed in braces and separated | |
* by the ASCII characters "<tt>, </tt>" (comma and space). Each | |
* entry is rendered as the key, an equals sign <tt>=</tt>, and the | |
* associated element, where the <tt>toString</tt> method is used to | |
* convert the key and element to strings. <p>Overrides to | |
* <tt>toString</tt> method of <tt>Object</tt>. | |
* | |
* @return a string representation of this lookup table. | |
*/ | |
public synchronized String toString() { | |
StringBuffer buf = new StringBuffer(); | |
buf.append("{"/*nonNLS*/); | |
boolean first = true; | |
for (int i = 0, max = keys.length; i < max; i++) { | |
if (keys[i] != null) { | |
if (first) { | |
first = false; | |
} | |
else { | |
buf.append(", "/*nonNLS*/); | |
} | |
if (buf.length() > 1000) { | |
buf.append("..."/*nonNLS*/); | |
break; | |
} | |
else { | |
buf.append(keys[i]).append("="/*nonNLS*/).append(values[i]); | |
} | |
} | |
} | |
buf.append("}"/*nonNLS*/); | |
return buf.toString(); | |
} | |
} |