/*
* Copyright (C) 2009-2012 University of Freiburg
*
* This file is part of SMTInterpol.
*
* SMTInterpol is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published
* by the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* SMTInterpol is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with SMTInterpol. If not, see <http://www.gnu.org/licenses/>.
*/
package de.uni_freiburg.informatik.ultimate.util.datastructures;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.ref.ReferenceQueue;
import java.lang.ref.WeakReference;
import java.util.AbstractCollection;
import java.util.Comparator;
import java.util.ConcurrentModificationException;
import java.util.Iterator;
import java.util.NoSuchElementException;
/**
* A UnifyHash is a collection that helps to implement the fly-weight
* design pattern. It stores a set of weak references to objects,
* which can be retrieved by a hash code.
*
* If you use the fly-weight design pattern consequently, you do not
* need to override equals or hash code at all, as two objects that
* equal are always the same. Unfortunately, while you are creating a
* new object, you need to check, if the same object already existed
* and therefore have a method to compare for equality and to
* calculate a hash code for the hash table used in this class.
* Therefore you have to call the put/remove/unify methods with
* some hash code calculated from the internal data. Also the
* unify method is called with a comparator, that checks for
* equality.
*
* The way to use this class is as follows. First follow the
* fly-weight design pattern and make the class immutable. Make the
* constructor private so it can't be called from outside the class.
* Instead add a static method "<code>create</code>" that
* creates new instances, or reuses old ones that have the same data:
*
* <pre>
* private static UnifyHash<MyObject> unifyHash;
*
* **
* * Create a new instance of MyObject with parameters
* * "a" and "child", or returns an already existing one.
* public static MyObject create(int a, MyObject child) {
* int hashcode = a*0x12345679 + child.hashCode();
*
* * First iterate the object with the same hashcode and
* * check if the same object is already present.
* for (MyObject o : unifyHash.iterateHashCode(hashcode)) {
* if (o.a == a && o.child == child) {
* return o;
* }
* }
* * Object was not found, create a new one and put it into
* * the unify hash.
* MyObject o = new MyObject(a, child);
* unifyHash.put(hashcode, o);
* return o;
* }
* </pre>
*/
public class UnifyHash<E> extends AbstractCollection<E> {
/**
* the default capacity
*/
private static final int DEFAULT_CAPACITY = 11;
/** the default load factor of a HashMap */
private static final float DEFAULT_LOAD_FACTOR = 0.75F;
private transient ReferenceQueue<E> mQueue = new ReferenceQueue<E>();
static class Bucket<E> extends WeakReference<E> {
public Bucket(E o, ReferenceQueue<E> q) {
super(o, q);
}
int mHash;
Bucket<E> mNext;
}
private transient Bucket<E>[] mBuckets;
transient int mModCount = 0;
int mSize = 0;
int mThreshold;
float mLoadFactor;
/**
* Creates a new unify hash.
* @param initialCapacity The initial number of hash buckets
* @param loadFactor The maximum average number of objects per
* buckets.
*/
@SuppressWarnings("unchecked")
public UnifyHash(int initialCapacity, float loadFactor) {
this.mLoadFactor = loadFactor;
mBuckets = new Bucket[initialCapacity];
mThreshold = (int) (loadFactor * initialCapacity);
}
/**
* Creates a new unify hash with default load factor.
* @param initialCapacity The initial number of hash buckets
*/
public UnifyHash(int initialCapacity) {
this(initialCapacity, DEFAULT_LOAD_FACTOR);
}
/**
* Creates a new unify hash with default initial size and
* load factor.
*/
public UnifyHash() {
this(DEFAULT_CAPACITY, DEFAULT_LOAD_FACTOR);
}
/**
* This method is called when the number of objects is bigger than
* loadFactor*buckets.length. It doubles the number of buckets
* and reorganizes the objects into the hash buckets.
*/
@SuppressWarnings("unchecked")
private void grow() {
final Bucket<E>[] oldBuckets = mBuckets;
final int newCap = mBuckets.length * 2 + 1;
mThreshold = (int) (mLoadFactor * newCap);
mBuckets = new Bucket[newCap];
for (int i = 0; i < oldBuckets.length; i++) {
Bucket<E> nextBucket;
for (Bucket<E> b = oldBuckets[i]; b != null; b = nextBucket) {
if (i != Math.abs(b.mHash % oldBuckets.length)) {
throw new RuntimeException(i + ", hash: " + b.mHash
+ ", oldlength: "
+ oldBuckets.length);
}
final int newSlot = Math.abs(b.mHash % newCap);
nextBucket = b.mNext;
b.mNext = mBuckets[newSlot];
mBuckets[newSlot] = b;
}
}
}
/**
* Clean up the hash table, by removing all weak references to objects
* that are no longer existing. This methods is already called from the
* other public methods, so there is no need to call it manually.
*/
@SuppressWarnings("unchecked")
public final void cleanUp() {
Bucket<E> died;
while ((died = (Bucket<E>) mQueue.poll()) != null) {
final int diedSlot = Math.abs(died.mHash % mBuckets.length);
if (mBuckets[diedSlot] == died) {
mBuckets[diedSlot] = died.mNext;
} else {
Bucket<E> b = mBuckets[diedSlot];
while (b.mNext != died) {
b = b.mNext;
}
b.mNext = died.mNext;
}
mSize--;
}
}
/**
* The number of objects that are stored in this collection.
*/
@Override
public int size() {
return mSize;
}
/**
* Gets an iterator of all objects in this collection.
*/
@Override
public Iterator<E> iterator() {
cleanUp();
return new Iterator<E>() {
private int mBucket = 0;
private final int mKnown = mModCount;
private Bucket<E> mNextBucket;
private E mNextVal;
{
internalNext();
}
private void internalNext() {
while (true) {
while (mNextBucket == null) {
if (mBucket == mBuckets.length) {
return;
}
mNextBucket = mBuckets[mBucket++];
}
mNextVal = mNextBucket.get();
if (mNextVal != null) {
return;
}
mNextBucket = mNextBucket.mNext;
}
}
@Override
public boolean hasNext() {
return mNextBucket != null;
}
@Override
public E next() {
if (mKnown != mModCount) {
throw new ConcurrentModificationException();
}
if (mNextBucket == null) {
throw new NoSuchElementException();
}
final E result = mNextVal;
mNextBucket = mNextBucket.mNext;
internalNext();
return result;
}
@Override
public void remove() {
throw new UnsupportedOperationException();
}
};
}
/**
* Gets an iterator of all objects in this collection with the
* given hash code.
*/
public Iterable<E> iterateHashCode(final int hash) {
cleanUp();
return new Iterable<E>() {
@Override
public Iterator<E> iterator() {
return new Iterator<E>() {
private int mKnown = mModCount;
private boolean mRemoveOk = false;
private Bucket<E> mRemoveBucket = null;
private Bucket<E> mPrevBucket = null;
private Bucket<E> mNextBucket =
mBuckets[Math.abs(hash % mBuckets.length)];
private E mNextVal;
{
internalNext();
}
private void internalNext() {
while (mNextBucket != null) {
if (mNextBucket.mHash == hash) {
mNextVal = mNextBucket.get();
if (mNextVal != null) {
return;
}
}
mPrevBucket = mNextBucket;
mNextBucket = mNextBucket.mNext;
}
}
@Override
public boolean hasNext() {
return mNextBucket != null;
}
@Override
public E next() {
if (mKnown != mModCount) {
throw new ConcurrentModificationException();
}
if (mNextBucket == null) {
throw new NoSuchElementException();
}
final E result = mNextVal;
mRemoveBucket = mPrevBucket;
mRemoveOk = true;
mPrevBucket = mNextBucket;
mNextBucket = mNextBucket.mNext;
internalNext();
return result;
}
@Override
public void remove() {
if (mKnown != mModCount) {
throw new ConcurrentModificationException();
}
if (!mRemoveOk) {
throw new IllegalStateException();
}
if (mRemoveBucket == null) {
mBuckets[Math.abs(hash % mBuckets.length)]
= mBuckets[Math.abs(hash % mBuckets.length)]
.mNext;
} else {
mRemoveBucket.mNext = mRemoveBucket.mNext.mNext;
}
mKnown = ++mModCount;
mSize--;
}
};
}
};
}
/**
* Adds a new object into this collection. There should be
* no "equal" object already in this collection.
* @param hash the hash code of the object (this does not need
* to be equal to object.hashCode().
* @param o the object to add to this collection.
*/
public void put(int hash, E o) {
if (mSize++ > mThreshold) {
grow();
}
mModCount++;
final int slot = Math.abs(hash % mBuckets.length);
final Bucket<E> b = new Bucket<E>(o, mQueue);
b.mHash = hash;
b.mNext = mBuckets[slot];
mBuckets[slot] = b;
}
/**
* Remove the object o from the collection.
*/
public boolean remove(int hash, E o) {
final Iterator<E> i = iterateHashCode(hash).iterator();
while (i.hasNext()) {
if (i.next() == o) {
i.remove();
return true;
}
}
return false;
}
/**
* Adds an object to this collection or returns a reference to
* an existing object from the collection.
* It is often easier to not use this function and use something
* similar to the example method in the class description. This
* way you do not need to create a Comparator class and create
* unnecessary objects.
*
* @param o the object to add to this collection.
* @param hash the hash code of the object. This does not need
* to be equal to object.hashCode(), but objects that compare equal
* by comparator should have the same hash code.
* @param comparator a comparator that determines if an object
* in this hash is equal to the object o.
* @return An object from the hash that equals o, or if no such
* object exists adds o to the collection and returns o.
*/
public E unify(E o, int hash, Comparator<E> comparator) {
cleanUp();
final int slot = Math.abs(hash % mBuckets.length);
for (Bucket<E> b = mBuckets[slot]; b != null; b = b.mNext) {
if (b.mHash == hash) {
final E old = b.get();
if (old != null && comparator.compare(o, old) == 0) {
return old;
}
}
}
put(hash, o);
return o;
}
/**
* Specialized form that takes the hash code from the object and compares
* with {@link Object#equals(Object) equals}.
* @param o Object to unify.
* @return Unified object.
*/
public E unify(E o) {
cleanUp();
final int hash = o.hashCode();
final int slot = Math.abs(hash % mBuckets.length);
for (Bucket<E> b = mBuckets[slot]; b != null; b = b.mNext) {
if (b.mHash == hash) {
final E old = b.get();
if (old != null && o.equals(old)) {
return old;
}
}
}
put(hash, o);
return o;
}
private void writeObject(ObjectOutputStream oos) throws IOException {
oos.defaultWriteObject();
oos.writeInt(mBuckets.length);
for (Bucket<E> b : mBuckets) {
while (b != null) {
final E elem = b.get();
if (elem != null) {
oos.writeInt(b.mHash);
oos.writeObject(elem);
}
b = b.mNext;
}
}
// Finish with <0,Null>
oos.writeInt(0);
oos.writeObject(null);
}
@SuppressWarnings("unchecked")
private void readObject(ObjectInputStream ois)
throws IOException, ClassNotFoundException {
mQueue = new ReferenceQueue<E>();
mModCount = 0;
ois.defaultReadObject();
final int bucketsize = ois.readInt();
mBuckets = new Bucket[bucketsize];
while (true) {
final int hash = ois.readInt();
final E obj = (E)ois.readObject();
if (obj == null) {
break;
}
put(hash,obj);
}
}
}