All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.semanticweb.elk.util.collections.entryset.EntryCollection Maven / Gradle / Ivy

The newest version!
/*
 * #%L
 * ELK Utilities Collections
 * 
 * $Id$
 * $HeadURL$
 * %%
 * Copyright (C) 2011 Department of Computer Science, University of Oxford
 * %%
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *      http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * #L%
 */
package org.semanticweb.elk.util.collections.entryset;

import java.util.AbstractCollection;
import java.util.ConcurrentModificationException;
import java.util.HashSet;
import java.util.Iterator;
import java.util.NoSuchElementException;

/**
 * TODO: update documentation
 * 
 * A collection of entries, maintained modulo structural equality
 * {@code Entry#structuralEquals(Object)}. This may be different from
 * {@code Entry#equals(Object)}, which is used for finding an element in the
 * collection. The main methods are inserting, removing, and finding elements
 * modulo structural equality.
 * 
 * The implementation is largely based on the implementation of
 * {@linkplain HashSet} from the standard Java collection library.
 * 
 * @author "Yevgeny Kazakov"
 * 
 * 
 * @param 
 *            the type of entries in the collection
 */
public class EntryCollection>
		extends AbstractCollection {

	/**
	 * The default initial capacity - MUST be a power of two.
	 */
	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.
	 */
	static final int MAXIMUM_CAPACITY = 1 << 30;

	/**
	 * The under-load factor used when none specified in constructor.
	 */
	static final float DEFAULT_UNDERLOAD_FACTOR = 0.15f;

	/**
	 * The over-load factor used when none specified in constructor.
	 */
	static final float DEFAULT_OVERLOAD_FACTOR = 0.75f;

	/**
	 * The bucket array, resized as necessary. Length MUST Always be a power of
	 * two.
	 */
	transient E[] buckets;

	/**
	 * The number of entries contained in this map.
	 */
	transient int size;

	/**
	 * The minimum size of the set, below which the set is not shrink.
	 * 
	 * @serial
	 */
	int minsize;

	/**
	 * The next minimum size value at which to shrink the buckets array array
	 * (capacity * under-load factor).
	 * 
	 * @serial
	 */
	int undersize;

	/**
	 * The next maximum size value at which to expand the buckets array
	 * (capacity * over-load factor).
	 * 
	 * @serial
	 */
	int oversize;

	/**
	 * The over-load factor for the buckets array.
	 * 
	 * @serial
	 */
	final float overloadFactor;

	/**
	 * The under-load factor for the buckets array.
	 * 
	 * @serial
	 */
	final float underloadFactor;

	/**
	 * The number of times this {@link EntryCollection} has been modified. This
	 * field is used to make iterators on Collection-views of the HashMap
	 * fail-fast. (See ConcurrentModificationException).
	 */
	transient int modCount;

	/**
	 * Constructs an empty {@link EntryCollection} with the specified initial
	 * capacity, under-load factor, and over-load factor. The over-load factor
	 * needs to be larger than twice the under-load factor (ideally even more).
	 * 
	 * @param initialCapacity
	 *            the initial capacity
	 * @param underloadFactor
	 *            the under-load factor
	 * @param overloadFactor
	 *            the over-load factor
	 * @throws IllegalArgumentException
	 *             if the initial capacity is negative or the load factor is
	 *             non-positive
	 */
	@SuppressWarnings("unchecked")
	public EntryCollection(int initialCapacity, float underloadFactor,
			float overloadFactor) {
		if (initialCapacity < 0)
			throw new IllegalArgumentException(
					"Illegal initial capacity: " + initialCapacity);
		if (initialCapacity > MAXIMUM_CAPACITY)
			initialCapacity = MAXIMUM_CAPACITY;
		if (overloadFactor <= 0 || Float.isNaN(overloadFactor))
			throw new IllegalArgumentException(
					"Illegal load factor: " + overloadFactor);

		// Find a power of 2 >= initialCapacity
		int capacity = 1;
		while (capacity < initialCapacity)
			capacity <<= 1;

		this.underloadFactor = underloadFactor;
		this.overloadFactor = overloadFactor;
		this.minsize = initialCapacity;
		undersize = (int) (capacity * underloadFactor);
		oversize = (int) (capacity * overloadFactor);
		buckets = (E[]) new Entry[capacity];
		init();
	}

	/**
	 * Constructs an empty {@link EntryCollection} with the specified initial
	 * capacity, the default under-load factor (0.15), and the default over-load
	 * factor (0.75).
	 * 
	 * @param initialCapacity
	 *            the initial capacity.
	 * @throws IllegalArgumentException
	 *             if the initial capacity is negative.
	 */
	public EntryCollection(int initialCapacity) {
		this(initialCapacity, DEFAULT_UNDERLOAD_FACTOR,
				DEFAULT_OVERLOAD_FACTOR);
	}

	/**
	 * Constructs an empty {@link EntryCollection} with the default initial
	 * capacity (16), the default under-load factor (0.15), and the default
	 * over-load factor (0.75).
	 */
	@SuppressWarnings("unchecked")
	public EntryCollection() {
		this.underloadFactor = DEFAULT_UNDERLOAD_FACTOR;
		this.overloadFactor = DEFAULT_OVERLOAD_FACTOR;
		undersize = (int) (DEFAULT_INITIAL_CAPACITY * DEFAULT_UNDERLOAD_FACTOR);
		oversize = (int) (DEFAULT_INITIAL_CAPACITY * DEFAULT_OVERLOAD_FACTOR);
		buckets = (E[]) new Entry[DEFAULT_INITIAL_CAPACITY];
		init();
	}

	// internal utilities

	/**
	 * Initialization hook for subclasses. This method is called in all
	 * constructors and pseudo-constructors (clone, readObject) after
	 * {@link EntryCollection} has been initialized but before any entries have
	 * been inserted. (In the absence of this method, readObject would require
	 * explicit knowledge of subclasses.)
	 */
	void init() {
	}

	/**
	 * Applies a supplemental hash function to a given hashCode, which defends
	 * against poor quality hash functions. This is critical because
	 * {@link EntryCollection} uses power-of-two length buckets array, that
	 * otherwise encounter collisions for hashCodes that do not differ in lower
	 * bits.
	 */
	static int hash(int h) {
		// This function ensures that hashCodes that differ only by
		// constant multiples at each bit position have a bounded
		// number of collisions (approximately 8 at default load factor).
		h ^= (h >>> 20) ^ (h >>> 12);
		return h ^ (h >>> 7) ^ (h >>> 4);
	}

	/**
	 * Returns index for hash code h.
	 */
	static int indexFor(int h, int length) {
		return hash(h) & (length - 1);
	}

	/**
	 * Returns the number of entries in this set.
	 * 
	 * @return the number of entries in this set
	 */
	@Override
	public int size() {
		return size;
	}

	/**
	 * Finds and returns the entry in set that is structurally equal to the
	 * input key. Equality of entries is decided using
	 * {@link GenericStructuralObject#structuralEquals(Object)} and
	 * {@link GenericStructuralObject#structuralHashCode()} methods.
	 * 
	 * @param key
	 *            a {@link GenericStructuralObject} using which the required
	 *            entry should be found
	 * @param 
	 *            the type of the key
	 * 
	 * @return the entry in the set that is equal to the input key, or
	 *         {@code null} if there is no such an entry
	 * 
	 */
	public > T findStructural(T key) {
		int h = key.structuralHashCode();
		int i = indexFor(h, buckets.length);
		T result = null;
		for (E r = buckets[i]; r != null; r = r.getNext()) {
			if (r.structuralHashCode() == h
					&& (result = key.structuralEquals(r)) != null)
				return result;
		}
		// else fail
		return null;
	}

	/**
	 * Adds the given entry to this collection; the entry is added even if a
	 * structurally equal entry is already present in the collection
	 * 
	 * @param entry
	 *            the entry to be inserted; it is not allowed to have linked
	 *            elements: {@link Entry#getNext()} should be {@code null}
	 */
	public void addStructural(E entry) {
		if (entry.getNext() != null)
			throw new IllegalArgumentException(
					"The given entry should be fresh!");
		int h = entry.structuralHashCode();
		int i = indexFor(h, buckets.length);
		modCount++;
		E e = buckets[i];
		entry.setNext(e);
		buckets[i] = entry;
		if (size++ >= oversize)
			resize(2 * buckets.length);
	}

	/**
	 * Removes and returns the entry in the set that is structurally equal to
	 * the specified key. Equality of entries is decided using
	 * {@link GenericStructuralObject#structuralEquals(Object)} and
	 * {@link GenericStructuralObject#structuralHashCode()} methods.
	 * 
	 * @param key
	 *            a {@link GenericStructuralObject} using which the required entry
	 *            should be found
	 * @param 
	 *            the type of the key           
	 * @return the removed entry, or {@code null} if no entry that is equal to
	 *         the input object is found
	 */
	public > T removeStructural(T key) {
		int h = key.structuralHashCode();
		int i = indexFor(h, buckets.length);
		E prev = buckets[i];
		E r = prev;
		T result = null;

		while (r != null) {
			E next = r.getNext();
			if (r.structuralHashCode() == h
					&& (result = key.structuralEquals(r)) != null) {
				modCount++;
				if (prev == r)
					buckets[i] = next;
				else
					prev.setNext(next);
				if (size-- <= undersize && buckets.length >= 2 * minsize)
					resize(buckets.length / 2);
				return result;
			}
			prev = r;
			r = next;
		}
		// not found
		return null;
	}

	/**
	 * Rehashes the contents of this map into a new array with a new capacity.
	 * This method is called automatically when the number of entries in this
	 * set becomes below {@link #undersize} or above the {@link #oversize}.
	 * 
	 * If current capacity is {@value #MAXIMUM_CAPACITY}, this method does not
	 * resize the map, but sets threshold to {@value #Integer.MAX_VALUE}. This
	 * has the effect of preventing future calls.
	 * 
	 * @param newCapacity
	 *            the new capacity, MUST be a power of two
	 */
	void resize(int newCapacity) {
		E[] oldTable = buckets;
		int oldCapacity = oldTable.length;
		if (oldCapacity == MAXIMUM_CAPACITY) {
			oversize = Integer.MAX_VALUE;
			oversize = (int) (newCapacity * overloadFactor);
			return;
		}

		@SuppressWarnings("unchecked")
		E[] newTable = (E[]) new Entry[newCapacity];
		transfer(newTable);
		buckets = newTable;
		undersize = (int) (newCapacity * underloadFactor);
		oversize = (int) (newCapacity * overloadFactor);
	}

	/**
	 * Transfers all entries from current buckets array to new buckets array.
	 */
	/**
	 * @param newArray
	 *            the new buckets array into which the entries are transferred.
	 */
	void transfer(E[] newArray) {
		E[] src = buckets;
		int newCapacity = newArray.length;
		for (int j = 0; j < src.length; j++) {
			E e = src[j];
			if (e != null) {
				src[j] = null;
				do {
					E next = e.getNext();
					int i = indexFor(e.structuralHashCode(), newCapacity);
					e.setNext(newArray[i]);
					newArray[i] = e;
					e = next;
				} while (e != null);
			}
		}
	}

	/**
	 * Removes all entries from this set. The set will be empty after this call
	 * returns.
	 */
	@Override
	public void clear() {
		modCount++;
		E[] tab = buckets;
		for (int i = 0; i < tab.length; i++)
			tab[i] = null;
		size = 0;
	}

	@Override
	public Iterator iterator() {
		return new EntryIterator();
	}

	/**
	 * The iterator over the entries in the collection.
	 * 
	 * @author "Yevgeny Kazakov"
	 * 
	 */
	private class EntryIterator implements Iterator {
		E prev, current, next; // the previously, currently, and next to return entries
		int expectedModCount; // For fast-fail
		int curIndex, nextIndex; // the bucket indexes for the current and next entries		

		EntryIterator() {
			expectedModCount = modCount;
			if (size > 0) { // advance to first entry
				E[] t = buckets;
				while (nextIndex < t.length && (next = t[nextIndex++]) == null)
					;
			}
		}

		@Override
		public final boolean hasNext() {
			return next != null;
		}

		@Override
		public final E next() {
			if (modCount != expectedModCount)
				throw new ConcurrentModificationException();
			E e = next;
			if (e == null)
				throw new NoSuchElementException();

			curIndex = nextIndex;
			if ((next = e.getNext()) == null) {
				E[] t = buckets;
				while (nextIndex < t.length && (next = t[nextIndex++]) == null)
					;
			}
			if (current != null) {
				prev = current;
			}			
			current = e;
			return e;
		}

		@Override
		public void remove() {
			if (current == null)
				throw new IllegalStateException();
			if (modCount != expectedModCount)
				throw new ConcurrentModificationException();
			if (prev != null && prev.getNext() == current) {
				prev.setNext(current.getNext());
			} else {
				buckets[curIndex - 1] = current.getNext();
			}
			size--;
			current = null;
		}

	}

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy