/* * Copyright (c) 2002,2008 Declarative Engineering LLC. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Declarative Engineering LLC * verson 1 which accompanies this distribution, and is available at * http://declarativeengineering.com/legal/DE_Developer_License_v1.txt */ package com.common.util; import com.common.debug.*; import com.common.exception.*; import com.common.comparison.*; /** *

TODO: Make this class serializeable by implementing the read and writeExternal methods. Don't forget to convert NULL_VALUE references to null's and back when reading.

* An collection that stores values so that they can be easily found and the collection can be manipulated rapidly. * Collection modification is very fast, and determining whether a value is in the collection is very fast. * Collection iteration is a bit slower than in a standard list, and indexing is not available. */ public class LiteHashSet extends LiteSet implements IHashSet { public static final int DEFAULT_INITIAL_CAPACITY = 51; public static final float DEFAULT_LOAD_FACTOR = 0.75f; public static final IComparator DEFAULT_COMPARATOR = Comparator.getLogicalComparator(); public static final LiteHashSet EMPTY_SET = new LiteHashSet(1); /** The array of hash entries. */ private HashSetEntry[] entries; /** The number of hash entries in the set. Note that this only counts duplicates if using STYLE_ADD_DUPLICATES. */ private int size; /** ? */ private int threshold; /** The ratio of size to the entries array size (size / entries.length). If this ratio is smaller than the actual ratio then the entries array will be resized. */ private float loadFactor; private boolean reuseEntryObjects = false; private LiteList reusableEntries = null; private IComparator locateComparator = DEFAULT_COMPARATOR; /** Whether multiple references to the same object instance can be stored in this collection. This is normally set to reject duplicates. */ private int style = DEFAULT_STYLE; /** * The entry class used to store HashSet values. */ protected static class HashSetEntry { public int hash; public Object value; public HashSetEntry next; public int count; protected HashSetEntry() { }//HashSetEntry()// }//HashSetEntry// /** * An iterator that can be used to iterate over the HashSet values. *

WARNING: While this class can be serialized, it will reset its' position to the start of the HashSet. * Also, the HashSet will be serialized with the iterator. *

This class is NOT thread safe. */ public static class HashSetIterator implements IIterator { private LiteHashSet hashSet = null; protected int currentIndex = -1; protected HashSetEntry currentEntry = null; protected int nextIndex = -1; protected HashSetEntry nextEntry = null; protected boolean hasNextEntry = false; protected HashSetIterator(LiteHashSet hashSet) { this.hashSet = hashSet; }//HashSetIterator()// public void resetToFront() { currentIndex = -1; currentEntry = null; nextIndex = -1; nextEntry = null; hasNextEntry = false; }//resetToFront()// public boolean remove() { boolean result = false; if(currentEntry != null) { result = hashSet.remove(currentEntry.value); }//if// return result; }//remove()// public Object next() { loadNextEntry(); currentEntry = nextEntry; currentIndex = nextIndex; hasNextEntry = false; nextEntry = null; return currentEntry.value == NULL_VALUE ? null : currentEntry.value; }//next()// public boolean hasNext() { loadNextEntry(); return nextEntry != null; }//hasNext()// /** * Preloads the next entry. This makes querying to see if the next entry exists and then getting the next entry much easier. */ protected void loadNextEntry() { if(!hasNextEntry) { nextEntry = null; nextIndex = currentIndex; if((currentEntry != null) && (currentEntry.next != null)) { nextEntry = currentEntry.next; }//if// else { HashSetEntry[] entries = hashSet.entries; while((nextEntry == null) && (entries.length > ++nextIndex)) { if(entries[nextIndex] != null) { nextEntry = entries[nextIndex]; }//if// }//while// }//else// if(nextEntry != null) { hasNextEntry = true; }//if// }//if// }//loadNextEntry()// /** * Allows the iterator to peek at the next value after calling loadNextEntry. *

This is for use only by subclasses an is not intended for public consumption. The value is only valid after calling loadNextEntry().

* @return The value that will be returned by the next call to next(). * @see #next() * @see #loadNextEntry() */ protected Object peekNext() { return nextEntry.value == NULL_VALUE ? null : nextEntry.value; }//peekNext()// /** * Allows the iterator to peek at whether there is a next value after calling loadNextEntry. *

This is for use only by subclasses an is not intended for public consumption. The value is only valid after calling loadNextEntry().

* @return Whether there is a next value, otherwise the iterator is finished. * @see #hasNext() * @see #loadNextEntry() */ protected boolean peekHasNext() { return nextEntry != null; }//peekHasNext()// public void writeExternal(java.io.ObjectOutput out) throws java.io.IOException { out.writeObject(hashSet); }//writeExternal()// public void readExternal(java.io.ObjectInput in) throws java.io.IOException, ClassNotFoundException { hashSet = (LiteHashSet) in.readObject(); resetToFront(); }//readExternal()// }//HashSetIterator// /** * LiteHashSet constructor. */ public LiteHashSet() { this(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR, DEFAULT_COMPARATOR, DEFAULT_STYLE); }//LiteHashSet()// /** * LiteHashSet copy constructor. * @param original The hash set to be copied. */ public LiteHashSet(LiteHashSet original) { this(original.getEntries().length, original.getLoadFactor(), original.getLocateComparator(), original.getStyle()); HashSetEntry[] entries = original.getEntries(); for(int index = 0; index < entries.length; index++) { for(HashSetEntry entry = entries[index]; entry != null; entry = entry.next) { for(int count = 0; count < entry.count; count++) { add(entry.value); }//for// }//for// }//for// }//LiteHashSet()// /** * LiteHashSet constructor. * @param initialCapacity Determines the size of the array holding the set values. */ public LiteHashSet(int initialCapacity) { this(initialCapacity, DEFAULT_LOAD_FACTOR, DEFAULT_COMPARATOR, DEFAULT_STYLE); }//LiteHashSet()// /** * LiteHashSet constructor. * @param initialCapacity Determines the size of the array holding the set values. * @param locateComparator The comparator used by this collection for locating values. This may be null in which case the default comparator will be used (it uses logical equality for comparisons). * @param allowMultipleInstanceReferences Whether multiple references to the same object instance can be stored in this collection. This is normally false. * @throws IllegalArgumentException If the initial capacity or load factor are not within their valid ranges. */ public LiteHashSet(int initialCapacity, IComparator locateComparator, int style) { this(initialCapacity, DEFAULT_LOAD_FACTOR, locateComparator, style); }//LiteHashSet()// /** * LiteHashSet constructor. * @param initialCapacity Determines the size of the array holding the set values. * @param loadFactor The ratio used to determine when to increase the size of the array holding the set values. * @param locateComparator The comparator used by this collection for locating values. This may be null in which case the default comparator will be used (it uses logical equality for comparisons). * @param allowMultipleInstanceReferences Whether multiple references to the same object instance can be stored in this collection. This is normally false. * @throws IllegalArgumentException If the initial capacity or load factor are not within their valid ranges. */ public LiteHashSet(int initialCapacity, float loadFactor, IComparator locateComparator, int style) { super(); initialize(initialCapacity, loadFactor, locateComparator, style); }//LiteHashSet()// /** * Initializes the hash set. * @param initialCapacity Determines the size of the array holding the set values. * @param loadFactor The ratio used to determine when to increase the size of the array holding the set values. * @param locateComparator The comparator used by this collection for locating values. This may be null in which case the default comparator will be used (it uses logical equality for comparisons). * @param allowMultipleInstanceReferences Whether multiple references to the same object instance can be stored in this collection. This is normally false. */ protected void initialize(int initialCapacity, float loadFactor, IComparator locateComparator, int style) { this.style = style; if(locateComparator == null) { locateComparator = DEFAULT_COMPARATOR; }//if// if(loadFactor <= 0.0) { throw new IllegalArgumentException("Invalid load factor supplied to the HashSet."); }//if// if(initialCapacity <= 0) { initialCapacity = 10; }//if// this.locateComparator = locateComparator; this.loadFactor = loadFactor; this.entries = new HashSetEntry[initialCapacity]; this.threshold = (int)(initialCapacity * loadFactor); }//initialize()// /** * LiteHashSet constructor. * @param values The initial set of values in the set. */ public LiteHashSet(ICollection values) { this(values.getSize(), DEFAULT_LOAD_FACTOR, DEFAULT_COMPARATOR, DEFAULT_STYLE); addAll(values); }//LiteHashSet()// /** * LiteHashSet constructor. * @param values The initial set of values in the set. * @param initialCapacity Determines the size of the array holding the set values. * @param loadFactor The ratio used to determine when to increase the size of the array holding the set values. * @param locateComparator The comparator used by this collection for locating values. This may be null in which case the default comparator will be used (it uses logical equality for comparisons). * @param allowMultipleInstanceReferences Whether multiple references to the same object instance can be stored in this collection. This is normally false. */ public LiteHashSet(ICollection values, int initialCapacity, float loadFactor, IComparator locateComparator, int style) { this(initialCapacity, loadFactor, locateComparator, style); addAll(values); }//LiteHashSet()// /** * LiteHashSet constructor. * @param values The initial set of values in the set. * @param locateComparator The comparator used by this collection for locating values. This may be null in which case the default comparator will be used (it uses logical equality for comparisons). * @param allowMultipleInstanceReferences Whether multiple references to the same object instance can be stored in this collection. This is normally false. */ public LiteHashSet(ICollection values, IComparator locateComparator, int style) { this(values.getSize(), DEFAULT_LOAD_FACTOR, locateComparator, style); addAll(values); }//LiteHashSet()// /** * LiteHashSet constructor. * @param values The initial set of values in the set. * @deprecated Shouldn't be called because the caller shouldn't be relying on defaults for comparison and style. */ public LiteHashSet(Object[] values) { this(values, 0, values.length, values.length, DEFAULT_LOAD_FACTOR, DEFAULT_COMPARATOR, DEFAULT_STYLE); }//LiteHashSet()// /** * LiteHashSet constructor. * @param values The initial set of values in the set. * @param locateComparator The comparator used by this collection for locating values. This may be null in which case the default comparator will be used (it uses logical equality for comparisons). * @param allowMultipleInstanceReferences Whether multiple references to the same object instance can be stored in this collection. This is normally false. */ public LiteHashSet(Object[] values, IComparator locateComparator, int style) { this(values, 0, values.length, values.length, DEFAULT_LOAD_FACTOR, locateComparator, style); }//LiteHashSet()// /** * LiteHashSet constructor. * @param values The initial set of values in the set. * @param initialCapacity Determines the size of the array holding the set values. * @param loadFactor The ratio used to determine when to increase the size of the array holding the set values. * @param locateComparator The comparator used by this collection for locating values. This may be null in which case the default comparator will be used (it uses logical equality for comparisons). * @param allowMultipleInstanceReferences Whether multiple references to the same object instance can be stored in this collection. This is normally false. */ public LiteHashSet(Object[] values, int initialCapacity, float loadFactor, IComparator locateComparator, int style) { this(values, 0, values.length, initialCapacity, loadFactor, locateComparator, style); }//LiteHashSet()// /** * LiteHashSet constructor. * @param values The initial set of values in the set. * @param offset The offset of the first value in the values array (this will not be checked for validity). * @param length The count of values in the values array (this will not be checked for validity). * @param locateComparator The comparator used by this collection for locating values. This may be null in which case the default comparator will be used (it uses logical equality for comparisons). * @param allowMultipleInstanceReferences Whether multiple references to the same object instance can be stored in this collection. This is normally false. */ public LiteHashSet(Object[] values, int offset, int length, IComparator locateComparator, int style) { this(values, offset, length, values.length, DEFAULT_LOAD_FACTOR, locateComparator, style); }//LiteHashSet()// /** * LiteHashSet constructor. * @param values The initial set of values in the set. * @param offset The offset of the first value in the values array (this will not be checked for validity). * @param length The count of values in the values array (this will not be checked for validity). * @param initialCapacity Determines the size of the array holding the set values. * @param loadFactor The ratio used to determine when to increase the size of the array holding the set values. * @param locateComparator The comparator used by this collection for locating values. This may be null in which case the default comparator will be used (it uses logical equality for comparisons). * @param allowMultipleInstanceReferences Whether multiple references to the same object instance can be stored in this collection. This is normally false. */ public LiteHashSet(Object[] values, int offset, int length, int initialCapacity, float loadFactor, IComparator locateComparator, int style) { this(initialCapacity, loadFactor, locateComparator, style); addAll(values, offset, length); }//LiteHashSet()// /* (non-Javadoc) * @see com.common.util.LiteCollection#ensureCapacity(int) */ public void ensureCapacity(int minimumCapacity) { }//ensureCapacity()// /** * Tests to see if the specified value is contained in the collection. * @param value The value to test for. * @return Will be true if the value is contained in the collection. */ public boolean containsValue(Object value) { //Ensure that null values are valid.// if(value == null) { value = NULL_VALUE; }//if// int hash = getLocateComparator().hash(value); int index = (hash & 0x7FFFFFFF) % entries.length; for(HashSetEntry e = entries[index]; e != null; e = e.next) { if((e.hash == hash) && (Comparator.isEqual(getLocateComparator().compare(e.value, value)))) { return true; }//if// }//for// return false; }//containsValue()// /** * Gets the number of times the value has been added to the set. * @param value The value to look for. * @return The number of times the value exists in the set. Note that the comparator will be used to compare the value with other values and it determines which objects are considered equivalent. */ public int getCount(Object value) { //Ensure that null values are valid.// if(value == null) { value = NULL_VALUE; }//if// int hash = getLocateComparator().hash(value); int index = (hash & 0x7FFFFFFF) % entries.length; for(HashSetEntry e = entries[index]; e != null; e = e.next) { if((e.hash == hash) && (Comparator.isEqual(getLocateComparator().compare(e.value, value)))) { return e.count; }//if// }//for// return 0; }//getCount()// /** * Creates or reuses an entry. */ protected HashSetEntry createEntry() { if(reuseEntryObjects) { int index = reusableEntries.getSize(); if(index > 0) { return (HashSetEntry) reusableEntries.remove(index - 1); }//if// else { return new HashSetEntry(); }//else// }//if// else { return new HashSetEntry(); }//else// }//createEntry()// /** * Creates or reuses an entry. */ protected void destroyEntry(HashSetEntry entry) { entry.next = null; entry.value = null; if(reuseEntryObjects) { reusableEntries.add(entry); }//if// }//destroyEntry()// /* (non-Javadoc) * @see com.common.util.ICollection#getFirst() */ public Object getFirst() { Object result = null; if(getSize() > 0) { for(int index = 0; (index < entries.length) && (result == null); index++) { if(entries[index] != null) { result = entries[index].value; }//if// }//for// }//if// return result; }//getFirst()// /** * Gets the value in the hash set when given another logically equal value. * @param value The logically equivalent value. * @return The matching value in the collection, or null if none was found. */ public Object get(Object value) { //Ensure that null values are valid.// if(value == null) { value = NULL_VALUE; }//if// int hash = getLocateComparator().hash(value); int index = (hash & 0x7FFFFFFF) % entries.length; for(HashSetEntry e = entries[index]; e != null; e = e.next) { if((e.hash == hash) && (Comparator.isEqual(getLocateComparator().compare(e.value, value)))) { return e.value; }//if// }//for// return null; }//get()// /** * Gets the hash set entries array. * @return The array referencing the indexed hash set entries. */ protected HashSetEntry[] getEntries() { return entries; }//getEntries()// /* (non-Javadoc) * @see com.common.util.LiteCollection#getSize() */ public int getSize() { return size; }//getSize()// /** * Sets the number of values in the set. * @param size The set's size. */ protected void setSize(int size) { this.size = size; }//setSize()// /* (non-Javadoc) * @see com.common.util.IHashSet#getLocateComparator() */ public IComparator getLocateComparator() { return locateComparator; }//getLocateComparator()// /* (non-Javadoc) * @see com.common.util.IHashSet#getThreshold() */ public int getThreshold() { return threshold; }//getThreshold()// /* (non-Javadoc) * @see com.common.util.IHashSet#getLoadFactor() */ public float getLoadFactor() { return loadFactor; }//getLoadFactor()// /* (non-Javadoc) * @see com.common.util.IHashSet#getStyle() */ public int getStyle() { return style; }//getStyle()// /** * Performs an actual add of the value to the set. * @param value The value to be added to this set. * @return 0 if successful, or -1 if the operation failed. */ protected int internalAdd(Object value) { //Ensure that null values are valid.// if(value == null) { value = NULL_VALUE; }//if// int hash = getLocateComparator().hash(value); int index = (hash & 0x7FFFFFFF) % entries.length; HashSetEntry entry = null; if(style != STYLE_ADD_DUPLICATES) { for(entry = entries[index]; entry != null; entry = entry.next) { if((entry.hash == hash) && (Comparator.isEqual(getLocateComparator().compare(entry.value, value)))) { if(style == STYLE_NO_DUPLICATES) { return -1; }//if// else { entry.count++; return 0; }//else// }//if// }//for// }//if// if(size >= threshold) { rehash(); return internalAdd(value); }//if// entry = createEntry(); entry.hash = hash; entry.value = value; entry.next = entries[index]; entry.count = 1; entries[index] = entry; size++; return 0; }//internalAdd()// /** * Handles removing a value from the collection. * @param value The value to remove. * @return Will be true upon success. */ protected boolean internalRemove(Object value) { //Ensure that null values are valid.// if(value == null) { value = NULL_VALUE; }//if// int hash = getLocateComparator().hash(value); int index = (hash & 0x7FFFFFFF) % entries.length; HashSetEntry entry = null; HashSetEntry previous = null; for(entry = entries[index], previous = null; entry != null; previous = entry, entry = entry.next) { if((entry.hash == hash) && (Comparator.isEqual(getLocateComparator().compare(entry.value, value)))) { entry.count--; if(entry.count == 0) { if(previous != null) { previous.next = entry.next; }//if// else { entries[index] = entry.next; }//else// size--; destroyEntry(entry); }//if// return true; }//if// }//for// return false; }//internalRemove()// /** * Handles removing all values from the collection. * @return Will be true upon success. */ protected void internalRemoveAll() { if(reuseEntryObjects) { for(int index = 0; index < entries.length; index++) { HashSetEntry entry = entries[index]; if(entry != null) { HashSetEntry next = null; do { next = entry.next; destroyEntry(entry); entry = next; } while(next != null); }//if// entries[index] = null; }//for// }//if// else { for(int index = 0; index < entries.length; index++) { entries[index] = null; }//for// }//else// size = 0; }//internalRemoveAll()// /* (non-Javadoc) * @see com.common.util.LiteCollection#internalReplace(java.lang.Object, java.lang.Object) */ protected boolean internalReplace(Object oldValue, Object newValue) { //Note: If this is ever supported, don't forget to replace null's with NULL_VALUE.// throw new MethodNotSupportedException(); }//internalReplace()// /* (non-Javadoc) * @see com.common.util.LiteCollection#internalReplaceAll(com.common.util.ICollection) */ protected boolean internalReplaceAll(ICollection values) { //Note: If this is ever supported, don't forget to replace null's with NULL_VALUE.// throw new MethodNotSupportedException(); }//internalReplaceAll()// /* (non-Javadoc) * @see com.common.util.LiteCollection#internalReplaceAll(com.common.util.ICollection, com.common.util.ICollection) */ protected boolean internalReplaceAll(ICollection removedValues, ICollection addedValues) { //Note: If this is ever supported, don't forget to replace null's with NULL_VALUE.// throw new MethodNotSupportedException(); }//internalReplaceAll()// /** * Gets a new iterator over the values in this collection. * @return IIterator The iterator that may be used to iterate over the collection values. */ public IIterator iterator() { return new HashSetIterator(this); }//iterator()// /** * Will rehash the collection so that new items may be added without going over the recommended ratio of values to list entries. */ protected void rehash() { int oldCapacity = entries.length; HashSetEntry[] oldEntries = entries; int newCapacity = (oldCapacity << 1) + 1; HashSetEntry[] newEntries = new HashSetEntry[newCapacity]; threshold = (int)(newCapacity * loadFactor); entries = newEntries; for(int oldIndex = oldCapacity; oldIndex-- > 0; ) { for(HashSetEntry oldEntry = oldEntries[oldIndex]; oldEntry != null; ) { HashSetEntry newEntry = oldEntry; int newIndex; oldEntry = oldEntry.next; newIndex = (newEntry.hash & 0x7FFFFFFF) % newCapacity; newEntry.next = newEntries[newIndex]; newEntries[newIndex] = newEntry; }//for// }//for// }//rehash()// /** * Fills an array with collection values. * @param array An array capable of containing collection values. * @return The number of values placed in the array. */ public int toArray(Object array) { HashSetEntry entry = null; int position = 0; HashSetEntry[] entries = this.entries; //Iterate over the entry table.// for(int index = 0; index < entries.length; index++) { entry = entries[index]; //Iterate through the entries at this table position.// while(entry != null) { java.lang.reflect.Array.set(array, position, entry.value == NULL_VALUE ? null : entry.value); entry = entry.next; position++; }//while// }//for// return position; }//toArray()// /** * Generates a string representing the hashset. * @return A debug string that represents this object. */ public String toString() { StringBuffer buffer = new StringBuffer(100); buffer.append("LiteHashSet {\r\n"); try { IIterator iterator = iterator(); while(iterator.hasNext()) { Object next = iterator.next(); buffer.append("\t"); buffer.append(next == NULL_VALUE ? null : next); buffer.append("\r\n"); }//while// }//try// catch(Throwable e) { Debug.log(e); }//catch// buffer.append("}"); return buffer.toString(); }//toArray()// }//HashSet//