home *** CD-ROM | disk | FTP | other *** search
Java Source | 1997-03-14 | 26.9 KB | 961 lines |
- // Copyright(c) 1996,1997 ObjectSpace, Inc.
- // Portions Copyright(c) 1995, 1996 Hewlett-Packard Company.
-
- package COM.objectspace.jgl;
-
- import java.util.Enumeration;
- import java.io.ObjectInputStream;
- import java.io.ObjectOutputStream;
- import java.io.IOException;
-
- /**
- * A HashSet is a container that is optimized for fast associative lookup.
- * Items are matched using a BinaryPredicate which is EqualTo() by default.
- * When an item is inserted into a HashSet, it is stored in a data structure
- * that allows the item to be found very quickly. Items are stored in buckets
- * based on their hash value, computed using the standard function hashCode().
- * By default, a HashSet cannot contain items that match.
- * The HashSet class supports the full range of generic set algorithms such as
- * union() and intersection() in a user-friendly manner.
- * <p>
- * HashSets are useful when fast associate lookup is important, when
- * index-based lookup is unnecessary, and when duplicates are not allowed.
- * <p>
- * Insertion can invalidate iterators.
- * <p>
- * Removal can invalidate iterators.
- * <p>
- * @see COM.objectspace.jgl.Set
- * @see COM.objectspace.jgl.BinaryPredicate
- * @see COM.objectspace.jgl.SetOperations
- * @see COM.objectspace.jgl.examples.HashSetExamples
- * @version 2.0.2
- * @author ObjectSpace, Inc.
- */
-
- public class HashSet implements Set
- {
- static final int DEFAULT_SIZE = 203;
- static final float DEFAULT_RATIO = 0.75F;
-
- BinaryPredicate comparator;
- boolean allowDups = false; // does the set allow duplicates?
- boolean expandActive = true; // will expand() have any effect?
- transient int size = 0; // # nodes.
- transient HashSetNode[] buckets; // Array of buckets.
- int length; // buckets.length, cached for speed.
- int limit;
- float ratio;
-
- /**
- * Construct myself to be an empty HashSet that compares objects using equals() and
- * does not allow duplicates.
- */
- public HashSet()
- {
- this( new EqualTo(), false, DEFAULT_SIZE, DEFAULT_RATIO );
- }
-
- /**
- * Construct myself to be an empty HashSet that compares objects using equals() and
- * that conditionally allows duplicates.
- * @param allowDuplicates true if duplicates are allowed.
- */
- public HashSet( boolean allowDuplicates )
- {
- this( new EqualTo(), allowDuplicates, DEFAULT_SIZE, DEFAULT_RATIO );
- }
-
- /**
- * Construct myself to be an empty HashSet that compares objects using the specified
- * binary predicate and does not allow duplicates.
- * @param comparator The predicate for comparing objects.
- */
- public HashSet( BinaryPredicate comparator )
- {
- this( comparator, false, DEFAULT_SIZE, DEFAULT_RATIO );
- }
-
- /**
- * Construct myself to be an empty HashSet that compares objects using the specified
- * binary predicate and conditionally allows duplicates.
- * @param comparator The predicate for comparing objects.
- * @param allowDuplicates true if duplicates are allowed.
- */
- public HashSet( BinaryPredicate comparator, boolean allowDuplicates )
- {
- this( comparator, allowDuplicates, DEFAULT_SIZE, DEFAULT_RATIO );
- }
-
- /**
- * Construct myself to be an empty HashSet that compares objects using the specified
- * binary predicate and conditionally allows duplicates. The initial capacity and
- * load ratio must also be specified.
- * @param comparator The predicate for comparing objects.
- * @param capacity The initial number of hash buckets to reserve.
- * @param loadRatio The maximum load ratio.
- */
- public HashSet( BinaryPredicate comparator, int capacity, float loadRatio )
- {
- this( comparator, false, capacity, loadRatio );
- }
-
- /**
- * Construct myself to be an empty HashSet that compares objects using the specified
- * binary predicate and conditionally allows duplicates. The initial capacity and
- * load ratio must also be specified.
- * @param comparator The predicate for comparing objects.
- * @param allowDuplicates true if duplicates are allowed.
- * @param capacity The initial number of hash buckets to reserve.
- * @param loadRatio The maximum load ratio.
- */
- public HashSet( BinaryPredicate comparator, boolean allowDuplicates, int capacity, float loadRatio )
- {
- this.comparator = comparator;
- allowDups = allowDuplicates;
- ratio = loadRatio;
- length = capacity;
- limit = (int)( length * ratio );
- buckets = new HashSetNode[ length ];
- }
-
- /**
- * Construct myself to be a shallow copy of an existing HashSet.
- * @param set The HashSet to copy.
- */
- public HashSet( HashSet set )
- {
- copy( set );
- }
-
- /**
- * Return true if I allow duplicate objects.
- */
- public boolean allowsDuplicates()
- {
- return allowDups;
- }
-
- /**
- * Return my comparator.
- */
- public BinaryPredicate getComparator()
- {
- return comparator;
- }
-
- /**
- * Return my load ratio.
- */
- public float getLoadRatio()
- {
- return ratio;
- }
-
- /**
- * Return a shallow copy of myself.
- */
- public synchronized Object clone()
- {
- return new HashSet( this );
- }
-
- /**
- * Become a shallow copy of an existing HashSet.
- * @param set The HashSet that I shall become a shallow copy of.
- */
- public synchronized void copy( HashSet set )
- {
- synchronized( set )
- {
- comparator = set.comparator;
- length = set.length;
- ratio = set.ratio;
- limit = set.limit;
- size = set.size;
- buckets = new HashSetNode[ length ];
- allowDups = set.allowDups;
-
- for ( int i = 0; i < length; i++ )
- {
- HashSetNode oldNode = null;
- HashSetNode node = set.buckets[ i ];
-
- while ( node != null )
- {
- HashSetNode newNode = new HashSetNode();
- newNode.object = node.object;
- newNode.hash = node.hash;
-
- if ( buckets[ i ] == null )
- buckets[ i ] = newNode;
- else
- oldNode.next = newNode;
-
- oldNode = newNode;
- node = node.next;
- }
- }
- }
- }
-
- /**
- * Return a string that describes me.
- */
- public synchronized String toString()
- {
- return Printing.toString( this, "HashSet" );
- }
-
- /**
- * Return an Enumeration of my objects.
- */
- public synchronized Enumeration elements()
- {
- return new HashSetIterator( first(), this );
- }
-
- /**
- * Return an iterator positioned at my first item.
- */
- public ForwardIterator start()
- {
- return begin();
- }
-
- /**
- * Return an iterator positioned immediately afer my last item.
- */
- public ForwardIterator finish()
- {
- return end();
- }
-
- /**
- * Return an iterator positioned at my first item.
- */
- public synchronized HashSetIterator begin()
- {
- return new HashSetIterator( first(), this );
- }
-
- /**
- * Return an iterator positioned immediately after my last item.
- */
- public synchronized HashSetIterator end()
- {
- return new HashSetIterator( null, this );
- }
-
- /**
- * Return true if I contain no entries.
- */
- public boolean isEmpty()
- {
- return size == 0;
- }
-
- /**
- * Return the number of entries that I contain.
- */
- public int size()
- {
- return size;
- }
-
- /**
- * Return the maximum number of entries that I can contain.
- */
- public int maxSize()
- {
- return Allocator.maxSize();
- }
-
- /**
- * Return true if I'm equal to another object.
- * @param object The object to compare myself against.
- */
- public boolean equals( Object object )
- {
- return object instanceof HashSet && equals( (HashSet)object );
- }
-
- /**
- * Return true if I contain exactly the same items as another HashSet.
- * Use equals() to compare the individual elements.
- * @param set The HashSet to compare myself against.
- */
- public synchronized boolean equals( HashSet set )
- {
- synchronized( set )
- {
- if ( size != set.size )
- return false;
-
- if ( allowDups )
- {
- Object previous = null;
-
- for ( HashSetIterator iterator = begin(); iterator.hasMoreElements(); iterator.advance() )
- {
- Object object = iterator.get();
-
- // Execute the following code for each unique object in the source.
- if ( previous == null || !object.equals( previous ) )
- {
- if ( count( object ) != set.count( object ) )
- return false;
-
- previous = object;
- }
- }
- }
- else
- {
- for ( HashSetIterator iterator = begin(); iterator.hasMoreElements(); iterator.advance() )
- if ( set.count( iterator.get() ) == 0 )
- return false;
- }
- }
- return true;
- }
-
- /**
- * Return my hash code for support of hashing containers
- */
- public synchronized int hashCode()
- {
- return Hashing.unorderedHash( this );
- }
-
- /**
- * Swap my contents with another HashSet.
- * @param set The HashSet that I will swap my contents with.
- */
- public synchronized void swap( HashSet set )
- {
- synchronized( set )
- {
- int tmpSize = size;
- size = set.size;
- set.size = tmpSize;
-
- HashSetNode[] tmpBuckets = buckets;
- buckets = set.buckets;
- set.buckets = tmpBuckets;
-
- int tmpLength = length;
- length = set.length;
- set.length = tmpLength;
-
- int tmpLimit = limit;
- limit = set.limit;
- set.limit = tmpLimit;
-
- float tmpRatio = ratio;
- ratio = set.ratio;
- set.ratio = tmpRatio;
-
- boolean tmpDups = allowDups;
- allowDups = set.allowDups;
- set.allowDups = tmpDups;
- }
- }
-
- /**
- * Remove all of my elements.
- */
- public synchronized void clear()
- {
- buckets = new HashSetNode[ length ];
- size = 0;
- }
-
- /**
- * Remove all objects that match the given object.
- * @param object The object to match for removals
- * @return Return the number of values removed.
- */
- public int remove( Object object )
- {
- Pair result = removeAux( object, size );
- return ( (Number)result.second ).intValue();
- }
-
- /**
- * Remove at most a given number of objects that match the given object.
- * @param object The object to match for removals
- * @param count The maximum number of the pair(s) to remove.
- * @return Return the number of values removed.
- */
- public int remove( Object key, int count )
- {
- Pair result = removeAux( key, count );
- return ( (Number)result.second ).intValue();
- }
-
- synchronized Pair removeAux( Object object, int maximum )
- {
- if ( maximum > 0 )
- {
- int hash = object.hashCode() & 0x7FFFFFFF;
- int probe = hash % length;
-
- for ( HashSetNode node = buckets[ probe ], previous = null; node != null; previous = node, node = node.next )
- if ( node.hash == hash && comparator.execute( node.object, object ) )
- {
- int count = 1;
- --maximum;
-
- HashSetNode end = node.next;
- Object value = node.object; // we only want the first one.
-
- if ( allowDups )
- {
- while ( maximum > 0 && end != null && end.hash == hash && comparator.execute( end.object, object ) )
- {
- ++count;
- --maximum;
- end = end.next;
- }
- }
-
- if ( previous == null )
- buckets[ probe ] = end;
- else
- previous.next = end;
-
- size -= count;
- return new Pair( value, new Integer( count ) );
- }
- }
- return new Pair( null, new Integer( 0 ) );
- }
-
- /**
- * Remove the element at a particular position.
- * @param e An Enumeration positioned at the element to remove.
- * @exception IllegalArgumentException is the Enumeration isn't a
- * HashSetIterator for this HashSet object.
- * @return Return the pair associated with the enumeration or null if none.
- */
- public synchronized Object remove( Enumeration e )
- {
- if ( ! (e instanceof HashSetIterator) )
- throw new IllegalArgumentException( "Enumeration not a HashSetIterator" );
-
- if ( ((HashSetIterator)e).myHashSet != this )
- throw new IllegalArgumentException( "Enumeration not for this HashSet" );
-
- HashSetIterator pos = (HashSetIterator)e;
- HashSetNode target = pos.myNode;
- int probe = target.hash % length;
- HashSetNode node = buckets[ probe ];
-
- if ( target == node )
- {
- buckets[ probe ] = target.next;
- }
- else
- {
- while ( node.next != target )
- node = node.next;
-
- node.next = target.next;
- }
-
- --size;
- return target == null
- ? null
- : target.object;
- }
-
- /**
- * Remove the elements within a specified range.
- * @param first An Enumeration positioned at the first element to remove.
- * @param last An Enumeration positioned immediately after the last element to remove.
- * @exception IllegalArgumentException is the Enumeration isn't a
- * HashSetIterator for this HashSet object.
- * @return Return the number of values removed.
- */
- public synchronized int remove( Enumeration first, Enumeration last )
- {
- if ( ( ! (first instanceof HashSetIterator) ) ||
- ( ! (last instanceof HashSetIterator) ) )
- throw new IllegalArgumentException( "Enumeration not a HashSetIterator" );
-
- if ( ( ((HashSetIterator)first).myHashSet != this ) ||
- ( ((HashSetIterator)last).myHashSet != this ) )
- throw new IllegalArgumentException( "Enumeration not for this HashSet" );
-
- HashSetIterator begin = (HashSetIterator)first;
- HashSetIterator end = (HashSetIterator)last;
-
- int count = 0;
- while ( !begin.equals( end ) )
- {
- HashSetIterator next = new HashSetIterator( begin );
- next.advance();
- remove( begin );
- begin = next;
- ++count;
- }
- return count;
- }
-
- /**
- * Find an object and return its position. If the object
- * is not found, return end().
- * @param object The object to locate.
- */
- public synchronized HashSetIterator find( Object object )
- {
- int hash = object.hashCode() & 0x7FFFFFFF;
-
- for ( HashSetNode node = buckets[ hash % length ]; node != null; node = node.next )
- if ( hash == node.hash && comparator.execute( node.object, object ) )
- return new HashSetIterator( node, this );
-
- return new HashSetIterator( null, this );
- }
-
- /**
- * Return the number of items that match a particular object.
- * @param object The object to match against.
- */
- public synchronized int count( Object object )
- {
- int hash = object.hashCode() & 0x7FFFFFFF;
-
- for ( HashSetNode node = buckets[ hash % length ]; node != null; node = node.next )
- if ( hash == node.hash && comparator.execute( node.object,object ) )
- {
- if ( allowDups )
- {
- int n = 1;
- node = node.next;
-
- while ( node != null && hash == node.hash && comparator.execute( node.object, object ) )
- {
- ++n;
- node = node.next;
- }
-
- return n;
- }
- else
- {
- return 1;
- }
- }
-
- return 0;
- }
-
- /**
- * If the object doesn't exist or duplicates are allowed, add the object and return null,
- * otherwise don't modify the set and return the matching object.
- * @param object The object to be added.
- * @exception NullPointerException If the value of the object is equal to null.
- */
- public synchronized Object add( Object object )
- {
- if ( object == null )
- throw new NullPointerException();
-
- int hash = object.hashCode() & 0x7FFFFFFF;
- int probe = hash % length;
-
- // find if object already exists first
- for ( HashSetNode node = buckets[ probe ]; node != null; node = node.next )
- if ( hash == node.hash && comparator.execute( node.object, object ) )
- {
- if ( allowDups )
- {
- // duplicate key, add this pair to end and return success.
- HashSetNode newNode = new HashSetNode();
- newNode.object = object;
- newNode.hash = hash;
- newNode.next = node.next;
- node.next = newNode;
-
- if ( ++size > limit )
- expand();
-
- return null;
- }
- else
- {
- // return the object that already exists. DO NOT add
- return node.object;
- }
- }
-
- // object doesn't exists, add appropriately
- HashSetNode newNode = new HashSetNode();
- newNode.object = object;
- newNode.hash = hash;
- newNode.next = buckets[ probe ];
- buckets[ probe ] = newNode;
-
- if ( ++size > limit )
- expand();
-
- return null;
- }
-
- /**
- * Return the first object that matches the given object, or null if no match exists.
- * @param object The object to match against.
- */
- public synchronized Object get( Object object )
- {
- int hash = object.hashCode() & 0x7FFFFFFF;
-
- for ( HashSetNode node = buckets[ hash % length ]; node != null; node = node.next )
- if ( hash == node.hash && comparator.execute( node.object, object ) )
- return node.object;
-
- return null;
- }
-
- /**
- * If the object doesn't exist, add the object and return null, otherwise replace the
- * first object that matches and return the old object.
- * @param object The object to add.
- * @exception NullPointerException If the value of the object is equal to null.
- */
- public synchronized Object put( Object object )
- {
- if ( object == null )
- throw new NullPointerException();
-
- int hash = object.hashCode() & 0x7FFFFFFF;
- int probe = hash % length;
-
- // find if object already exists first
- for ( HashSetNode node = buckets[ probe ]; node != null; node = node.next )
- if ( hash == node.hash && comparator.execute( node.object, object ) )
- {
- // an object matches, replace with new object and return old one.
- Object previous = node.object;
- node.object = object;
- return previous;
- }
-
- // object doesn't exists, add appropriately
- HashSetNode newNode = new HashSetNode();
- newNode.object = object;
- newNode.hash = hash;
- newNode.next = buckets[ probe ];
- buckets[ probe ] = newNode;
-
- if ( ++size > limit )
- expand();
-
- return null;
- }
-
- /**
- * Return a new HashSet that contains all of my elements and all of the elements in
- * a specified HashSet.
- * @param set The HashSet to union myself with.
- */
- public synchronized HashSet union( HashSet set )
- {
- synchronized( set )
- {
- if ( allowDups || set.allowDups )
- throw new InvalidOperationException( "union operation invalid on multisets" );
-
- HashSet result = new HashSet();
- InsertIterator iterator = new InsertIterator( result );
- Copying.copy( this, iterator );
- Copying.copy( set, iterator );
- return result;
- }
- }
-
- /**
- * Return a new HashSet that contains the elements that are both in me and in
- * a specified set.
- * @param set The HashSet to intersect myself with.
- */
- public synchronized HashSet intersection( HashSet set )
- {
- synchronized( set )
- {
- if ( allowDups || set.allowDups )
- throw new InvalidOperationException( "intersection operation invalid on multisets" );
-
- HashSet result = new HashSet();
- HashSetIterator iterator;
-
- // Loop through the smallest set.
- if ( size >= set.size )
- {
- iterator = begin();
- }
- else
- {
- iterator = set.begin();
- set = this;
- }
-
- while ( iterator.hasMoreElements() )
- {
- Object object = iterator.nextElement();
-
- if ( set.count( object ) > 0 )
- result.add( object );
- }
- return result;
- }
- }
-
- /**
- * Return a new HashSet that contains the elements that are in me but not in a
- * specified set.
- * @param set The HashSet to difference myself with.
- */
- public synchronized HashSet difference( HashSet set )
- {
- synchronized( set )
- {
- if ( allowDups || set.allowDups )
- throw new InvalidOperationException( "difference operation invalid on multisets" );
-
- HashSet result = new HashSet();
- HashSetIterator iterator = begin();
-
- while ( iterator.hasMoreElements() )
- {
- Object object = iterator.nextElement();
-
- if ( set.count( object ) == 0 )
- result.add( object );
- }
-
- return result;
- }
- }
-
- /**
- * Return a new HashSet that contains the elements that are either in me or in
- * a specified HashSet, but not both.
- * @param set The HashSet to symmetric difference myself with.
- */
- public synchronized HashSet symmetricDifference( HashSet set )
- {
- synchronized( set )
- {
- if ( allowDups || set.allowDups )
- throw new InvalidOperationException( "symmetricDifference operation invalid on multisets" );
-
- HashSet result = new HashSet();
- HashSetIterator iterator = begin();
-
- while ( iterator.hasMoreElements() )
- {
- Object object = iterator.nextElement();
-
- if ( set.count( object ) == 0 )
- result.add( object );
- }
-
- iterator = set.begin();
-
- while ( iterator.hasMoreElements() )
- {
- Object object = iterator.nextElement();
-
- if ( count( object ) == 0 )
- result.add( object );
- }
-
- return result;
- }
- }
-
- /**
- * Return true if every element in me is also in a specified HashSet.
- * @param set The HashSet to test against.
- */
- public synchronized boolean subsetOf( HashSet set )
- {
- synchronized( set )
- {
- if ( allowDups || set.allowDups )
- throw new InvalidOperationException( "subsetOf operation invalid on multisets" );
-
- HashSetIterator iterator = begin();
-
- while ( iterator.hasMoreElements() )
- if ( set.count( iterator.nextElement() ) == 0 )
- return false;
-
- return true;
- }
- }
-
- /**
- * Return true if every element in me is also in a specified HashSet and I'm smaller
- * than the specified HashSet.
- * @param set The HashSet to test against.
- */
- public synchronized boolean properSubsetOf( HashSet set )
- {
- synchronized( set )
- {
- if ( allowDups || set.allowDups )
- throw new InvalidOperationException( "properSubsetOf operation invalid on multisets" );
-
- return size < set.size && subsetOf( set );
- }
- }
-
- /**
- * Return an iterator positioned at the first location that a
- * particular object could be inserted without violating the ordering
- * criteria. If no such location is found, return an iterator
- * positioned at end().
- * @param object The object in question.
- */
- public synchronized HashSetIterator lowerBound( Object object )
- {
- return (HashSetIterator)equalRange( object ).begin;
- }
-
- /**
- * Return an iterator positioned at the last location that
- * a particular object could be inserted without violating the ordering
- * criteria. If no such location is found, return an iterator
- * positioned at end().
- * @param object The object in question.
- */
- public synchronized HashSetIterator upperBound( Object object )
- {
- return (HashSetIterator)equalRange( object ).end;
- }
-
- /**
- * Return a range whose first element is an iterator positioned
- * at the first occurence of a specific object and whose second element is an
- * iterator positioned immediately after the last occurence of that object.
- * Note that all objects inbetween these iterators will also match the specified
- * object. If no matching object is found, both ends of the range will be
- * the same.
- * @param object The object whose bounds are to be found.
- */
- public synchronized Range equalRange( Object object )
- {
- int hash = object.hashCode() & 0x7FFFFFFF;
-
- for ( HashSetNode node = buckets[ hash % length ]; node != null; node = node.next )
- if ( hash == node.hash && comparator.execute( node.object, object ) )
- {
- HashSetNode begin = node;
- node = node.next;
-
- while ( node != null && hash == node.hash && comparator.execute( node.object, object ) )
- node = node.next == null ? next( node ) : node.next;
-
- return new Range
- (
- new HashSetIterator( begin, this ),
- new HashSetIterator( node, this )
- );
- }
-
- return new Range( end(), end() );
- }
-
- private HashSetNode first()
- {
- if ( size > 0 )
- for ( int i = 0; i < length; i++ )
- if ( buckets[ i ] != null )
- return buckets[ i ];
-
- return null;
- }
-
- private HashSetNode next( HashSetNode node )
- {
- for ( int i = ( node.hash % length ) + 1; i < length; i++ )
- if ( buckets[ i ] != null )
- return buckets[ i ];
-
- return null;
- }
-
- /**
- * Return true if adding an object to myself could result in an expansion
- * of the number of hash buckets I currently use.
- */
- public boolean expansionAllowed()
- {
- return expandActive;
- }
-
- /**
- * Enable or disable the current expansion mode. If disabled, no new
- * hash buckets will ever be created regardless of my size.
- * @param allow The new expansion mode.
- */
- public synchronized void allowExpansion( boolean allow )
- {
- expandActive = allow;
- }
-
- private void expand()
- {
- if ( !expansionAllowed() )
- return;
-
- int newLength = length * 2 + 1;
- HashSetNode[] newBuckets = new HashSetNode[ newLength ];
-
- for ( int i = 0; i < length; i++ )
- {
- HashSetNode node = buckets[ i ];
-
- while ( node != null )
- {
- HashSetNode current = node;
- node = node.next;
- int probe = current.hash % newLength;
- current.next = newBuckets[ probe ];
- newBuckets[ probe ] = current;
- }
- }
-
- buckets = newBuckets;
- length = newLength;
- limit = (int)( length * ratio );
- }
-
- private synchronized void writeObject( ObjectOutputStream stream ) throws IOException
- {
- stream.defaultWriteObject();
- stream.writeInt( size );
- Copying.copy( begin(), end(), new ObjectOutputStreamIterator( stream ) );
- }
-
- private void readObject( ObjectInputStream stream ) throws IOException, ClassNotFoundException
- {
- stream.defaultReadObject();
- buckets = new HashSetNode[ length ];
- int count = stream.readInt();
- while ( count-- > 0 )
- add( stream.readObject() );
- }
-
- final class HashSetNode
- {
- Object object;
- int hash;
- HashSetNode next;
- }
- }
-