src/share/classes/java/util/EnumMap.java

Print this page
rev 3765 : 6312706: Map entrySet iterators should return different entries on each call to next()
Reviewed-by: mduigou
Contributed-by: Neil Richards <neil.richards@ngmr.net>

@@ -104,21 +104,25 @@
     private transient int size = 0;
 
     /**
      * Distinguished non-null value for representing null values.
      */
-    private static final Object NULL = new Object();
+    private static final Object NULL = new Object() {
+        public int hashCode() {
+            return 0;
+        }
+    };
 
     private Object maskNull(Object value) {
         return (value == null ? NULL : value);
     }
 
     private V unmaskNull(Object value) {
         return (V) (value == NULL ? null : value);
     }
 
-    private static Enum[] ZERO_LENGTH_ENUM_ARRAY = new Enum[0];
+    private static final Enum[] ZERO_LENGTH_ENUM_ARRAY = new Enum[0];
 
     /**
      * Creates an empty enum map with the specified key type.
      *
      * @param keyType the class object of the key type for this enum map

@@ -462,10 +466,11 @@
 
     private class EntrySet extends AbstractSet<Map.Entry<K,V>> {
         public Iterator<Map.Entry<K,V>> iterator() {
             return new EntryIterator();
         }
+
         public boolean contains(Object o) {
             if (!(o instanceof Map.Entry))
                 return false;
             Map.Entry entry = (Map.Entry)o;
             return containsMapping(entry.getKey(), entry.getValue());

@@ -550,76 +555,88 @@
             lastReturnedIndex = index++;
             return unmaskNull(vals[lastReturnedIndex]);
         }
     }
 
-    /**
-     * Since we don't use Entry objects, we use the Iterator itself as entry.
-     */
-    private class EntryIterator extends EnumMapIterator<Map.Entry<K,V>>
-        implements Map.Entry<K,V>
-    {
+    private class EntryIterator extends EnumMapIterator<Map.Entry<K,V>> {
+        private Entry lastReturnedEntry = null;
+
         public Map.Entry<K,V> next() {
             if (!hasNext())
                 throw new NoSuchElementException();
-            lastReturnedIndex = index++;
-            return this;
+            lastReturnedEntry = new Entry(index++);
+            return lastReturnedEntry;
+        }
+
+        public void remove() {
+            lastReturnedIndex =
+                ((null == lastReturnedEntry) ? -1 : lastReturnedEntry.index);
+            super.remove();
+            lastReturnedEntry.index = lastReturnedIndex;
+            lastReturnedEntry = null;
+        }
+
+        private class Entry implements Map.Entry<K,V> {
+            private int index;
+
+            private Entry(int index) {
+                this.index = index;
         }
 
         public K getKey() {
-            checkLastReturnedIndexForEntryUse();
-            return keyUniverse[lastReturnedIndex];
+                checkIndexForEntryUse();
+                return keyUniverse[index];
         }
 
         public V getValue() {
-            checkLastReturnedIndexForEntryUse();
-            return unmaskNull(vals[lastReturnedIndex]);
+                checkIndexForEntryUse();
+                return unmaskNull(vals[index]);
         }
 
         public V setValue(V value) {
-            checkLastReturnedIndexForEntryUse();
-            V oldValue = unmaskNull(vals[lastReturnedIndex]);
-            vals[lastReturnedIndex] = maskNull(value);
+                checkIndexForEntryUse();
+                V oldValue = unmaskNull(vals[index]);
+                vals[index] = maskNull(value);
             return oldValue;
         }
 
         public boolean equals(Object o) {
-            if (lastReturnedIndex < 0)
+                if (index < 0)
                 return o == this;
 
             if (!(o instanceof Map.Entry))
                 return false;
+
             Map.Entry e = (Map.Entry)o;
-            V ourValue = unmaskNull(vals[lastReturnedIndex]);
+                V ourValue = unmaskNull(vals[index]);
             Object hisValue = e.getValue();
-            return e.getKey() == keyUniverse[lastReturnedIndex] &&
+                return (e.getKey() == keyUniverse[index] &&
                 (ourValue == hisValue ||
-                 (ourValue != null && ourValue.equals(hisValue)));
+                         (ourValue != null && ourValue.equals(hisValue))));
         }
 
         public int hashCode() {
-            if (lastReturnedIndex < 0)
+                if (index < 0)
                 return super.hashCode();
 
-            Object value = vals[lastReturnedIndex];
-            return keyUniverse[lastReturnedIndex].hashCode()
-                ^ (value == NULL ? 0 : value.hashCode());
+                return entryHashCode(index);
         }
 
         public String toString() {
-            if (lastReturnedIndex < 0)
+                if (index < 0)
                 return super.toString();
 
-            return keyUniverse[lastReturnedIndex] + "="
-                + unmaskNull(vals[lastReturnedIndex]);
+                return keyUniverse[index] + "="
+                    + unmaskNull(vals[index]);
         }
 
-        private void checkLastReturnedIndexForEntryUse() {
-            if (lastReturnedIndex < 0)
+            private void checkIndexForEntryUse() {
+                if (index < 0)
                 throw new IllegalStateException("Entry was removed");
         }
     }
+    }
 
     // Comparison and hashing
 
     /**
      * Compares the specified object with this map for equality.  Returns

@@ -629,14 +646,39 @@
      *
      * @param o the object to be compared for equality with this map
      * @return <tt>true</tt> if the specified object is equal to this map
      */
     public boolean equals(Object o) {
-        if (!(o instanceof EnumMap))
-            return super.equals(o);
+        if (this == o)
+            return true;
+        if (o instanceof EnumMap)
+            return equals((EnumMap)o);
+        if (!(o instanceof Map))
+            return false;
 
-        EnumMap em = (EnumMap)o;
+        Map<K,V> m = (Map<K,V>)o;
+        if (size != m.size())
+            return false;
+
+        for (int i = 0; i < keyUniverse.length; i++) {
+            if (null != vals[i]) {
+                K key = keyUniverse[i];
+                V value = unmaskNull(vals[i]);
+                if (null == value) {
+                    if (!((null == m.get(key)) && m.containsKey(key)))
+                       return false;
+                } else {
+                   if (!value.equals(m.get(key)))
+                      return false;
+                }
+            }
+        }
+
+        return true;
+    }
+
+    private boolean equals(EnumMap em) {
         if (em.keyType != keyType)
             return size == 0 && em.size == 0;
 
         // Key types match, compare each value
         for (int i = 0; i < keyUniverse.length; i++) {

@@ -648,10 +690,30 @@
         }
         return true;
     }
 
     /**
+     * Returns the hash code value for this map.  The hash code of a map is
+     * defined to be the sum of the hash codes of each entry in the map.
+     */
+    public int hashCode() {
+        int h = 0;
+
+        for (int i = 0; i < keyUniverse.length; i++) {
+            if (null != vals[i]) {
+                h += entryHashCode(i);
+            }
+        }
+
+        return h;
+    }
+
+    private int entryHashCode(int index) {
+        return (keyUniverse[index].hashCode() ^ vals[index].hashCode());
+    }
+
+    /**
      * Returns a shallow copy of this enum map.  (The values themselves
      * are not cloned.
      *
      * @return a shallow copy of this enum map
      */

@@ -703,13 +765,17 @@
 
         // Write out size (number of Mappings)
         s.writeInt(size);
 
         // Write out keys and values (alternating)
-        for (Map.Entry<K,V> e :  entrySet()) {
-            s.writeObject(e.getKey());
-            s.writeObject(e.getValue());
+        int entriesToBeWritten = size;
+        for (int i = 0; entriesToBeWritten > 0; i++) {
+            if (null != vals[i]) {
+                s.writeObject(keyUniverse[i]);
+                s.writeObject(unmaskNull(vals[i]));
+                entriesToBeWritten--;
+            }
         }
     }
 
     /**
      * Reconstitute the <tt>EnumMap</tt> instance from a stream (i.e.,