/[classpath]/classpath/java/util/IdentityHashMap.java
ViewVC logotype

Diff of /classpath/java/util/IdentityHashMap.java

Parent Directory Parent Directory | Revision Log Revision Log | View Patch Patch

revision 1.6 by tromey, Thu Sep 27 16:47:49 2001 UTC revision 1.7 by ericb, Fri Oct 19 07:06:45 2001 UTC
# Line 31  import java.io.*; Line 31  import java.io.*;
31    
32  /**  /**
33   * This class provides a hashtable-backed implementation of the   * This class provides a hashtable-backed implementation of the
34   * Map interface.  Unlike HashMap, it uses object identity to   * Map interface, but uses object identity to do its hashing.  In fact,
35   * do its hashing.  Also, it uses a linear-probe hash table.   * it uses object identity for comparing values, as well. It uses a
36     * linear-probe hash table, which may have faster performance
37     * than the chaining employed by HashMap.
38     * <p>
39     *
40     * <em>WARNING: This is not a general purpose map. Because it uses
41     * System.identityHashCode and ==, instead of hashCode and equals, for
42     * comparison, it violated Map's general contract, and may cause
43     * undefined behavior when compared to other maps which are not
44     * IdentityHashMaps.  This is designed only for the rare cases when
45     * identity semantics are needed.</em> An example use is
46     * topology-preserving graph transformations, such as deep cloning,
47     * or as proxy object mapping such as in debugging.
48     * <p>
49     *
50     * This map permits <code>null</code> keys and values, and does not
51     * guarantee that elements will stay in the same order over time. The
52     * basic operations (<code>get</code> and <code>put</code>) take
53     * constant time, provided System.identityHashCode is decent. You can
54     * tune the behavior by specifying the expected maximum size. As more
55     * elements are added, the map may need to allocate a larger table,
56     * which can be expensive.
57     * <p>
58     *
59     * This implementation is unsynchronized.  If you want multi-thread
60     * access to be consistent, you must synchronize it, perhaps by using
61     * <code>Collections.synchronizedMap(new IdentityHashMap(...));</code>.
62     * The iterators are <i>fail-fast</i>, meaning that a structural modification
63     * made to the map outside of an iterator's remove method cause the
64     * iterator, and in the case of the entrySet, the Map.Entry, to
65     * fail with a {@link ConcurrentModificationException}.
66   *   *
67   * @author Tom Tromey <tromey@redhat.com>   * @author Tom Tromey <tromey@redhat.com>
68     * @author Eric Blake <ebb9@email.byu.edu>
69     * @see System#identityHashCode(Object)
70     * @see Collection
71     * @see Map
72     * @see HashMap
73     * @see TreeMap
74     * @see LinkedHashMap
75     * @see WeakHashMap
76   * @since 1.4   * @since 1.4
77     * @status updated to 1.4
78   */   */
79  public class IdentityHashMap extends AbstractMap  public class IdentityHashMap extends AbstractMap
80    implements Map, Serializable, Cloneable    implements Map, Serializable, Cloneable
81  {  {
82      /** The default capacity. */
83    private static final int DEFAULT_CAPACITY = 21;    private static final int DEFAULT_CAPACITY = 21;
84    
85    /** Create a new IdentityHashMap with the default capacity (21    /**
86     * entries).     * This object is used to mark deleted items. Package visible for use by
87       * nested classes.
88       */
89      static final Object tombstone = new Object();
90    
91      /**
92       * This object is used to mark empty slots.  We need this because
93       * using null is ambiguous. Package visible for use by nested classes.
94       */
95      static final Object emptyslot = new Object();
96    
97      /**
98       * Compatible with JDK 1.4.
99       */
100      private static final long serialVersionUID = 8188218128353913216L;
101    
102      /**
103       * The number of mappings in the table. Package visible for use by nested
104       * classes.
105       * @serial
106       */
107      int size;
108    
109      /**
110       * The table itself. Package visible for use by nested classes.
111       */
112      transient Object[] table;
113    
114      /**
115       * The number of structural modifications made so far. Package visible for
116       * use by nested classes.
117       */
118      transient int modCount;
119    
120      /**
121       * The cache for {@link #entrySet()}.
122       */
123      private transient Set entries;
124    
125      /**
126       * The threshold for rehashing, which is 75% of (table.length / 2).
127       */
128      private transient int threshold;
129    
130      /**
131       * Create a new IdentityHashMap with the default capacity (21 entries).
132     */     */
133    public IdentityHashMap ()    public IdentityHashMap()
134    {    {
135      this (DEFAULT_CAPACITY);      this(DEFAULT_CAPACITY);
136    }    }
137    
138    /** Create a new IdentityHashMap with the indicated number of    /**
139       * Create a new IdentityHashMap with the indicated number of
140     * entries.  If the number of elements added to this hash map     * entries.  If the number of elements added to this hash map
141     * exceeds this maximum, the map will grow itself; however, that     * exceeds this maximum, the map will grow itself; however, that
142     * incurs a performance penalty.     * incurs a performance penalty.
143     * @param max Initial size     *
144       * @param max initial size
145       * @throws IllegalArgumentException if max is negative
146     */     */
147    public IdentityHashMap (int max)    public IdentityHashMap(int max)
148    {    {
149      if (max < 0)      if (max < 0)
150        throw new IllegalArgumentException ();        throw new IllegalArgumentException();
151        // Need at least two slots, or hash() will break.
152        if (max < 2)
153          max = 2;
154      table = new Object[2 * max];      table = new Object[2 * max];
155      Arrays.fill (table, emptyslot);      Arrays.fill(table, emptyslot);
156      size = 0;      // This is automatically set.
157        // size = 0;
158        threshold = max / 4 * 3;
159    }    }
160    
161    /** Create a new IdentityHashMap whose contents are taken from the    /**
162       * Create a new IdentityHashMap whose contents are taken from the
163     * given Map.     * given Map.
164     * @param m The map whose elements are to be put in this map.     *
165       * @param m The map whose elements are to be put in this map
166       * @throws NullPointerException if m is null
167     */     */
168    public IdentityHashMap (Map m)    public IdentityHashMap(Map m)
169    {    {
170      int len = 2 * Math.max (m.size (), DEFAULT_CAPACITY);      this(Math.max(m.size() * 2, DEFAULT_CAPACITY));
171      table = new Object[len];      putAll(m);
     Arrays.fill (table, emptyslot);  
     putAll (m);  
172    }    }
173    
174    public void clear ()    /**
175       * Remove all mappings from this map.
176       */
177      public void clear()
178    {    {
179      Arrays.fill (table, emptyslot);      if (size != 0)
180      size = 0;        {
181            modCount++;
182            Arrays.fill(table, emptyslot);
183            size = 0;
184          }
185    }    }
186    
187    /**    /**
188     * Creates a shallow copy where keys and values are not cloned.     * Creates a shallow copy where keys and values are not cloned.
189     */     */
190    public Object clone ()    public Object clone()
191    {    {
192      try      try
193        {        {
194          IdentityHashMap copy = (IdentityHashMap) super.clone ();          IdentityHashMap copy = (IdentityHashMap) super.clone();
195          copy.table = (Object[]) table.clone ();          copy.table = (Object[]) table.clone();
196          return copy;          copy.entries = null; // invalidate the cache
197            return copy;
198        }        }
199      catch (CloneNotSupportedException e)      catch (CloneNotSupportedException e)
200        {        {
201          // Can't happen.          // Can't happen.
202          return null;          return null;
203        }        }
204    }    }
205    
206    public boolean containsKey (Object key)    /**
207       * Tests whether the specified key is in this map.  Unlike normal Maps,
208       * this test uses <code>entry == key</code> instead of
209       * <code>entry == null ? key == null : entry.equals(key)</code>.
210       *
211       * @param key the key to look for
212       * @return true if the key is contained in the map
213       * @see #containsValue(Object)
214       * @see #get(Object)
215       */
216      public boolean containsKey(Object key)
217    {    {
218      int h = getHash (key);      return key == table[hash(key)];
     int save = h;  
     while (true)  
       {  
         if (table[h] == key)  
           return true;  
         if (table[h] == emptyslot)  
           return false;  
         h += 2;  
         if (h >= table.length)  
           h = 0;  
         if (h == save)  
           return false;  
       }  
219    }    }
220    
221    public boolean containsValue (Object value)    /**
222       * Returns true if this HashMap contains the value.  Unlike normal maps,
223       * this test uses <code>entry == value</code> instead of
224       * <code>entry == null ? value == null : entry.equals(value)</code>.
225       *
226       * @param value the value to search for in this HashMap
227       * @return true if at least one key maps to the value
228       * @see #containsKey(Object)
229       */
230      public boolean containsValue(Object value)
231    {    {
232      for (int i = 1; i < table.length; i += 2)      for (int i = table.length - 1; i > 0; i -= 2)
233        if (table[i] == value)        if (table[i] == value)
234          return true;          return true;
235      return false;      return false;
236    }    }
237    
238    public Set entrySet ()    /**
239       * Returns a "set view" of this Map's entries. The set is backed by
240       * the Map, so changes in one show up in the other.  The set supports
241       * element removal, but not element addition.
242       * <p>
243       *
244       * <em>The semantics of this set, and of its contained entries, are
245       * different from the contract of Set and Map.Entry in order to make
246       * IdentityHashMap work.  This means that while you can compare these
247       * objects between IdentityHashMaps, comparing them with regular sets
248       * or entries is likely to have undefined behavior.</em>  The entries
249       * in this set are reference-based, rather than the normal object
250       * equality.  Therefore, <code>e1.equals(e2)</code> returns
251       * <code>e1.getKey() == e2.getKey() && e1.getValue() == e2.getValue()</code>,
252       * and <code>e.hashCode()</code> returns
253       * <code>System.identityHashCode(e.getKey()) ^
254       *       System.identityHashCode(e.getValue())</code>.
255       * <p>
256       *
257       * Note that the iterators for all three views, from keySet(), entrySet(),
258       * and values(), traverse the Map in the same sequence.
259       *
260       * @return a set view of the entries
261       * @see #keySet()
262       * @see #values()
263       * @see Map.Entry
264       */
265      public Set entrySet()
266    {    {
267      return new AbstractSet ()      if (entries == null)
268      {        entries = new AbstractSet()
       public int size ()  
       {  
         return size;  
       }  
   
       public Iterator iterator ()  
       {  
         return new IdentityIterator (IdentityIterator.ENTRIES);  
       }  
   
       public void clear ()  
269        {        {
270          IdentityHashMap.this.clear ();          public int size()
271        }          {
272              return size;
273            }
274    
275            public Iterator iterator()
276            {
277              return new IdentityIterator(ENTRIES);
278            }
279    
280            public void clear()
281            {
282              IdentityHashMap.this.clear();
283            }
284    
285            public boolean contains(Object o)
286            {
287              if (! (o instanceof Map.Entry))
288                return false;
289              Map.Entry m = (Map.Entry) o;
290              return m.getValue() == table[hash(m.getKey()) + 1];
291            }
292    
293            public int hashCode()
294            {
295              return IdentityHashMap.this.hashCode();
296            }
297    
298            public boolean remove(Object o)
299            {
300              if (! (o instanceof Map.Entry))
301                return false;
302              Object key = ((Map.Entry) o).getKey();
303              int h = hash(key);
304              if (table[h] == key)
305                {
306                  size--;
307                  modCount++;
308                  table[h] = tombstone;
309                  table[h + 1] = tombstone;
310                  return true;
311                }
312              return false;
313            }
314          };
315        return entries;
316      }
317    
318        public boolean contains (Object o)    /**
319        {     * Compares two maps for equality. This returns true only if both maps
320          if (! (o instanceof Map.Entry))     * have the same reference-identity comparisons. While this returns
321            return false;     * <code>this.entrySet().equals(m.entrySet())</code> as specified by Map,
322          Map.Entry m = (Map.Entry) o;     * this will not work with normal maps, since the entry set compares
323          return (IdentityHashMap.this.containsKey (m.getKey ())     * with == instead of .equals.
324                  && IdentityHashMap.this.get (m.getKey ()) == m.getValue ());     *
325        }     * @param o the object to compare to
326       * @return true if it is equal
327       */
328      public boolean equals(Object o)
329      {
330        // Why did Sun specify this one? The superclass does the right thing.
331        return super.equals(o);
332      }
333    
334        public boolean remove (Object o)    /**
335        {     * Return the value in this Map associated with the supplied key,
336          if (! (o instanceof Map.Entry))     * or <pre>null</pre> if the key maps to nothing.  NOTE: Since the value
337            return false;     * could also be null, you must use containsKey to see if this key
338          Map.Entry m = (Map.Entry) o;     * actually maps to something.  Unlike normal maps, this tests for the key
339          if (IdentityHashMap.this.containsKey (m.getKey ())     * with <code>entry == key</code> instead of
340              && IdentityHashMap.this.get (m.getKey ()) == m.getValue ())     * <code>entry == null ? key == null : entry.equals(key)</code>.
341            {     *
342              int oldsize = size;     * @param key the key for which to fetch an associated value
343              IdentityHashMap.this.remove (m.getKey ());     * @return what the key maps to, if present
344              return oldsize != size;     * @see #put(Object, Object)
345            }     * @see #containsKey(Object)
346          return false;     */
347        }    public Object get(Object key)
348      };    {
349        int h = hash(key);
350        return table[h] == key ? table[h + 1] : null;
351    }    }
352    
353    public Object get (Object key)    /**
354       * Returns the hashcode of this map. This guarantees that two
355       * IdentityHashMaps that compare with equals() will have the same hash code,
356       * but may break with comparison to normal maps since it uses
357       * System.identityHashCode() instead of hashCode().
358       *
359       * @return the hash code
360       */
361      public int hashCode()
362    {    {
363      int h = getHash (key);      int hash = 0;
364      int save = h;      for (int i = table.length - 2; i >= 0; i -= 2)
     while (true)  
365        {        {
366          if (table[h] == key)          Object key = table[i];
367            return table[h + 1];          if (key == emptyslot || key == tombstone)
368          if (table[h] == emptyslot)            continue;
369            return null;          hash += (System.identityHashCode(key)
370          h += 2;                   ^ System.identityHashCode(table[i + 1]));
         if (h >= table.length)  
           h = 0;  
         if (h == save)  
           return null;  
371        }        }
372        return hash;
373    }    }
374    
375    public boolean isEmpty ()    /**
376       * Returns true if there are no key-value mappings currently in this Map
377       * @return <code>size() == 0</code>
378       */
379      public boolean isEmpty()
380    {    {
381      return size == 0;      return size == 0;
382    }    }
383    
384    public Set keySet ()    /**
385       * Returns a "set view" of this Map's keys. The set is backed by the
386       * Map, so changes in one show up in the other.  The set supports
387       * element removal, but not element addition.
388       * <p>
389       *
390       * <em>The semantics of this set are different from the contract of Set
391       * in order to make IdentityHashMap work.  This means that while you can
392       * compare these objects between IdentityHashMaps, comparing them with
393       * regular sets is likely to have undefined behavior.</em>  The hashCode
394       * of the set is the sum of the identity hash codes, instead of the
395       * regular hashCodes, and equality is determined by reference instead
396       * of by the equals method.
397       * <p>
398       *
399       * @return a set view of the keys
400       * @see #values()
401       * @see #entrySet()
402       */
403      public Set keySet()
404    {    {
405      return new AbstractSet ()      if (keys == null)
406      {        keys = new AbstractSet()
       public int size ()  
       {  
         return size;  
       }  
   
       public Iterator iterator ()  
       {  
         return new IdentityIterator (IdentityIterator.KEYS);  
       }  
   
       public void clear ()  
       {  
         IdentityHashMap.this.clear ();  
       }  
   
       public boolean contains (Object o)  
407        {        {
408          return IdentityHashMap.this.containsKey (o);          public int size()
409        }          {
410              return size;
411        public boolean remove (Object o)          }
412        {  
413          int oldsize = size;          public Iterator iterator()
414          IdentityHashMap.this.remove (o);          {
415          return oldsize != size;            return new IdentityIterator(KEYS);
416        }          }
417      };  
418            public void clear()
419            {
420              IdentityHashMap.this.clear();
421            }
422    
423            public boolean contains(Object o)
424            {
425              return containsKey(o);
426            }
427    
428            public int hashCode()
429            {
430              int hash = 0;
431              for (int i = table.length - 2; i >= 0; i -= 2)
432                {
433                  Object key = table[i];
434                  if (key == emptyslot || key == tombstone)
435                    continue;
436                  hash += System.identityHashCode(key);
437                }
438              return hash;
439    
440            }
441    
442            public boolean remove(Object o)
443            {
444              int h = hash(o);
445              if (table[h] == o)
446                {
447                  size--;
448                  modCount++;
449                  table[h] = tombstone;
450                  table[h + 1] = tombstone;
451                  return true;
452                }
453              return false;
454            }
455          };
456        return keys;
457    }    }
458    
459    public Object put (Object key, Object value)    /**
460       * Puts the supplied value into the Map, mapped by the supplied key.
461       * The value may be retrieved by any object which <code>equals()</code>
462       * this key. NOTE: Since the prior value could also be null, you must
463       * first use containsKey if you want to see if you are replacing the
464       * key's mapping.  Unlike normal maps, this tests for the key
465       * with <code>entry == key</code> instead of
466       * <code>entry == null ? key == null : entry.equals(key)</code>.
467       *
468       * @param key the key used to locate the value
469       * @param value the value to be stored in the HashMap
470       * @return the prior mapping of the key, or null if there was none
471       * @see #get(Object)
472       */
473      public Object put(Object key, Object value)
474    {    {
475      // Rehash if the load factor is too high.  We use a factor of 1.5      // Rehash if the load factor is too high.
476      // -- the division by 2 is implicit on both sides.      if (size > threshold)
     if (size * 3 > table.length)  
       {  
         Object[] old = table;  
         table = new Object[old.length * 2];  
         Arrays.fill (table, emptyslot);  
         size = 0;  
         for (int i = 0; i < old.length; i += 2)  
           {  
             if (old[i] != tombstone && old[i] != emptyslot)  
               {  
                 // Just use put.  This isn't very efficient, but it is  
                 // ok.  
                 put (old[i], old[i + 1]);  
               }  
           }  
       }  
   
     int h = getHash (key);  
     int save = h;  
     int del = -1;  
     while (true)  
477        {        {
478          if (table[h] == key)          Object[] old = table;
479            {          // This isn't necessarily prime, but it is an odd number of key/value
480              Object r = table[h + 1];          // slots, which has a higher probability of fewer collisions.
481              table[h + 1] = value;          table = new Object[old.length * 2 + 2];
482              return r;          Arrays.fill(table, emptyslot);
483            }          size = 0;
484          else if (table[h] == tombstone && del == -1)          threshold = table.length / 4 * 3;
485            del = h;  
486          else if (table[h] == emptyslot)          for (int i = old.length - 2; i >= 0; i -= 2)
487            {            {
488              if (del == -1)              Object oldkey = old[i];
489                del = h;              if (oldkey != tombstone && oldkey != emptyslot)
490              break;                // Just use put.  This isn't very efficient, but it is ok.
491            }                put(oldkey, old[i + 1]);
492          h += 2;            }
493          if (h >= table.length)        }
494            h = 0;  
495          if (h == save)      int h = hash(key);
496            break;      if (table[h] == key)
497        }        {
498            Object r = table[h + 1];
499      if (del != -1)          table[h + 1] = value;
500        {          return r;
501          table[del] = key;        }
502          table[del + 1] = value;  
503          ++size;      // At this point, we add a new mapping.
504          return null;      size++;
505        }      modCount++;
506        table[h] = key;
507      // This is an error.      table[h + 1] = value;
508      return null;      return null;
509    }    }
510    
511    public Object remove (Object key)    /**
512       * Copies all of the mappings from the specified map to this. If a key
513       * is already in this map, its value is replaced.
514       *
515       * @param m the map to copy
516       * @throws NullPointerException if m is null
517       */
518      public void putAll(Map m)
519    {    {
520      int h = getHash (key);      // Why did Sun specify this one? The superclass does the right thing.
521      int save = h;      super.putAll(m);
522      while (true)    }
523    
524      /**
525       * Removes from the HashMap and returns the value which is mapped by the
526       * supplied key. If the key maps to nothing, then the HashMap remains
527       * unchanged, and <pre>null</pre> is returned. NOTE: Since the value
528       * could also be null, you must use containsKey to see if you are
529       * actually removing a mapping.  Unlike normal maps, this tests for the
530       * key with <code>entry == key</code> instead of
531       * <code>entry == null ? key == null : entry.equals(key)</code>.
532       *
533       * @param key the key used to locate the value to remove
534       * @return whatever the key mapped to, if present
535       */
536      public Object remove(Object key)
537      {
538        int h = hash(key);
539        if (table[h] == key)
540        {        {
541          if (table[h] == key)          size--;
542            {          modCount++;
543              Object r = table[h + 1];          Object r = table[h + 1];
544              table[h] = tombstone;          table[h] = tombstone;
545              table[h + 1] = tombstone;          table[h + 1] = tombstone;
546              --size;          return r;
             return r;  
           }  
         h += 2;  
         if (h >= table.length)  
           h = 0;  
         if (h == save)  
           break;  
547        }        }
   
548      return null;      return null;
549    }    }
550    
551    public int size ()    /**
552       * Returns the number of kay-value mappings currently in this Map
553       * @return the size
554       */
555      public int size()
556    {    {
557      return size;      return size;
558    }    }
559    
560    public Collection values ()    /**
561       * Returns a "collection view" (or "bag view") of this Map's values.
562       * The collection is backed by the Map, so changes in one show up
563       * in the other.  The collection supports element removal, but not element
564       * addition.
565       * <p>
566       *
567       * <em>The semantics of this set are different from the contract of
568       * Collection in order to make IdentityHashMap work.  This means that
569       * while you can compare these objects between IdentityHashMaps, comparing
570       * them with regular sets is likely to have undefined behavior.</em>
571       * Likewise, contains and remove go by == instead of equals().
572       * <p>
573       *
574       * @return a bag view of the values
575       * @see #keySet()
576       * @see #entrySet()
577       */
578      public Collection values()
579    {    {
580      return new AbstractCollection ()      if (values == null)
581      {        values = new AbstractCollection()
       public int size ()  
582        {        {
583          return size;          public int size()
584        }          {
585              return size;
586            }
587    
588            public Iterator iterator()
589            {
590              return new IdentityIterator(VALUES);
591            }
592    
593            public void clear()
594            {
595              IdentityHashMap.this.clear();
596            }
597    
598            public boolean remove(Object o)
599            {
600              for (int i = table.length - 1; i > 0; i -= 2)
601                if (table[i] == o)
602                  {
603                    table[i - 1] = tombstone;
604                    table[i] = tombstone;
605                    size--;
606                    modCount++;
607                    return true;
608                  }
609              return false;
610            }
611          };
612        return values;
613      }
614    
615        public Iterator iterator ()    /**
616        {     * Helper method which computes the hash code, then traverses the table
617          return new IdentityIterator (IdentityIterator.VALUES);     * until it finds the key, or the spot where the key would go.
618        }     *
619       * @param key the key to check
620       * @return the index where the key belongs
621       * @see #IdentityHashMap(int)
622       * @see #put(Object, Object)
623       */
624      // Package visible for use by nested classes.
625      int hash(Object key)
626      {
627        // Implementation note: it is feasible for the table to have no
628        // emptyslots, if it is full with entries and tombstones, so we must
629        // remember where we started. If we encounter the key or an emptyslot,
630        // we are done.  If we encounter a tombstone, the key may still be in
631        // the array.  If we don't encounter the key, we use the first emptyslot
632        // or tombstone we encountered as the location where the key would go.
633        // By requiring at least 2 key/value slots, and rehashing at 75%
634        // capacity, we guarantee that there will always be either an emptyslot
635        // or a tombstone somewhere in the table.
636        int h = 2 * Math.abs(System.identityHashCode(key) % table.length);
637        int del = -1;
638        int save = h;
639    
640        public void clear ()      do
641        {        {
642          IdentityHashMap.this.clear ();          if (table[h] == key)
643              return h;
644            if (table[h] == emptyslot)
645              break;
646            if (table[h] == tombstone && del < 0)
647              del = h;
648            h -= 2;
649            if (h < 0)
650              h = table.length - 2;
651        }        }
652      };      while (h != save);
653    
654        return del < 0 ? h : del;
655    }    }
656    
657    private class IdentityIterator implements Iterator    /**
658       * This class allows parameterized iteration over IdentityHashMaps.  Based
659       * on its construction, it returns the key or value of a mapping, or
660       * creates the appropriate Map.Entry object with the correct fail-fast
661       * semantics and identity comparisons.
662       *
663       * @author Tom Tromey <tromey@redhat.com>
664       * @author Eric Blake <ebb9@email.byu.edu>
665       */
666      private final class IdentityIterator implements Iterator
667    {    {
668      static final int KEYS = 0;      /**
669      static final int VALUES = 1;       * The type of this Iterator: {@link #KEYS}, {@link #VALUES},
670      static final int ENTRIES = 2;       * or {@link #ENTRIES}.
671         */
672      // Type of iterator.      final int type;
673      int type;      /** The number of modifications to the backing Map that we know about. */
674      // Location in the table.      int knownMod = modCount;
675      int loc;      /** The number of elements remaining to be returned by next(). */
676      // How many items we've seen.      int count = size;
677      int seen;      /** Location in the table. */
678        int loc = table.length;
679      IdentityIterator (int type)  
680        /**
681         * Construct a new Iterator with the supplied type.
682         * @param type {@link #KEYS}, {@link #VALUES}, or {@link #ENTRIES}
683         */
684        IdentityIterator(int type)
685      {      {
686        this.type = type;        this.type = type;
       loc = 0;  
       seen = 0;  
687      }      }
688    
689      public boolean hasNext ()      /**
690         * Returns true if the Iterator has more elements.
691         * @return true if there are more elements
692         * @throws ConcurrentModificationException if the Map was modified
693         */
694        public boolean hasNext()
695      {      {
696        return seen < size;        if (knownMod != modCount)
697            throw new ConcurrentModificationException();
698          return count > 0;
699      }      }
700    
701      public Object next ()      /**
702         * Returns the next element in the Iterator's sequential view.
703         * @return the next element
704         * @throws ConcurrentModificationException if the Map was modified
705         * @throws NoSuchElementException if there is none
706         */
707        public Object next()
708      {      {
709        while (true)        if (knownMod != modCount)
710          {          throw new ConcurrentModificationException();
711            loc += 2;        if (count == 0)
712            if (loc >= table.length)          throw new NoSuchElementException();
713              throw new NoSuchElementException ();        count--;
714            if (table[loc] != tombstone && table[loc] != emptyslot)  
715              {        Object key;
716                ++seen;        do
717                return table[loc];          {
718              }            loc -= 2;
719          }            key = table[loc];
720            }
721          while (key == emptyslot || key == tombstone);
722    
723          return type == KEYS ? key : (type == VALUES ? table[loc + 1]
724                                       : new IdentityEntry(loc));
725      }      }
726    
727      public void remove ()      /**
728         * Removes from the backing Map the last element which was fetched
729         * with the <pre>next()</pre> method.
730         * @throws ConcurrentModificationException if the Map was modified
731         * @throws IllegalStateException if called when there is no last element
732         */
733        public void remove()
734      {      {
735        if (loc >= table.length        if (knownMod != modCount)
736            || table[loc] == tombstone          throw new ConcurrentModificationException();
737            || table[loc] == emptyslot)        if (loc == table.length || table[loc] == tombstone)
738          throw new IllegalStateException ();          throw new IllegalStateException();
739          size--;
740          modCount++;
741          knownMod++;
742        table[loc] = tombstone;        table[loc] = tombstone;
743        table[loc + 1] = tombstone;        table[loc + 1] = tombstone;
       --size;  
744      }      }
745    }    } // class IdentityIterator
746    
747      /**
748       * This class provides Map.Entry objects for IdentityHashMaps.  The entry
749       * is fail-fast, and will throw a ConcurrentModificationException if
750       * the underlying map is modified, or if remove is called on the iterator
751       * that generated this object.  It is identity based, so it violates
752       * the general contract of Map.Entry, and is probably unsuitable for
753       * comparison to normal maps; but it works among other IdentityHashMaps.
754       *
755       * @author Eric Blake <ebb9@email.byu.edu>
756       */
757      private final class IdentityEntry implements Map.Entry
758      {
759        /** The location of this entry. */
760        final int loc;
761        /** The number of modifications to the backing Map that we know about. */
762        final int knownMod = modCount;
763    
764        /**
765         * Constructs the Entry.
766         *
767         * @param loc the location of this entry in table
768         */
769        IdentityEntry(int loc)
770        {
771          this.loc = loc;
772        }
773    
774        /**
775         * Compares the specified object with this entry, using identity
776         * semantics. Note that this can lead to undefined results with
777         * Entry objects created by normal maps.
778         *
779         * @param o the object to compare
780         * @return true if it is equal
781         * @throws ConcurrentModificationException if the entry was invalidated
782         *         by modifying the Map or calling Iterator.remove()
783         */
784        public boolean equals(Object o)
785        {
786          if (knownMod != modCount || table[loc] == tombstone)
787            throw new ConcurrentModificationException();
788          if (! (o instanceof Map.Entry))
789            return false;
790          Map.Entry e = (Map.Entry) o;
791          return table[loc] == e.getKey() && table[loc + 1] == e.getValue();
792        }
793    
794        /**
795         * Returns the key of this entry.
796         *
797         * @return the key
798         * @throws ConcurrentModificationException if the entry was invalidated
799         *         by modifying the Map or calling Iterator.remove()
800         */
801        public Object getKey()
802        {
803          if (knownMod != modCount || table[loc] == tombstone)
804            throw new ConcurrentModificationException();
805          return table[loc];
806        }
807    
808        /**
809         * Returns the value of this entry.
810         *
811         * @return the value
812         * @throws ConcurrentModificationException if the entry was invalidated
813         *         by modifying the Map or calling Iterator.remove()
814         */
815        public Object getValue()
816        {
817          if (knownMod != modCount || table[loc] == tombstone)
818            throw new ConcurrentModificationException();
819          return table[loc + 1];
820        }
821    
822    private void readObject (ObjectInputStream s)      /**
823         * Returns the hashcode of the entry, using identity semantics.
824         * Note that this can lead to undefined results with Entry objects
825         * created by normal maps.
826         *
827         * @return the hash code
828         * @throws ConcurrentModificationException if the entry was invalidated
829         *         by modifying the Map or calling Iterator.remove()
830         */
831        public int hashCode()
832        {
833          if (knownMod != modCount || table[loc] == tombstone)
834            throw new ConcurrentModificationException();
835          return (System.identityHashCode(table[loc])
836                  ^ System.identityHashCode(table[loc + 1]));
837        }
838    
839        /**
840         * Replaces the value of this mapping, and returns the old value.
841         *
842         * @param value the new value
843         * @return the old value
844         * @throws ConcurrentModificationException if the entry was invalidated
845         *         by modifying the Map or calling Iterator.remove()
846         */
847        public Object setValue(Object value)
848        {
849          if (knownMod != modCount || table[loc] == tombstone)
850            throw new ConcurrentModificationException();
851          Object r = table[loc + 1];
852          table[loc + 1] = value;
853          return r;
854        }
855    
856        /**
857         * This provides a string representation of the entry. It is of the form
858         * "key=value", where string concatenation is used on key and value.
859         *
860         * @return the string representation
861         * @throws ConcurrentModificationException if the entry was invalidated
862         *         by modifying the Map or calling Iterator.remove()
863         */
864        public final String toString()
865        {
866          if (knownMod != modCount || table[loc] == tombstone)
867            throw new ConcurrentModificationException();
868          return table[loc] + "=" + table[loc + 1];
869        }
870      } // class IdentityEntry
871    
872      /**
873       * Reads the object from a serial stream.
874       *
875       * @param s the stream to read from
876       * @throws ClassNotFoundException if the underlying stream fails
877       * @throws IOException if the underlying stream fails
878       * @serialData expects the size (int), followed by that many key (Object)
879       *             and value (Object) pairs, with the pairs in no particular
880       *             order
881       */
882      private void readObject(ObjectInputStream s)
883      throws IOException, ClassNotFoundException      throws IOException, ClassNotFoundException
884    {    {
885      int num = s.readInt ();      int num = s.readInt();
886      for (int i = 0; i < num; ++i)      table = new Object[2 * Math.max(num * 2, DEFAULT_CAPACITY)];
887        {      // Read key/value pairs.
888          Object key = s.readObject ();      while (--num >= 0)
889          Object value = s.readObject ();        put(s.readObject(), s.readObject());
         put (key, value);  
       }  
890    }    }
891    
892    private void writeObject (ObjectOutputStream s)    /**
893       * Writes the object to a serial stream.
894       *
895       * @param s the stream to write to
896       * @throws IOException if the underlying stream fails
897       * @serialData outputs the size (int), followed by that many key (Object)
898       *             and value (Object) pairs, with the pairs in no particular
899       *             order
900       */
901      private void writeObject(ObjectOutputStream s)
902      throws IOException      throws IOException
903    {    {
904      s.writeInt (size);      s.writeInt(size);
905      Iterator it = entrySet ().iterator ();      for (int i = table.length - 2; i >= 0; i -= 2)
906      while (it.hasNext ())        {
907        {          Object key = table[i];
908          Map.Entry entry = (Map.Entry) it.next ();          if (key != tombstone && key != emptyslot)
909          s.writeObject (entry.getKey ());            {
910          s.writeObject (entry.getValue ());              s.writeObject(key);
911                s.writeObject(table[i + 1]);
912              }
913        }        }
914    }    }
   
   // Compute the hash value we will use for an object.  
   private int getHash (Object o)  
   {  
     return 2 * Math.abs (System.identityHashCode (o) % (table.length / 2));  
   }  
   
   // Number of items in hash table.  
   private int size;  
   // The table itself.  
   private Object[] table;  
   
   // This object is used to mark deleted items.  
   private static final Object tombstone = new Object ();  
   // This object is used to mark empty slots.  We need this because  
   // using null is ambiguous.  
   private static final Object emptyslot = new Object ();  
915  }  }

Legend:
Removed from v.1.6  
changed lines
  Added in v.1.7

savannah-hackers-public@gnu.org
ViewVC Help
Powered by ViewVC 1.1.26