/*
* Kodkod -- Copyright (c) 2005-present, Emina Torlak
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package kodkod.util.collections;
import java.util.AbstractSet;
import java.util.Collection;
import java.util.Iterator;
import java.util.NoSuchElementException;
/**
* Implements the <tt>Set</tt> interface, backed by a hash table.
* It makes no guarantees as to the
* iteration order of the set; in particular, it does not guarantee that the
* order will remain constant over time. This class does not permit the <tt>null</tt>
* element.<p>
*
* This class offers constant time performance for the basic operations
* (<tt>add</tt>, <tt>remove</tt>, <tt>contains</tt> and <tt>size</tt>),
* assuming the hash function disperses the elements properly among the
* buckets. Iterating over this set requires time proportional to the sum of
* the <tt>HashSet</tt> instance's size (the number of elements) plus the
* "capacity" of the backing map (the number of
* buckets). Thus, it's very important not to set the initial capacity too
* high (or the load factor too low) if iteration performance is important.<p>
*
* <p>This set differs from Java's HashSet in that it provides methods for
* retrieving elements with a particular <tt>hashcode</tt>. This makes it
* easy to set as a cache in which cached objects' hashcodes are their keys.</p>
*
* <p><b>Note that this implementation is not synchronized.</b>
* The iterators returned by this class's <tt>iterator</tt> method are
* not <i>fail-fast</i></p>
*
* @specfield elts: set T
* @author Emina Torlak
*/
public final class CacheSet<E> extends AbstractSet<E> {
/* implementation adapted from java.util.HashMap and java.util.HashSet */
/**
* The default initial capacity - MUST be a power of two.
*/
private 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.
*/
private static final int MAXIMUM_CAPACITY = 1 << 30;
/**
* The load factor used when none specified in constructor.
**/
private static final float DEFAULT_LOAD_FACTOR = 0.75f;
/**
* The table, resized as necessary. Length MUST Always be a power of two.
*/
private Entry<E>[] table;
/**
* The number of key-value mappings contained in this identity hash map.
*/
private int size;
/**
* The next size value at which to resize (capacity * load factor).
*/
private int threshold;
/**
* The load factor for the hash table.
*/
final float loadFactor;
/**
* Constructs a new, empty set; the backing map has
* default initial capacity (16) and load factor (0.75).
* @ensures no this.elts'
*/
@SuppressWarnings("unchecked")
public CacheSet() {
loadFactor = DEFAULT_LOAD_FACTOR;
threshold = (int)(DEFAULT_INITIAL_CAPACITY * DEFAULT_LOAD_FACTOR);
table = new Entry[DEFAULT_INITIAL_CAPACITY];
}
/**
* Constructs a new, empty set; the backing map has
* the specified initial capacity and the specified load factor.
*
* @param initialCapacity the initial capacity of the hash map.
* @param loadFactor the load factor of the hash map.
* @throws IllegalArgumentException if the initial capacity is less
* than zero, or if the load factor is nonpositive.
* @ensures no this.elts'
*/
@SuppressWarnings("unchecked")
public CacheSet(int initialCapacity, float loadFactor) {
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal initial capacity: " +
initialCapacity);
if (initialCapacity > MAXIMUM_CAPACITY)
initialCapacity = MAXIMUM_CAPACITY;
if (loadFactor <= 0 || Float.isNaN(loadFactor))
throw new IllegalArgumentException("Illegal load factor: " +
loadFactor);
// Find a power of 2 >= initialCapacity
int capacity = 1;
while (capacity < initialCapacity)
capacity <<= 1;
this.loadFactor = loadFactor;
threshold = (int)(capacity * loadFactor);
table = new Entry[capacity];
}
/**
* Constructs a new set containing the elements in the specified
* collection. The <tt>HashMap</tt> is created with default load factor
* (0.75) and an initial capacity sufficient to contain the elements in
* the specified collection.
*
* @param c the collection whose elements are to be placed into this set.
* @throws NullPointerException if the specified collection is null.
*/
public CacheSet(Collection<? extends E> c) {
this(c.size(), .75f);
addAll(c);
}
/**
* Returns a hash value for the specified integer.
* The shift distances in this function were chosen as the result
* of an automated search over the entire four-dimensional search space.
*/
private static int hash(int h) {
h += ~(h << 9);
h ^= (h >>> 14);
h += (h << 4);
h ^= (h >>> 10);
return h;
}
/**
* Returns a hash value for the specified object. In addition to
* the object's own hashCode, this method applies a "supplemental
* hash function," which defends against poor quality hash functions.
* This is critical because HashMap uses power-of two length
* hash tables.<p>
*
* The shift distances in this function were chosen as the result
* of an automated search over the entire four-dimensional search space.
*/
private static int hash(Object x) {
return hash(x.hashCode());
}
/**
* Returns index for hash code h.
*/
private static int indexFor(int h, int length) {
return h & (length-1);
}
/**
* Returns the number of elements in this set.
* @return #this.elts
* @see java.util.Set#size()
*/
public int size() {
return size;
}
/**
* Returns true if this set is empty.
* @return no this.elts
* @see java.util.Set#isEmpty()
*/
public boolean isEmpty() {
return size==0;
}
/**
* Returns true if this set contains the given element.
* @return elt in this.elts
* @throws NullPointerException elt = null
* @see java.util.Set#contains(java.lang.Object)
*/
public boolean contains(Object elt) {
Entry<E> e = table[indexFor(hash(elt), table.length)];
while (e != null) {
if (e.val.equals(elt))
return true;
e = e.next;
}
return false;
}
/**
* Returns an iterator over the elements in this set.
* @return an iterator over this.elts.
* @see java.util.Set#iterator()
*/
public Iterator<E> iterator() {
return new SetIterator();
}
/**
* Adds the given element to this set, if not already present.
* @ensures this.elts' = this.elts + elt
* @throws NullPointerException elt = null
* @return elt !in this.elts
*/
public boolean add(E elt) {
final int i = indexFor(hash(elt), table.length);
for (Entry<E> e = table[i]; e != null; e = e.next) {
if (e.val.equals(elt)) {
return false;
}
}
table[i] = new Entry<E>(elt, table[i]);
if (size++ >= threshold)
resize(2 * table.length);
return true;
}
/**
* 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).
*/
@SuppressWarnings("unchecked")
private void resize(int newCapacity) {
Entry<E>[] oldTable = table;
int oldCapacity = oldTable.length;
if (oldCapacity == MAXIMUM_CAPACITY) {
threshold = Integer.MAX_VALUE;
return;
}
Entry<E>[] newTable = new Entry[newCapacity];
transfer(newTable);
table = newTable;
threshold = (int)(newCapacity * loadFactor);
}
/**
* Transfer all entries from current table to newTable.
*/
private void transfer(Entry<E>[] newTable) {
Entry<E>[] src = table;
int newCapacity = newTable.length;
for (int j = 0; j < src.length; j++) {
Entry<E> e = src[j];
if (e != null) {
src[j] = null;
do {
Entry<E> next = e.next;
int i = indexFor(hash(e.val), newCapacity);
e.next = newTable[i];
newTable[i] = e;
e = next;
} while (e != null);
}
}
}
/**
* Removes the specified object from this set, if present.
* @ensures this.elts' = this.elts - elt
* @return elt in this.elts
* @throws NullPointerException elt = null
* @see java.util.Set#remove(java.lang.Object)
*/
public boolean remove(Object elt) {
int i = indexFor(hash(elt), table.length);
Entry<E> prev = table[i];
Entry<E> e = prev;
while (e != null) {
Entry<E> next = e.next;
if (e.val.equals(elt)) {
size--;
if (prev == e)
table[i] = next;
else
prev.next = next;
return true;
}
prev = e;
e = next;
}
return false;
}
/**
* Returns an iterator over the elements
* whose hashcode() method returns the given hash.
* @return an iterator over {e: this.elts | e.hashCode() = hash }
*/
public Iterator<E> get(final int hash) {
final int i = indexFor(hash(hash), table.length);
return new Iterator<E>() {
Entry<E> current = null, next = table[i];
public boolean hasNext() {
while(next != null && next.val.hashCode()!=hash) {
next = next.next;
}
return next != null;
}
public E next() {
if (!hasNext()) throw new NoSuchElementException();
current = next;
next = next.next;
return current.val;
}
public void remove() {
if (current==null)
throw new IllegalStateException();
Entry<E> prev = table[i];
Entry<E> e = prev;
while (e.next != current) {
prev = e;
e = e.next;
}
size--;
if (prev==e)
table[i] = next;
else
prev.next = next;
current = null;
}
};
}
/**
* Removes all elements from this set.
* @ensures no this.elts'
* @see java.util.Set#clear()
*/
public void clear() {
for (int i = 0; i < table.length; i++)
table[i] = null;
size = 0;
}
private static final class Entry<T> {
Entry<T> next;
T val;
Entry(T val, Entry<T> next) {
this.val = val;
this.next = next;
}
}
private final class SetIterator implements Iterator<E> {
Entry<E> next; // next entry to return
int index; // current slot
Entry<E> current; // current entry
SetIterator() {
index = table.length;
next = null;
if (size != 0) { // advance to first entry
while (index > 0 && (next = table[--index]) == null)
;
}
}
public boolean hasNext() {
return next != null;
}
public E next() {
Entry<E> e = next;
if (e == null)
throw new NoSuchElementException();
Entry<E> n = e.next;
while (n == null && index > 0)
n = table[--index];
next = n;
return (current = e).val;
}
public void remove() {
if (current == null)
throw new IllegalStateException();
CacheSet.this.remove(current.val);
current = null;
}
}
}