home *** CD-ROM | disk | FTP | other *** search
/ Symantec Visual Cafe for Java 2.5 / symantec-visual-cafe-2.5-database-dev-edition.iso / Extras / OSpace / jgl.exe / jgl_2_0 / src / COM / objectspace / jgl / HashSet.java < prev    next >
Encoding:
Java Source  |  1997-03-14  |  26.9 KB  |  961 lines

  1. // Copyright(c) 1996,1997 ObjectSpace, Inc.
  2. // Portions Copyright(c) 1995, 1996 Hewlett-Packard Company.
  3.  
  4. package COM.objectspace.jgl;
  5.  
  6. import java.util.Enumeration;
  7. import java.io.ObjectInputStream;
  8. import java.io.ObjectOutputStream;
  9. import java.io.IOException;
  10.  
  11. /**
  12.  * A HashSet is a container that is optimized for fast associative lookup.
  13.  * Items are matched using a BinaryPredicate which is EqualTo() by default.
  14.  * When an item is inserted into a HashSet, it is stored in a data structure
  15.  * that allows the item to be found very quickly. Items are stored in buckets
  16.  * based on their hash value, computed using the standard function hashCode().
  17.  * By default, a HashSet cannot contain items that match.
  18.  * The HashSet class supports the full range of generic set algorithms such as
  19.  * union() and intersection() in a user-friendly manner.
  20.  * <p>
  21.  * HashSets are useful when fast associate lookup is important, when
  22.  * index-based lookup is unnecessary, and when duplicates are not allowed.
  23.  * <p>
  24.  * Insertion can invalidate iterators.
  25.  * <p>
  26.  * Removal can invalidate iterators.
  27.  * <p>
  28.  * @see COM.objectspace.jgl.Set
  29.  * @see COM.objectspace.jgl.BinaryPredicate
  30.  * @see COM.objectspace.jgl.SetOperations
  31.  * @see COM.objectspace.jgl.examples.HashSetExamples
  32.  * @version 2.0.2
  33.  * @author ObjectSpace, Inc.
  34.  */
  35.  
  36. public class HashSet implements Set
  37.   {
  38.   static final int DEFAULT_SIZE = 203;
  39.   static final float DEFAULT_RATIO = 0.75F;
  40.  
  41.   BinaryPredicate comparator;
  42.   boolean allowDups = false; // does the set allow duplicates?
  43.   boolean expandActive = true; // will expand() have any effect?
  44.   transient int size = 0; // # nodes.
  45.   transient HashSetNode[] buckets; // Array of buckets.
  46.   int length; // buckets.length, cached for speed.
  47.   int limit;
  48.   float ratio;
  49.  
  50.   /**
  51.    * Construct myself to be an empty HashSet that compares objects using equals() and
  52.    * does not allow duplicates.
  53.    */
  54.   public HashSet()
  55.     {
  56.     this( new EqualTo(), false, DEFAULT_SIZE, DEFAULT_RATIO );
  57.     }
  58.  
  59.   /**
  60.    * Construct myself to be an empty HashSet that compares objects using equals() and
  61.    * that conditionally allows duplicates.
  62.    * @param allowDuplicates true if duplicates are allowed.
  63.    */
  64.   public HashSet( boolean allowDuplicates )
  65.     {
  66.     this( new EqualTo(), allowDuplicates, DEFAULT_SIZE, DEFAULT_RATIO );
  67.     }
  68.  
  69.   /**
  70.    * Construct myself to be an empty HashSet that compares objects using the specified
  71.    * binary predicate and does not allow duplicates.
  72.    * @param comparator The predicate for comparing objects.
  73.    */
  74.   public HashSet( BinaryPredicate comparator )
  75.     {
  76.     this( comparator, false, DEFAULT_SIZE, DEFAULT_RATIO );
  77.     }
  78.  
  79.   /**
  80.    * Construct myself to be an empty HashSet that compares objects using the specified
  81.    * binary predicate and conditionally allows duplicates.
  82.    * @param comparator The predicate for comparing objects.
  83.    * @param allowDuplicates true if duplicates are allowed.
  84.    */
  85.   public HashSet( BinaryPredicate comparator, boolean allowDuplicates )
  86.     {
  87.     this( comparator, allowDuplicates, DEFAULT_SIZE, DEFAULT_RATIO );
  88.     }
  89.  
  90.   /**
  91.    * Construct myself to be an empty HashSet that compares objects using the specified
  92.    * binary predicate and conditionally allows duplicates. The initial capacity and
  93.    * load ratio must also be specified.
  94.    * @param comparator The predicate for comparing objects.
  95.    * @param capacity The initial number of hash buckets to reserve.
  96.    * @param loadRatio The maximum load ratio.
  97.    */
  98.   public HashSet( BinaryPredicate comparator, int capacity, float loadRatio  )
  99.     {
  100.     this( comparator, false, capacity, loadRatio );
  101.     }
  102.  
  103.   /**
  104.    * Construct myself to be an empty HashSet that compares objects using the specified
  105.    * binary predicate and conditionally allows duplicates. The initial capacity and
  106.    * load ratio must also be specified.
  107.    * @param comparator The predicate for comparing objects.
  108.    * @param allowDuplicates true if duplicates are allowed.
  109.    * @param capacity The initial number of hash buckets to reserve.
  110.    * @param loadRatio The maximum load ratio.
  111.    */
  112.   public HashSet( BinaryPredicate comparator, boolean allowDuplicates, int capacity, float loadRatio  )
  113.     {
  114.     this.comparator = comparator;
  115.     allowDups = allowDuplicates;
  116.     ratio = loadRatio;
  117.     length = capacity;
  118.     limit = (int)( length * ratio );
  119.     buckets = new HashSetNode[ length ];
  120.     }
  121.  
  122.   /**
  123.    * Construct myself to be a shallow copy of an existing HashSet.
  124.    * @param set The HashSet to copy.
  125.    */
  126.   public HashSet( HashSet set )
  127.     {
  128.     copy( set );
  129.     }
  130.  
  131.   /**
  132.    * Return true if I allow duplicate objects.
  133.    */
  134.   public boolean allowsDuplicates()
  135.     {
  136.     return allowDups;
  137.     }
  138.  
  139.  /**
  140.   * Return my comparator.
  141.   */
  142.   public BinaryPredicate getComparator()
  143.     {
  144.     return comparator;
  145.     }
  146.  
  147.   /**
  148.    * Return my load ratio.
  149.    */
  150.   public float getLoadRatio()
  151.     {
  152.     return ratio;
  153.     }
  154.  
  155.   /**
  156.    * Return a shallow copy of myself.
  157.    */
  158.   public synchronized Object clone()
  159.     {
  160.     return new HashSet( this );
  161.     }
  162.  
  163.   /**
  164.    * Become a shallow copy of an existing HashSet.
  165.    * @param set The HashSet that I shall become a shallow copy of.
  166.    */
  167.   public synchronized void copy( HashSet set )
  168.     {
  169.     synchronized( set )
  170.       {
  171.       comparator = set.comparator;
  172.       length = set.length;
  173.       ratio = set.ratio;
  174.       limit = set.limit;
  175.       size = set.size;
  176.       buckets = new HashSetNode[ length ];
  177.       allowDups = set.allowDups;
  178.  
  179.       for ( int i = 0; i < length; i++ )
  180.         {
  181.         HashSetNode oldNode = null;
  182.         HashSetNode node = set.buckets[ i ];
  183.  
  184.         while ( node != null )
  185.           {
  186.           HashSetNode newNode = new HashSetNode();
  187.           newNode.object = node.object;
  188.           newNode.hash = node.hash;
  189.  
  190.           if ( buckets[ i ] == null )
  191.             buckets[ i ] = newNode;
  192.           else
  193.             oldNode.next = newNode;
  194.  
  195.           oldNode = newNode;
  196.           node = node.next;
  197.           }
  198.         }
  199.       }
  200.     }
  201.  
  202.   /**
  203.    * Return a string that describes me.
  204.    */
  205.   public synchronized String toString()
  206.     {
  207.     return Printing.toString( this, "HashSet" );
  208.     }
  209.  
  210.   /**
  211.    * Return an Enumeration of my objects.
  212.    */
  213.   public synchronized Enumeration elements()
  214.     {
  215.     return new HashSetIterator( first(), this );
  216.     }
  217.  
  218.   /**
  219.    * Return an iterator positioned at my first item.
  220.    */
  221.   public ForwardIterator start()
  222.     {
  223.     return begin();
  224.     }
  225.  
  226.   /**
  227.    * Return an iterator positioned immediately afer my last item.
  228.    */
  229.   public ForwardIterator finish()
  230.     {
  231.     return end();
  232.     }
  233.  
  234.   /**
  235.    * Return an iterator positioned at my first item.
  236.    */
  237.   public synchronized HashSetIterator begin()
  238.     {
  239.     return new HashSetIterator( first(), this );
  240.     }
  241.  
  242.   /**
  243.    * Return an iterator positioned immediately after my last item.
  244.    */
  245.   public synchronized HashSetIterator end()
  246.     {
  247.     return new HashSetIterator( null, this );
  248.     }
  249.  
  250.   /**
  251.    * Return true if I contain no entries.
  252.    */
  253.   public boolean isEmpty()
  254.     {
  255.     return size == 0;
  256.     }
  257.  
  258.   /**
  259.    * Return the number of entries that I contain.
  260.    */
  261.   public int size()
  262.     {
  263.     return size;
  264.     }
  265.  
  266.   /**
  267.    * Return the maximum number of entries that I can contain.
  268.    */
  269.   public int maxSize()
  270.     {
  271.     return Allocator.maxSize();
  272.     }
  273.  
  274.   /**
  275.    * Return true if I'm equal to another object.
  276.    * @param object The object to compare myself against.
  277.    */
  278.   public boolean equals( Object object )
  279.     {
  280.     return object instanceof HashSet && equals( (HashSet)object );
  281.     }
  282.  
  283.   /**
  284.    * Return true if I contain exactly the same items as another HashSet.
  285.    * Use equals() to compare the individual elements.
  286.    * @param set The HashSet to compare myself against.
  287.    */
  288.   public synchronized boolean equals( HashSet set )
  289.     {
  290.     synchronized( set )
  291.       {
  292.       if ( size != set.size )
  293.         return false;
  294.  
  295.       if ( allowDups )
  296.         {
  297.         Object previous = null;
  298.  
  299.         for ( HashSetIterator iterator = begin(); iterator.hasMoreElements(); iterator.advance() )
  300.           {
  301.           Object object = iterator.get();
  302.  
  303.           // Execute the following code for each unique object in the source.
  304.           if ( previous == null || !object.equals( previous ) )
  305.             {
  306.             if ( count( object ) != set.count( object ) )
  307.               return false;
  308.  
  309.             previous = object;
  310.             }
  311.           }
  312.         }
  313.       else
  314.         {
  315.         for ( HashSetIterator iterator = begin(); iterator.hasMoreElements(); iterator.advance() )
  316.           if ( set.count( iterator.get() ) == 0 )
  317.             return false;
  318.         }
  319.       }
  320.     return true;
  321.     }
  322.  
  323.   /**
  324.    * Return my hash code for support of hashing containers
  325.    */
  326.   public synchronized int hashCode()
  327.     {
  328.     return Hashing.unorderedHash( this );
  329.     }
  330.  
  331.   /**
  332.    * Swap my contents with another HashSet.
  333.    * @param set The HashSet that I will swap my contents with.
  334.    */
  335.   public synchronized void swap( HashSet set )
  336.     {
  337.     synchronized( set )
  338.       {
  339.       int tmpSize = size;
  340.       size = set.size;
  341.       set.size = tmpSize;
  342.  
  343.       HashSetNode[] tmpBuckets = buckets;
  344.       buckets = set.buckets;
  345.       set.buckets = tmpBuckets;
  346.  
  347.       int tmpLength = length;
  348.       length = set.length;
  349.       set.length = tmpLength;
  350.  
  351.       int tmpLimit = limit;
  352.       limit = set.limit;
  353.       set.limit = tmpLimit;
  354.  
  355.       float tmpRatio = ratio;
  356.       ratio = set.ratio;
  357.       set.ratio = tmpRatio;
  358.  
  359.       boolean tmpDups = allowDups;
  360.       allowDups = set.allowDups;
  361.       set.allowDups = tmpDups;
  362.       }
  363.     }
  364.  
  365.   /**
  366.    * Remove all of my elements.
  367.    */
  368.   public synchronized void clear()
  369.     {
  370.     buckets = new HashSetNode[ length ];
  371.     size = 0;
  372.     }
  373.  
  374.   /**
  375.    * Remove all objects that match the given object.
  376.    * @param object The object to match for removals
  377.    * @return Return the number of values removed.
  378.    */
  379.   public int remove( Object object )
  380.     {
  381.     Pair result = removeAux( object, size );
  382.     return ( (Number)result.second ).intValue();
  383.     }
  384.  
  385.   /**
  386.    * Remove at most a given number of objects that match the given object.
  387.    * @param object The object to match for removals
  388.    * @param count The maximum number of the pair(s) to remove.
  389.    * @return Return the number of values removed.
  390.    */
  391.   public int remove( Object key, int count )
  392.     {
  393.     Pair result = removeAux( key, count );
  394.     return ( (Number)result.second ).intValue();
  395.     }
  396.  
  397.   synchronized Pair removeAux( Object object, int maximum )
  398.     {
  399.     if ( maximum > 0 )
  400.       {
  401.       int hash = object.hashCode() & 0x7FFFFFFF;
  402.       int probe = hash % length;
  403.  
  404.       for ( HashSetNode node = buckets[ probe ], previous = null; node != null; previous = node, node = node.next )
  405.         if ( node.hash == hash && comparator.execute( node.object, object ) )
  406.           {
  407.           int count = 1;
  408.           --maximum;
  409.  
  410.           HashSetNode end = node.next;
  411.           Object value = node.object;  // we only want the first one.
  412.  
  413.           if ( allowDups )
  414.             {
  415.             while ( maximum > 0 && end != null && end.hash == hash && comparator.execute( end.object, object ) )
  416.               {
  417.               ++count;
  418.               --maximum;
  419.               end = end.next;
  420.               }
  421.             }
  422.  
  423.           if ( previous == null )
  424.             buckets[ probe ] = end;
  425.           else
  426.             previous.next = end;
  427.  
  428.           size -= count;
  429.           return new Pair( value, new Integer( count ) );
  430.           }
  431.       }
  432.     return new Pair( null, new Integer( 0 ) );
  433.     }
  434.  
  435.   /**
  436.    * Remove the element at a particular position.
  437.    * @param e An Enumeration positioned at the element to remove.
  438.    * @exception IllegalArgumentException is the Enumeration isn't a
  439.    * HashSetIterator for this HashSet object.
  440.    * @return Return the pair associated with the enumeration or null if none.
  441.    */
  442.   public synchronized Object remove( Enumeration e )
  443.     {
  444.     if ( ! (e instanceof HashSetIterator) )
  445.       throw new IllegalArgumentException( "Enumeration not a HashSetIterator" );
  446.  
  447.     if ( ((HashSetIterator)e).myHashSet != this )
  448.       throw new IllegalArgumentException( "Enumeration not for this HashSet" );
  449.  
  450.     HashSetIterator pos = (HashSetIterator)e;
  451.     HashSetNode target = pos.myNode;
  452.     int probe = target.hash % length;
  453.     HashSetNode node = buckets[ probe ];
  454.  
  455.     if ( target == node )
  456.       {
  457.       buckets[ probe ] = target.next;
  458.       }
  459.     else
  460.       {
  461.       while ( node.next != target )
  462.         node = node.next;
  463.  
  464.       node.next = target.next;
  465.       }
  466.  
  467.     --size;
  468.     return target == null
  469.       ? null
  470.       : target.object;
  471.     }
  472.  
  473.   /**
  474.    * Remove the elements within a specified range.
  475.    * @param first An Enumeration positioned at the first element to remove.
  476.    * @param last An Enumeration positioned immediately after the last element to remove.
  477.    * @exception IllegalArgumentException is the Enumeration isn't a
  478.    * HashSetIterator for this HashSet object.
  479.    * @return Return the number of values removed.
  480.    */
  481.   public synchronized int remove( Enumeration first, Enumeration last )
  482.     {
  483.     if ( ( ! (first instanceof HashSetIterator) ) ||
  484.         ( ! (last instanceof HashSetIterator) ) )
  485.       throw new IllegalArgumentException( "Enumeration not a HashSetIterator" );
  486.  
  487.     if ( ( ((HashSetIterator)first).myHashSet != this ) ||
  488.         ( ((HashSetIterator)last).myHashSet != this ) )
  489.       throw new IllegalArgumentException( "Enumeration not for this HashSet" );
  490.  
  491.     HashSetIterator begin = (HashSetIterator)first;
  492.     HashSetIterator end = (HashSetIterator)last;
  493.  
  494.     int count = 0;
  495.     while ( !begin.equals( end ) )
  496.       {
  497.       HashSetIterator next = new HashSetIterator( begin );
  498.       next.advance();
  499.       remove( begin );
  500.       begin = next;
  501.       ++count;
  502.       }
  503.     return count;
  504.     }
  505.  
  506.   /**
  507.    * Find an object and return its position. If the object
  508.    * is not found, return end().
  509.    * @param object The object to locate.
  510.    */
  511.   public synchronized HashSetIterator find( Object object )
  512.     {
  513.     int hash = object.hashCode() & 0x7FFFFFFF;
  514.  
  515.     for ( HashSetNode node = buckets[ hash % length ]; node != null; node = node.next )
  516.       if ( hash == node.hash && comparator.execute( node.object, object ) )
  517.         return new HashSetIterator( node, this );
  518.  
  519.     return new HashSetIterator( null, this );
  520.     }
  521.  
  522.   /**
  523.    * Return the number of items that match a particular object.
  524.    * @param object The object to match against.
  525.    */
  526.   public synchronized int count( Object object )
  527.     {
  528.     int hash = object.hashCode() & 0x7FFFFFFF;
  529.  
  530.     for ( HashSetNode node = buckets[ hash % length ]; node != null; node = node.next )
  531.       if ( hash == node.hash && comparator.execute( node.object,object ) )
  532.         {
  533.         if ( allowDups )
  534.           {
  535.           int n = 1;
  536.           node = node.next;
  537.  
  538.           while ( node != null && hash == node.hash && comparator.execute( node.object, object ) )
  539.             {
  540.             ++n;
  541.             node = node.next;
  542.             }
  543.  
  544.           return n;
  545.           }
  546.         else
  547.           {
  548.           return 1;
  549.           }
  550.         }
  551.  
  552.     return 0;
  553.     }
  554.  
  555.   /**
  556.    * If the object doesn't exist or duplicates are allowed, add the object and return null,
  557.    * otherwise don't modify the set and return the matching object.
  558.    * @param object The object to be added.
  559.    * @exception NullPointerException If the value of the object is equal to null.
  560.    */
  561.   public synchronized Object add( Object object )
  562.     {
  563.     if ( object == null )
  564.       throw new NullPointerException();
  565.  
  566.     int hash = object.hashCode() & 0x7FFFFFFF;
  567.     int probe = hash % length;
  568.  
  569.     // find if object already exists first
  570.     for ( HashSetNode node = buckets[ probe ]; node != null; node = node.next )
  571.       if ( hash == node.hash && comparator.execute( node.object, object ) )
  572.         {
  573.         if ( allowDups )
  574.           {
  575.           // duplicate key, add this pair to end and return success.
  576.           HashSetNode newNode = new HashSetNode();
  577.           newNode.object = object;
  578.           newNode.hash = hash;
  579.           newNode.next = node.next;
  580.           node.next = newNode;
  581.  
  582.           if ( ++size > limit )
  583.             expand();
  584.  
  585.           return null;
  586.           }
  587.         else
  588.           {
  589.           // return the object that already exists. DO NOT add
  590.           return node.object;
  591.           }
  592.        }
  593.  
  594.     // object doesn't exists, add appropriately
  595.     HashSetNode newNode = new HashSetNode();
  596.     newNode.object = object;
  597.     newNode.hash = hash;
  598.     newNode.next = buckets[ probe ];
  599.     buckets[ probe ] = newNode;
  600.  
  601.     if ( ++size > limit )
  602.       expand();
  603.  
  604.     return null;
  605.     }
  606.  
  607.   /**
  608.    * Return the first object that matches the given object, or null if no match exists.
  609.    * @param object The object to match against.
  610.    */
  611.   public synchronized Object get( Object object )
  612.     {
  613.     int hash = object.hashCode() & 0x7FFFFFFF;
  614.  
  615.     for ( HashSetNode node = buckets[ hash % length ]; node != null; node = node.next )
  616.       if ( hash == node.hash && comparator.execute( node.object, object ) )
  617.         return node.object;
  618.  
  619.     return null;
  620.     }
  621.  
  622.   /**
  623.    * If the object doesn't exist, add the object and return null, otherwise replace the
  624.    * first object that matches and return the old object.
  625.    * @param object The object to add.
  626.    * @exception NullPointerException If the value of the object is equal to null.
  627.    */
  628.   public synchronized Object put( Object object )
  629.     {
  630.     if ( object == null )
  631.       throw new NullPointerException();
  632.  
  633.     int hash = object.hashCode() & 0x7FFFFFFF;
  634.     int probe = hash % length;
  635.  
  636.     // find if object already exists first
  637.     for ( HashSetNode node = buckets[ probe ]; node != null; node = node.next )
  638.       if ( hash == node.hash && comparator.execute( node.object, object ) )
  639.         {
  640.         // an object matches, replace with new object and return old one.
  641.         Object previous = node.object;
  642.         node.object = object;
  643.         return previous;
  644.         }
  645.  
  646.     // object doesn't exists, add appropriately
  647.     HashSetNode newNode = new HashSetNode();
  648.     newNode.object = object;
  649.     newNode.hash = hash;
  650.     newNode.next = buckets[ probe ];
  651.     buckets[ probe ] = newNode;
  652.  
  653.     if ( ++size > limit )
  654.       expand();
  655.  
  656.     return null;
  657.     }
  658.  
  659.   /**
  660.    * Return a new HashSet that contains all of my elements and all of the elements in
  661.    * a specified HashSet.
  662.    * @param set The HashSet to union myself with.
  663.    */
  664.   public synchronized HashSet union( HashSet set )
  665.     {
  666.     synchronized( set )
  667.       {
  668.       if ( allowDups || set.allowDups )
  669.         throw new InvalidOperationException( "union operation invalid on multisets" );
  670.  
  671.       HashSet result = new HashSet();
  672.       InsertIterator iterator = new InsertIterator( result );
  673.       Copying.copy( this, iterator );
  674.       Copying.copy( set, iterator );
  675.       return result;
  676.       }
  677.     }
  678.  
  679.   /**
  680.    * Return a new HashSet that contains the elements that are both in me and in
  681.    * a specified set.
  682.    * @param set The HashSet to intersect myself with.
  683.    */
  684.   public synchronized HashSet intersection( HashSet set )
  685.     {
  686.     synchronized( set )
  687.       {
  688.       if ( allowDups || set.allowDups )
  689.         throw new InvalidOperationException( "intersection operation invalid on multisets" );
  690.  
  691.       HashSet result = new HashSet();
  692.       HashSetIterator iterator;
  693.  
  694.       // Loop through the smallest set.
  695.       if ( size >= set.size )
  696.         {
  697.         iterator = begin();
  698.         }
  699.       else
  700.         {
  701.         iterator = set.begin();
  702.         set = this;
  703.         }
  704.  
  705.       while ( iterator.hasMoreElements() )
  706.         {
  707.         Object object = iterator.nextElement();
  708.  
  709.         if ( set.count( object ) > 0 )
  710.           result.add( object );
  711.         }
  712.       return result;
  713.       }
  714.     }
  715.  
  716.   /**
  717.    * Return a new HashSet that contains the elements that are in me but not in a
  718.    * specified set.
  719.    * @param set The HashSet to difference myself with.
  720.    */
  721.   public synchronized HashSet difference( HashSet set )
  722.     {
  723.     synchronized( set )
  724.       {
  725.       if ( allowDups || set.allowDups )
  726.         throw new InvalidOperationException( "difference operation invalid on multisets" );
  727.  
  728.       HashSet result = new HashSet();
  729.       HashSetIterator iterator = begin();
  730.  
  731.       while ( iterator.hasMoreElements() )
  732.         {
  733.         Object object = iterator.nextElement();
  734.  
  735.         if ( set.count( object ) == 0 )
  736.           result.add( object );
  737.         }
  738.  
  739.       return result;
  740.       }
  741.     }
  742.  
  743.   /**
  744.    * Return a new HashSet that contains the elements that are either in me or in
  745.    * a specified HashSet, but not both.
  746.    * @param set The HashSet to symmetric difference myself with.
  747.    */
  748.   public synchronized HashSet symmetricDifference( HashSet set )
  749.     {
  750.     synchronized( set )
  751.       {
  752.       if ( allowDups || set.allowDups )
  753.         throw new InvalidOperationException( "symmetricDifference operation invalid on multisets" );
  754.  
  755.       HashSet result = new HashSet();
  756.       HashSetIterator iterator = begin();
  757.  
  758.       while ( iterator.hasMoreElements() )
  759.         {
  760.         Object object = iterator.nextElement();
  761.  
  762.         if ( set.count( object ) == 0 )
  763.           result.add( object );
  764.         }
  765.  
  766.       iterator = set.begin();
  767.  
  768.       while ( iterator.hasMoreElements() )
  769.         {
  770.         Object object = iterator.nextElement();
  771.  
  772.         if ( count( object ) == 0 )
  773.           result.add( object );
  774.         }
  775.  
  776.       return result;
  777.       }
  778.     }
  779.  
  780.   /**
  781.    * Return true if every element in me is also in a specified HashSet.
  782.    * @param set The HashSet to test against.
  783.    */
  784.   public synchronized boolean subsetOf( HashSet set )
  785.     {
  786.     synchronized( set )
  787.       {
  788.       if ( allowDups || set.allowDups )
  789.         throw new InvalidOperationException( "subsetOf operation invalid on multisets" );
  790.  
  791.       HashSetIterator iterator = begin();
  792.  
  793.       while ( iterator.hasMoreElements() )
  794.         if ( set.count( iterator.nextElement() ) == 0 )
  795.           return false;
  796.  
  797.       return true;
  798.       }
  799.     }
  800.  
  801.   /**
  802.    * Return true if every element in me is also in a specified HashSet and I'm smaller
  803.    * than the specified HashSet.
  804.    * @param set The HashSet to test against.
  805.    */
  806.   public synchronized boolean properSubsetOf( HashSet set )
  807.     {
  808.     synchronized( set )
  809.       {
  810.       if ( allowDups || set.allowDups )
  811.         throw new InvalidOperationException( "properSubsetOf operation invalid on multisets" );
  812.  
  813.       return size < set.size && subsetOf( set );
  814.       }
  815.     }
  816.  
  817.   /**
  818.    * Return an iterator positioned at the first location that a
  819.    * particular object could be inserted without violating the ordering
  820.    * criteria. If no such location is found, return an iterator
  821.    * positioned at end().
  822.    * @param object The object in question.
  823.    */
  824.   public synchronized HashSetIterator lowerBound( Object object )
  825.     {
  826.     return (HashSetIterator)equalRange( object ).begin;
  827.     }
  828.  
  829.   /**
  830.    * Return an iterator positioned at the last location that
  831.    * a particular object could be inserted without violating the ordering
  832.    * criteria. If no such location is found, return an iterator
  833.    * positioned at end().
  834.    * @param object The object in question.
  835.    */
  836.   public synchronized HashSetIterator upperBound( Object object )
  837.     {
  838.     return (HashSetIterator)equalRange( object ).end;
  839.     }
  840.  
  841.   /**
  842.    * Return a range whose first element is an iterator positioned
  843.    * at the first occurence of a specific object and whose second element is an
  844.    * iterator positioned immediately after the last occurence of that object.
  845.    * Note that all objects inbetween these iterators will also match the specified
  846.    * object. If no matching object is found, both ends of the range will be
  847.    * the same.
  848.    * @param object The object whose bounds are to be found.
  849.    */
  850.   public synchronized Range equalRange( Object object )
  851.     {
  852.     int hash = object.hashCode() & 0x7FFFFFFF;
  853.  
  854.     for ( HashSetNode node = buckets[ hash % length ]; node != null; node = node.next )
  855.       if ( hash == node.hash && comparator.execute( node.object, object ) )
  856.         {
  857.         HashSetNode begin = node;
  858.         node = node.next;
  859.  
  860.         while ( node != null && hash == node.hash && comparator.execute( node.object, object ) )
  861.           node = node.next == null ? next( node ) : node.next;
  862.  
  863.         return new Range
  864.           (
  865.           new HashSetIterator( begin, this ),
  866.           new HashSetIterator( node, this )
  867.           );
  868.         }
  869.  
  870.     return new Range( end(), end() );
  871.     }
  872.  
  873.   private HashSetNode first()
  874.     {
  875.     if ( size > 0 )
  876.       for ( int i = 0; i < length; i++ )
  877.         if ( buckets[ i ] != null )
  878.           return buckets[ i ];
  879.  
  880.     return null;
  881.     }
  882.  
  883.   private HashSetNode next( HashSetNode node )
  884.     {
  885.     for ( int i = ( node.hash % length ) + 1; i < length; i++ )
  886.       if ( buckets[ i ] != null )
  887.         return buckets[ i ];
  888.  
  889.     return null;
  890.     }
  891.  
  892.   /**
  893.    * Return true if adding an object to myself could result in an expansion
  894.    * of the number of hash buckets I currently use.
  895.    */
  896.   public boolean expansionAllowed()
  897.     {
  898.     return expandActive;
  899.     }
  900.  
  901.   /**
  902.    * Enable or disable the current expansion mode.  If disabled, no new
  903.    * hash buckets will ever be created regardless of my size.
  904.    * @param allow The new expansion mode.
  905.    */
  906.   public synchronized void allowExpansion( boolean allow )
  907.     {
  908.     expandActive = allow;
  909.     }
  910.  
  911.   private void expand()
  912.     {
  913.     if ( !expansionAllowed() )
  914.       return;
  915.  
  916.     int newLength = length * 2 + 1;
  917.     HashSetNode[] newBuckets = new HashSetNode[ newLength ];
  918.  
  919.     for ( int i = 0; i < length; i++ )
  920.       {
  921.       HashSetNode node = buckets[ i ];
  922.  
  923.       while ( node != null )
  924.         {
  925.         HashSetNode current = node;
  926.         node = node.next;
  927.         int probe = current.hash % newLength;
  928.         current.next = newBuckets[ probe ];
  929.         newBuckets[ probe ] = current;
  930.         }
  931.       }
  932.  
  933.     buckets = newBuckets;
  934.     length = newLength;
  935.     limit = (int)( length * ratio );
  936.     }
  937.  
  938.   private synchronized void writeObject( ObjectOutputStream stream ) throws IOException
  939.     {
  940.     stream.defaultWriteObject();
  941.     stream.writeInt( size );
  942.     Copying.copy( begin(), end(), new ObjectOutputStreamIterator( stream ) );
  943.     }
  944.  
  945.   private void readObject( ObjectInputStream stream ) throws IOException, ClassNotFoundException
  946.     {
  947.     stream.defaultReadObject();
  948.     buckets = new HashSetNode[ length ];
  949.     int count = stream.readInt();
  950.     while ( count-- > 0 )
  951.       add( stream.readObject() );
  952.     }
  953.  
  954.   final class HashSetNode
  955.     {
  956.     Object object;
  957.     int hash;
  958.     HashSetNode next;
  959.     }
  960.   }
  961.