Implemented IdentityHashMap with tests.

Review by: jat (pair prog)


git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@2322 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/user/super/com/google/gwt/emul/java/util/AbstractHashMap.java b/user/super/com/google/gwt/emul/java/util/AbstractHashMap.java
new file mode 100644
index 0000000..f775124
--- /dev/null
+++ b/user/super/com/google/gwt/emul/java/util/AbstractHashMap.java
@@ -0,0 +1,470 @@
+/*
+ * Copyright 2008 Google Inc.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package java.util;
+
+import com.google.gwt.core.client.JavaScriptObject;
+
+/**
+ * Implementation of Map interface based on a hash table. <a
+ * href="http://java.sun.com/j2se/1.5.0/docs/api/java/util/HashMap.html">[Sun
+ * docs]</a>
+ * 
+ * @param <K> key type
+ * @param <V> value type
+ */
+abstract class AbstractHashMap<K, V> extends AbstractMap<K, V> {
+  /*
+   * Implementation notes:
+   * 
+   * String keys are stored in a separate map from non-String keys. String keys
+   * are mapped to their values via a JS associative map, stringMap. String keys
+   * could collide with intrinsic properties (like watch, constructor) so we
+   * prepend each key with a ':' inside of stringMap.
+   * 
+   * Integer keys are used to index all non-string keys. A key's hashCode is the
+   * index in hashCodeMap which should contain that key. Since several keys may
+   * have the same hash, each value in hashCodeMap is actually an array
+   * containing all entries whose keys share the same hash.
+   */
+  private final class EntrySet extends AbstractSet<Entry<K, V>> {
+
+    @Override
+    public void clear() {
+      AbstractHashMap.this.clear();
+    }
+
+    @Override
+    public boolean contains(Object o) {
+      if (o instanceof Map.Entry) {
+        Map.Entry<?, ?> entry = (Map.Entry<?, ?>) o;
+        Object key = entry.getKey();
+        if (AbstractHashMap.this.containsKey(key)) {
+          Object value = AbstractHashMap.this.get(key);
+          return AbstractHashMap.this.equals(entry.getValue(), value);
+        }
+      }
+      return false;
+    }
+
+    @Override
+    public Iterator<Entry<K, V>> iterator() {
+      return new EntrySetIterator();
+    }
+
+    @Override
+    public boolean remove(Object entry) {
+      if (contains(entry)) {
+        Object key = ((Map.Entry<?, ?>) entry).getKey();
+        AbstractHashMap.this.remove(key);
+        return true;
+      }
+      return false;
+    }
+
+    @Override
+    public int size() {
+      return AbstractHashMap.this.size();
+    }
+  }
+
+  /**
+   * Iterator for <code>EntrySetImpl</code>.
+   */
+  private final class EntrySetIterator implements Iterator<Entry<K, V>> {
+    private final Iterator<Map.Entry<K, V>> iter;
+    private Map.Entry<K, V> last = null;
+
+    /**
+     * Constructor for <code>EntrySetIterator</code>.
+     */
+    public EntrySetIterator() {
+      List<Map.Entry<K, V>> list = new ArrayList<Map.Entry<K, V>>();
+      if (nullSlotLive) {
+        MapEntryImpl<K, V> entryImpl = new MapEntryImpl<K, V>(null, nullSlot);
+        list.add(entryImpl);
+      }
+      addAllStringEntries(stringMap, list);
+      addAllHashEntries(hashCodeMap, list);
+      this.iter = list.iterator();
+    }
+
+    public boolean hasNext() {
+      return iter.hasNext();
+    }
+
+    public Map.Entry<K, V> next() {
+      return last = iter.next();
+    }
+
+    public void remove() {
+      if (last == null) {
+        throw new IllegalStateException("Must call next() before remove().");
+      } else {
+        iter.remove();
+        AbstractHashMap.this.remove(last.getKey());
+        last = null;
+      }
+    }
+  }
+
+  private static native void addAllHashEntries(JavaScriptObject hashCodeMap,
+      Collection<?> dest) /*-{
+    for (var hashCode in hashCodeMap) {
+      // sanity check that it's really an integer
+      if (hashCode == parseInt(hashCode)) {
+        var array = hashCodeMap[hashCode];
+        for (var i = 0, c = array.length; i < c; ++i) {
+          dest.@java.util.Collection::add(Ljava/lang/Object;)(array[i]);
+        }
+      }
+    }
+  }-*/;
+
+  private static native void addAllStringEntries(JavaScriptObject stringMap,
+      Collection<?> dest) /*-{
+    for (var key in stringMap) {
+      // only keys that start with a colon ':' count
+      if (key.charCodeAt(0) == 58) {
+        var value = stringMap[key];
+        var entry = @java.util.MapEntryImpl::create(Ljava/lang/Object;Ljava/lang/Object;)(key.substring(1), value);
+        dest.@java.util.Collection::add(Ljava/lang/Object;)(entry);
+      }
+    }
+  }-*/;
+
+  /**
+   * A map of integral hashCodes onto entries.
+   */
+  private transient JavaScriptObject hashCodeMap;
+
+  /**
+   * This is the slot that holds the value associated with the "null" key.
+   */
+  private transient V nullSlot;
+
+  private transient boolean nullSlotLive;
+
+  private int size;
+
+  /**
+   * A map of Strings onto values.
+   */
+  private transient JavaScriptObject stringMap;
+
+  {
+    clearImpl();
+  }
+
+  public AbstractHashMap() {
+  }
+
+  public AbstractHashMap(int ignored) {
+    // This implementation of HashMap has no need of initial capacities.
+    this(ignored, 0);
+  }
+
+  public AbstractHashMap(int ignored, float alsoIgnored) {
+    // This implementation of HashMap has no need of load factors or capacities.
+    if (ignored < 0 || alsoIgnored < 0) {
+      throw new IllegalArgumentException(
+          "initial capacity was negative or load factor was non-positive");
+    }
+  }
+
+  public AbstractHashMap(Map<? extends K, ? extends V> toBeCopied) {
+    this.putAll(toBeCopied);
+  }
+
+  @Override
+  public void clear() {
+    clearImpl();
+  }
+
+  public abstract Object clone();
+
+  @Override
+  public boolean containsKey(Object key) {
+    return (key == null) ? nullSlotLive : (!(key instanceof String) ? hasHashValue(
+        key, getHashCode(key)) : hasStringValue((String) key));
+  }
+
+  @Override
+  public boolean containsValue(Object value) {
+    if (nullSlotLive && equals(nullSlot, value)) {
+      return true;
+    } else if (containsStringValue(value)) {
+      return true;
+    } else if (containsHashValue(value)) {
+      return true;
+    }
+    return false;
+  }
+
+  @Override
+  public Set<Map.Entry<K, V>> entrySet() {
+    return new EntrySet();
+  }
+
+  @Override
+  public V get(Object key) {
+    return (key == null) ? nullSlot : (!(key instanceof String) ? getHashValue(
+        key, getHashCode(key)) : getStringValue((String) key));
+  }
+
+  @Override
+  public V put(K key, V value) {
+    return (key == null) ? putNullSlot(value) : (!(key instanceof String)
+        ? putHashValue(key, value, getHashCode(key)) : putStringValue(
+            (String) key, value));
+  }
+
+  @Override
+  public V remove(Object key) {
+    return (key == null) ? removeNullSlot() : (!(key instanceof String) ? removeHashValue(
+        key, getHashCode(key)) : removeStringValue((String) key));
+  }
+
+  @Override
+  public int size() {
+    return size;
+  }
+
+  /**
+   * Subclasses must override to return a whether or not two keys or values are
+   * equal.
+   */
+  protected abstract boolean equals(Object value1, Object value2);
+
+  /**
+   * Subclasses must override to return a hash code for a given key. The key is
+   * guaranteed to be non-null and not a String.
+   */
+  protected abstract int getHashCode(Object key);
+
+  private void clearImpl() {
+    hashCodeMap = JavaScriptObject.createArray();
+    stringMap = JavaScriptObject.createObject();
+    nullSlotLive = false;
+    nullSlot = null;
+    size = 0;
+  }
+
+  /**
+   * Returns true if hashCodeMap contains any Map.Entry whose value is Object
+   * equal to <code>value</code>.
+   */
+  private native boolean containsHashValue(Object value) /*-{
+    var hashCodeMap = this.@java.util.AbstractHashMap::hashCodeMap;
+    for (var hashCode in hashCodeMap) {
+      // sanity check that it's really one of ours
+      if (hashCode == parseInt(hashCode)) {
+        var array = hashCodeMap[hashCode];
+        for (var i = 0, c = array.length; i < c; ++i) {
+          var entry = array[i];
+          var entryValue = entry.@java.util.Map$Entry::getValue()();
+          if (this.@java.util.AbstractHashMap::equalsBridge(Ljava/lang/Object;Ljava/lang/Object;)(value, entryValue)) {
+            return true;
+          }
+        }
+      }
+    }
+    return false;
+  }-*/;
+
+  /**
+   * Returns true if stringMap contains any key whose value is Object equal to
+   * <code>value</code>.
+   */
+  private native boolean containsStringValue(Object value) /*-{
+    var stringMap = this.@java.util.AbstractHashMap::stringMap;
+    for (var key in stringMap) {
+      // only keys that start with a colon ':' count
+      if (key.charCodeAt(0) == 58) {
+        var entryValue = stringMap[key];
+        if (this.@java.util.AbstractHashMap::equalsBridge(Ljava/lang/Object;Ljava/lang/Object;)(value, entryValue)) {
+          return true;
+        }
+      }
+    }
+    return false;
+  }-*/;
+
+  /**
+   * Bridge method from JSNI that keeps us from having to make polymorphic calls
+   * in JSNI. By putting the polymorphism in Java code, the compiler can do a
+   * better job of optimizing in most cases.
+   */
+  @SuppressWarnings("unused")
+  private boolean equalsBridge(Object value1, Object value2) {
+    return equals(value1, value2);
+  }
+
+  /**
+   * Returns the Map.Entry whose key is Object equal to <code>key</code>,
+   * provided that <code>key</code>'s hash code is <code>hashCode</code>;
+   * or <code>null</code> if no such Map.Entry exists at the specified
+   * hashCode.
+   */
+  private native V getHashValue(Object key, int hashCode) /*-{
+    var array = this.@java.util.AbstractHashMap::hashCodeMap[hashCode];
+    if (array) {
+      for (var i = 0, c = array.length; i < c; ++i) {
+        var entry = array[i];
+        var entryKey = entry.@java.util.Map$Entry::getKey()();
+        if (this.@java.util.AbstractHashMap::equalsBridge(Ljava/lang/Object;Ljava/lang/Object;)(key, entryKey)) {
+          return entry.@java.util.Map$Entry::getValue()();
+        }
+      }
+    }
+    return null;
+  }-*/;
+
+  /**
+   * Returns the value for the given key in the stringMap. Returns
+   * <code>null</code> if the specified key does not exist.
+   */
+  private native V getStringValue(String key) /*-{
+    return (_ = this.@java.util.AbstractHashMap::stringMap[':' + key]) == null ? null : _ ;
+  }-*/;
+  
+  /**
+   * Returns true if the a key exists in the hashCodeMap that is Object equal to
+   * <code>key</code>, provided that <code>key</code>'s hash code is
+   * <code>hashCode</code>.
+   */
+  private native boolean hasHashValue(Object key, int hashCode) /*-{
+    var array = this.@java.util.AbstractHashMap::hashCodeMap[hashCode];
+    if (array) {
+      for (var i = 0, c = array.length; i < c; ++i) {
+        var entry = array[i];
+        var entryKey = entry.@java.util.Map$Entry::getKey()();
+        if (this.@java.util.AbstractHashMap::equalsBridge(Ljava/lang/Object;Ljava/lang/Object;)(key, entryKey)) {
+          return true;
+        }
+      }
+    }
+    return false;
+  }-*/;
+
+  /**
+   * Returns true if the given key exists in the stringMap.
+   */
+  private native boolean hasStringValue(String key) /*-{
+    return (':' + key) in this.@java.util.AbstractHashMap::stringMap;
+  }-*/;
+  
+  /**
+   * Sets the specified key to the specified value in the hashCodeMap. Returns
+   * the value previously at that key. Returns <code>null</code> if the
+   * specified key did not exist.
+   */
+  private native V putHashValue(K key, V value, int hashCode) /*-{
+    var array = this.@java.util.AbstractHashMap::hashCodeMap[hashCode];
+    if (array) {
+      for (var i = 0, c = array.length; i < c; ++i) {
+        var entry = array[i];
+        var entryKey = entry.@java.util.Map$Entry::getKey()();
+        if (this.@java.util.AbstractHashMap::equalsBridge(Ljava/lang/Object;Ljava/lang/Object;)(key, entryKey)) {
+          // Found an exact match, just update the existing entry
+          var previous = entry.@java.util.Map$Entry::getValue()();
+          entry.@java.util.Map$Entry::setValue(Ljava/lang/Object;)(value);
+          return previous;
+        }
+      }
+    } else {
+      array = this.@java.util.AbstractHashMap::hashCodeMap[hashCode] = [];
+    }
+    var entry = @java.util.MapEntryImpl::create(Ljava/lang/Object;Ljava/lang/Object;)(key, value);
+    array.push(entry);
+    ++this.@java.util.AbstractHashMap::size;
+    return null;
+  }-*/;
+
+  private V putNullSlot(V value) {
+    V result = nullSlot;
+    nullSlot = value;
+    if (!nullSlotLive) {
+      nullSlotLive = true;
+      ++size;
+    }
+    return result;
+  }
+
+  /**
+   * Sets the specified key to the specified value in the stringMap. Returns the
+   * value previously at that key. Returns <code>null</code> if the specified
+   * key did not exist.
+   */
+  private native V putStringValue(String key, V value) /*-{
+    key = ':' + key;
+    var result = this.@java.util.AbstractHashMap::stringMap[key];
+    this.@java.util.AbstractHashMap::stringMap[key] = value;
+    return (result === undefined) ? 
+      (++this.@java.util.AbstractHashMap::size, null) : result;
+  }-*/;
+
+  /**
+   * Removes the pair whose key is Object equal to <code>key</code> from
+   * <code>hashCodeMap</code>, provided that <code>key</code>'s hash code
+   * is <code>hashCode</code>. Returns the value that was associated with the
+   * removed key, or null if no such key existed.
+   */
+  private native V removeHashValue(Object key, int hashCode) /*-{
+    var array = this.@java.util.AbstractHashMap::hashCodeMap[hashCode];
+    if (array) {
+      for (var i = 0, c = array.length; i < c; ++i) {
+        var entry = array[i];
+        var entryKey = entry.@java.util.Map$Entry::getKey()();
+        if (this.@java.util.AbstractHashMap::equalsBridge(Ljava/lang/Object;Ljava/lang/Object;)(key, entryKey)) {
+          if (array.length == 1) {
+            // remove the whole array
+            delete this.@java.util.AbstractHashMap::hashCodeMap[hashCode];
+          } else {
+            // splice out the entry we're removing
+            array.splice(i, 1);
+          }
+          --this.@java.util.AbstractHashMap::size;
+          return entry.@java.util.Map$Entry::getValue()();
+        }
+      }
+    }
+    return null;
+  }-*/;
+
+  private V removeNullSlot() {
+    V result = nullSlot;
+    nullSlot = null;
+    if (nullSlotLive) {
+      nullSlotLive = false;
+      --size;
+    }
+    return result;
+  }
+
+  /**
+   * Removes the specified key from the stringMap and returns the value that was
+   * previously there. Returns <code>null</code> if the specified key
+   * does not exist.
+   */
+  private native V removeStringValue(String key) /*-{
+    key = ':' + key;
+    var result = this.@java.util.AbstractHashMap::stringMap[key];
+    return (result === undefined) ? null :
+      (--this.@java.util.AbstractHashMap::size,
+       delete this.@java.util.AbstractHashMap::stringMap[key],
+       result);
+  }-*/;
+}
diff --git a/user/super/com/google/gwt/emul/java/util/AbstractMap.java b/user/super/com/google/gwt/emul/java/util/AbstractMap.java
index 7a99dc3..e85ba13 100644
--- a/user/super/com/google/gwt/emul/java/util/AbstractMap.java
+++ b/user/super/com/google/gwt/emul/java/util/AbstractMap.java
@@ -60,16 +60,17 @@
       return false;
     }
     Map<?, ?> otherMap = (Map<?, ?>) obj;
-    Set<K> keys = keySet();
-    Set<?> otherKeys = otherMap.keySet();
-    if (!keys.equals(otherKeys)) {
+    if (size() != otherMap.size()) {
       return false;
     }
-    for (Iterator<K> iter = keys.iterator(); iter.hasNext();) {
-      K key = iter.next();
-      V value = get(key);
-      Object otherValue = otherMap.get(key);
-      if (value == null ? otherValue != null : !value.equals(otherValue)) {
+
+    for (Entry<?, ?> entry : otherMap.entrySet()) {
+      Object otherKey = entry.getKey();
+      Object otherValue = entry.getValue();
+      if (!containsKey(otherKey)) {
+        return false;
+      }
+      if (!Utility.equalsWithNullCheck(otherValue, get(otherKey))) {
         return false;
       }
     }
@@ -84,8 +85,7 @@
   @Override
   public int hashCode() {
     int hashCode = 0;
-    for (Iterator<Entry<K, V>> iter = entrySet().iterator(); iter.hasNext();) {
-      Entry<K, V> entry = iter.next();
+    for (Entry<K, V> entry : entrySet()) {
       hashCode += entry.hashCode();
     }
     return hashCode;
diff --git a/user/super/com/google/gwt/emul/java/util/HashMap.java b/user/super/com/google/gwt/emul/java/util/HashMap.java
index 8cf4abb..713057d 100644
--- a/user/super/com/google/gwt/emul/java/util/HashMap.java
+++ b/user/super/com/google/gwt/emul/java/util/HashMap.java
@@ -1,452 +1,65 @@
-/*
- * Copyright 2007 Google Inc.
- * 
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
- * 
- * http://www.apache.org/licenses/LICENSE-2.0
- * 
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
- */
-package java.util;
-
-import com.google.gwt.core.client.JavaScriptObject;
-
-import java.io.Serializable;
-
-/**
- * Implementation of Map interface based on a hash table. <a
- * href="http://java.sun.com/j2se/1.5.0/docs/api/java/util/HashMap.html">[Sun
- * docs]</a>
- * 
- * @param <K> key type
- * @param <V> value type
- */
-public class HashMap<K, V> extends AbstractMap<K, V> implements Serializable {
-  /*
-   * Implementation notes:
-   * 
-   * String keys are stored in a separate map from non-String keys. String keys
-   * are mapped to their values via a JS associative map, stringMap. String keys
-   * could collide with intrinsic properties (like watch, constructor) so we
-   * prepend each key with a ':' inside of stringMap.
-   * 
-   * Integer keys are used to index all non-string keys. A key's hashCode is the
-   * index in hashCodeMap which should contain that key. Since several keys may
-   * have the same hash, each value in hashCodeMap is actually an array
-   * containing all entries whose keys share the same hash.
-   */
-  private final class EntrySet extends AbstractSet<Entry<K, V>> {
-
-    @Override
-    public void clear() {
-      HashMap.this.clear();
-    }
-
-    @Override
-    public boolean contains(Object o) {
-      if (o instanceof Map.Entry) {
-        Map.Entry<?, ?> entry = (Map.Entry<?, ?>) o;
-        Object key = entry.getKey();
-        if (HashMap.this.containsKey(key)) {
-          Object value = HashMap.this.get(key);
-          return Utility.equalsWithNullCheck(entry.getValue(), value);
-        }
-      }
-      return false;
-    }
-
-    @Override
-    public Iterator<Entry<K, V>> iterator() {
-      return new EntrySetIterator();
-    }
-
-    @Override
-    public boolean remove(Object entry) {
-      if (contains(entry)) {
-        Object key = ((Map.Entry<?, ?>) entry).getKey();
-        HashMap.this.remove(key);
-        return true;
-      }
-      return false;
-    }
-
-    @Override
-    public int size() {
-      return HashMap.this.size();
-    }
-  }
-
-  /**
-   * Iterator for <code>EntrySetImpl</code>.
-   */
-  private final class EntrySetIterator implements Iterator<Entry<K, V>> {
-    private final Iterator<Map.Entry<K, V>> iter;
-    private Map.Entry<K, V> last = null;
-
-    /**
-     * Constructor for <code>EntrySetIterator</code>.
-     */
-    public EntrySetIterator() {
-      List<Map.Entry<K, V>> list = new ArrayList<Map.Entry<K, V>>();
-      if (nullSlotLive) {
-        MapEntryImpl<K, V> entryImpl = new MapEntryImpl<K, V>(null, nullSlot);
-        list.add(entryImpl);
-      }
-      addAllStringEntries(stringMap, list);
-      addAllHashEntries(hashCodeMap, list);
-      this.iter = list.iterator();
-    }
-
-    public boolean hasNext() {
-      return iter.hasNext();
-    }
-
-    public Map.Entry<K, V> next() {
-      return last = iter.next();
-    }
-
-    public void remove() {
-      if (last == null) {
-        throw new IllegalStateException("Must call next() before remove().");
-      } else {
-        iter.remove();
-        HashMap.this.remove(last.getKey());
-        last = null;
-      }
-    }
-  }
-
-  private static native void addAllHashEntries(JavaScriptObject hashCodeMap,
-      Collection<?> dest) /*-{
-    for (var hashCode in hashCodeMap) {
-      // sanity check that it's really an integer
-      if (hashCode == parseInt(hashCode)) {
-        var array = hashCodeMap[hashCode];
-        for (var i = 0, c = array.length; i < c; ++i) {
-          dest.@java.util.Collection::add(Ljava/lang/Object;)(array[i]);
-        }
-      }
-    }
-  }-*/;
-
-  private static native void addAllStringEntries(JavaScriptObject stringMap,
-      Collection<?> dest) /*-{
-    for (var key in stringMap) {
-      // only keys that start with a colon ':' count
-      if (key.charCodeAt(0) == 58) {
-        var value = stringMap[key];
-        var entry = @java.util.MapEntryImpl::create(Ljava/lang/Object;Ljava/lang/Object;)(key.substring(1), value);
-        dest.@java.util.Collection::add(Ljava/lang/Object;)(entry);
-      }
-    }
-  }-*/;
-
-  /**
-   * Returns true if hashCodeMap contains any Map.Entry whose value is Object
-   * equal to <code>value</code>.
-   */
-  private static native boolean containsHashValue(JavaScriptObject hashCodeMap,
-      Object value) /*-{
-    for (var hashCode in hashCodeMap) {
-      // sanity check that it's really one of ours
-      if (hashCode == parseInt(hashCode)) {
-        var array = hashCodeMap[hashCode];
-        for (var i = 0, c = array.length; i < c; ++i) {
-          var entry = array[i];
-          var entryValue = entry.@java.util.Map$Entry::getValue()();
-          if (@java.util.Utility::equalsWithNullCheck(Ljava/lang/Object;Ljava/lang/Object;)(value, entryValue)) {
-            return true;
-          }
-        }
-      }
-    }
-    return false;
-  }-*/;
-
-  /**
-   * Returns true if stringMap contains any key whose value is Object equal to
-   * <code>value</code>.
-   */
-  private static native boolean containsStringValue(JavaScriptObject stringMap,
-      Object value) /*-{
-    for (var key in stringMap) {
-      // only keys that start with a colon ':' count
-      if (key.charCodeAt(0) == 58) {
-        var entryValue = stringMap[key];
-        if (@java.util.Utility::equalsWithNullCheck(Ljava/lang/Object;Ljava/lang/Object;)(value, entryValue)) {
-          return true;
-        }
-      }
-    }
-    return false;
-  }-*/;
-
-  /**
-   * A map of integral hashCodes onto entries.
-   */
-  private transient JavaScriptObject hashCodeMap;
-
-  /**
-   * This is the slot that holds the value associated with the "null" key.
-   */
-  private transient V nullSlot;
-
-  private transient boolean nullSlotLive;
-
-  private int size;
-
-  /**
-   * A map of Strings onto values.
-   */
-  private transient JavaScriptObject stringMap;
-
-  {
-    clearImpl();
-  }
-
-  public HashMap() {
-  }
-
-  public HashMap(int ignored) {
-    // This implementation of HashMap has no need of initial capacities.
-    this(ignored, 0);
-  }
-
-  public HashMap(int ignored, float alsoIgnored) {
-    // This implementation of HashMap has no need of load factors or capacities.
-    if (ignored < 0 || alsoIgnored < 0) {
-      throw new IllegalArgumentException(
-          "initial capacity was negative or load factor was non-positive");
-    }
-  }
-
-  public HashMap(Map<? extends K, ? extends V> toBeCopied) {
-    this.putAll(toBeCopied);
-  }
-
-  @Override
-  public void clear() {
-    clearImpl();
-  }
-
-  public Object clone() {
-    return new HashMap<K, V>(this);
-  }
-
-  @Override
-  public boolean containsKey(Object key) {
-    return (key == null) ? nullSlotLive : (!(key instanceof String) ? hasHashValue(
-        key, key.hashCode()) : hasStringValue((String) key));
-  }
-
-  @Override
-  public boolean containsValue(Object value) {
-    if (nullSlotLive && Utility.equalsWithNullCheck(nullSlot, value)) {
-      return true;
-    } else if (containsStringValue(stringMap, value)) {
-      return true;
-    } else if (containsHashValue(hashCodeMap, value)) {
-      return true;
-    }
-    return false;
-  }
-
-  @Override
-  public Set<Map.Entry<K, V>> entrySet() {
-    return new EntrySet();
-  }
-
-  @Override
-  public V get(Object key) {
-    return (key == null) ? nullSlot : (!(key instanceof String) ? getHashValue(
-        key, key.hashCode()) : getStringValue((String) key));
-  }
-
-  @Override
-  public V put(K key, V value) {
-    return (key == null) ? putNullSlot(value) : (!(key instanceof String)
-        ? putHashValue(key, value, key.hashCode()) : putStringValue(
-            (String) key, value));
-  }
-
-  @Override
-  public V remove(Object key) {
-    return (key == null) ? removeNullSlot() : (!(key instanceof String) ? removeHashValue(
-        key, key.hashCode()) : removeStringValue((String) key));
-  }
-
-  @Override
-  public int size() {
-    return size;
-  }
-
-  private void clearImpl() {
-    hashCodeMap = JavaScriptObject.createArray();
-    stringMap = JavaScriptObject.createObject();
-    nullSlotLive = false;
-    nullSlot = null;
-    size = 0;
-  }
-
-  /**
-   * Returns the Map.Entry whose key is Object equal to <code>key</code>,
-   * provided that <code>key</code>'s hash code is <code>hashCode</code>;
-   * or <code>null</code> if no such Map.Entry exists at the specified
-   * hashCode.
-   */
-  private native V getHashValue(Object key, int hashCode) /*-{
-    var array = this.@java.util.HashMap::hashCodeMap[hashCode];
-    if (array) {
-      for (var i = 0, c = array.length; i < c; ++i) {
-        var entry = array[i];
-        var entryKey = entry.@java.util.Map$Entry::getKey()();
-        if (@java.util.Utility::equalsWithNullCheck(Ljava/lang/Object;Ljava/lang/Object;)(key, entryKey)) {
-          return entry.@java.util.Map$Entry::getValue()();
-        }
-      }
-    }
-    return null;
-  }-*/;
-
-  /**
-   * Returns the value for the given key in the stringMap. Returns
-   * <code>null</code> if the specified key does not exist.
-   */
-  private native V getStringValue(String key) /*-{
-    return (_ = this.@java.util.HashMap::stringMap[':' + key]) == null ? null : _ ;
-  }-*/;
-  
-  /**
-   * Returns true if the a key exists in the hashCodeMap that is Object equal to
-   * <code>key</code>, provided that <code>key</code>'s hash code is
-   * <code>hashCode</code>.
-   */
-  private native boolean hasHashValue(Object key, int hashCode) /*-{
-    var array = this.@java.util.HashMap::hashCodeMap[hashCode];
-    if (array) {
-      for (var i = 0, c = array.length; i < c; ++i) {
-        var entry = array[i];
-        var entryKey = entry.@java.util.Map$Entry::getKey()();
-        if (@java.util.Utility::equalsWithNullCheck(Ljava/lang/Object;Ljava/lang/Object;)(key, entryKey)) {
-          return true;
-        }
-      }
-    }
-    return false;
-  }-*/;
-
-  /**
-   * Returns true if the given key exists in the stringMap.
-   */
-  private native boolean hasStringValue(String key) /*-{
-    return (':' + key) in this.@java.util.HashMap::stringMap;
-  }-*/;
-  
-  /**
-   * Sets the specified key to the specified value in the hashCodeMap. Returns
-   * the value previously at that key. Returns <code>null</code> if the
-   * specified key did not exist.
-   */
-  private native V putHashValue(K key, V value, int hashCode) /*-{
-    var array = this.@java.util.HashMap::hashCodeMap[hashCode];
-    if (array) {
-      for (var i = 0, c = array.length; i < c; ++i) {
-        var entry = array[i];
-        var entryKey = entry.@java.util.Map$Entry::getKey()();
-        if (@java.util.Utility::equalsWithNullCheck(Ljava/lang/Object;Ljava/lang/Object;)(key, entryKey)) {
-          // Found an exact match, just update the existing entry
-          var previous = entry.@java.util.Map$Entry::getValue()();
-          entry.@java.util.Map$Entry::setValue(Ljava/lang/Object;)(value);
-          return previous;
-        }
-      }
-    } else {
-      array = this.@java.util.HashMap::hashCodeMap[hashCode] = [];
-    }
-    var entry = @java.util.MapEntryImpl::create(Ljava/lang/Object;Ljava/lang/Object;)(key, value);
-    array.push(entry);
-    ++this.@java.util.HashMap::size;
-    return null;
-  }-*/;
-
-  private V putNullSlot(V value) {
-    V result = nullSlot;
-    nullSlot = value;
-    if (!nullSlotLive) {
-      nullSlotLive = true;
-      ++size;
-    }
-    return result;
-  }
-
-  /**
-   * Sets the specified key to the specified value in the stringMap. Returns the
-   * value previously at that key. Returns <code>null</code> if the specified
-   * key did not exist.
-   */
-  private native V putStringValue(String key, V value) /*-{
-    key = ':' + key;
-    var result = this.@java.util.HashMap::stringMap[key];
-    this.@java.util.HashMap::stringMap[key] = value;
-    return (result === undefined) ? 
-      (++this.@java.util.HashMap::size, null) : result;
-  }-*/;
-
-  /**
-   * Removes the pair whose key is Object equal to <code>key</code> from
-   * <code>hashCodeMap</code>, provided that <code>key</code>'s hash code
-   * is <code>hashCode</code>. Returns the value that was associated with the
-   * removed key, or null if no such key existed.
-   */
-  private native V removeHashValue(Object key, int hashCode) /*-{
-    var array = this.@java.util.HashMap::hashCodeMap[hashCode];
-    if (array) {
-      for (var i = 0, c = array.length; i < c; ++i) {
-        var entry = array[i];
-        var entryKey = entry.@java.util.Map$Entry::getKey()();
-        if (@java.util.Utility::equalsWithNullCheck(Ljava/lang/Object;Ljava/lang/Object;)(key, entryKey)) {
-          if (array.length == 1) {
-            // remove the whole array
-            delete this.@java.util.HashMap::hashCodeMap[hashCode];
-          } else {
-            // splice out the entry we're removing
-            array.splice(i, 1);
-          }
-          --this.@java.util.HashMap::size;
-          return entry.@java.util.Map$Entry::getValue()();
-        }
-      }
-    }
-    return null;
-  }-*/;
-
-  private V removeNullSlot() {
-    V result = nullSlot;
-    nullSlot = null;
-    if (nullSlotLive) {
-      nullSlotLive = false;
-      --size;
-    }
-    return result;
-  }
-
-  /**
-   * Removes the specified key from the stringMap and returns the value that was
-   * previously there. Returns <code>null</code> if the specified key
-   * does not exist.
-   */
-  private native V removeStringValue(String key) /*-{
-    key = ':' + key;
-    var result = this.@java.util.HashMap::stringMap[key];
-    return (result === undefined) ? null :
-      (--this.@java.util.HashMap::size,
-       delete this.@java.util.HashMap::stringMap[key],
-       result);
-  }-*/;
-}
+/*

+ * Copyright 2008 Google Inc.

+ * 

+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not

+ * use this file except in compliance with the License. You may obtain a copy of

+ * the License at

+ * 

+ * http://www.apache.org/licenses/LICENSE-2.0

+ * 

+ * Unless required by applicable law or agreed to in writing, software

+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT

+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the

+ * License for the specific language governing permissions and limitations under

+ * the License.

+ */

+package java.util;

+

+import java.io.Serializable;

+

+/**

+ * Implementation of Map interface based on a hash table. <a

+ * href="http://java.sun.com/j2se/1.5.0/docs/api/java/util/HashMap.html">[Sun

+ * docs]</a>

+ * 

+ * @param <K> key type

+ * @param <V> value type

+ */

+public class HashMap<K, V> extends AbstractHashMap<K, V> implements Cloneable,

+    Serializable {

+

+  public HashMap() {

+  }

+

+  public HashMap(int ignored) {

+    // This implementation of HashMap has no need of initial capacities.

+    this(ignored, 0);

+  }

+

+  public HashMap(int ignored, float alsoIgnored) {

+    // This implementation of HashMap has no need of load factors or capacities.

+    if (ignored < 0 || alsoIgnored < 0) {

+      throw new IllegalArgumentException(

+          "initial capacity was negative or load factor was non-positive");

+    }

+  }

+

+  public HashMap(Map<? extends K, ? extends V> toBeCopied) {

+    super(toBeCopied);

+  }

+

+  @Override

+  public Object clone() {

+    return new HashMap<K, V>(this);

+  }

+

+  @Override

+  protected boolean equals(Object value1, Object value2) {

+    return Utility.equalsWithNullCheck(value1, value2);

+  }

+

+  @Override

+  protected int getHashCode(Object key) {

+    return key.hashCode();

+  }

+}

diff --git a/user/super/com/google/gwt/emul/java/util/IdentityHashMap.java b/user/super/com/google/gwt/emul/java/util/IdentityHashMap.java
index cc30614..d95b8f7 100644
--- a/user/super/com/google/gwt/emul/java/util/IdentityHashMap.java
+++ b/user/super/com/google/gwt/emul/java/util/IdentityHashMap.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2007 Google Inc.
+ * Copyright 2008 Google Inc.
  * 
  * Licensed under the Apache License, Version 2.0 (the "License"); you may not
  * use this file except in compliance with the License. You may obtain a copy of
@@ -15,7 +15,10 @@
  */
 package java.util;
 
+import com.google.gwt.core.client.impl.Impl;
+
 import java.io.Serializable;
+import java.util.Map.Entry;
 
 /**
  * Map using reference equality on keys. <a
@@ -25,45 +28,71 @@
  * @param <K> key type
  * @param <V> value type
  */
-public class IdentityHashMap<K, V> extends AbstractMap<K, V> implements
+public class IdentityHashMap<K, V> extends AbstractHashMap<K, V> implements
     Map<K, V>, Cloneable, Serializable {
 
   public IdentityHashMap() {
-    this(10);
   }
 
-  public IdentityHashMap(int expectedMaxSize) {
-    // TODO(jat): implement
-    throw new UnsupportedOperationException("IdentityHashMap not implemented");
+  public IdentityHashMap(int ignored) {
+    // This implementation of HashMap has no need of load factors or capacities.
+    if (ignored < 0) {
+      throw new IllegalArgumentException("initial capacity was negative");
+    }
   }
 
-  public IdentityHashMap(Map<? extends K, ? extends V> map) {
-    this(map.size());
-    putAll(map);
+  public IdentityHashMap(Map<? extends K, ? extends V> toBeCopied) {
+    super(toBeCopied);
   }
 
   @Override
-  public Set<java.util.Map.Entry<K, V>> entrySet() {
-    // TODO(jat): implement
-    return null;
+  public Object clone() {
+    return new IdentityHashMap<K, V>(this);
   }
 
   @Override
-  public V get(Object key) {
-    // TODO(jat): implement
-    return null;
+  public boolean equals(Object obj) {
+    if (obj == this) {
+      return true;
+    }
+    if (!(obj instanceof Map)) {
+      return false;
+    }
+    Map<?, ?> otherMap = (Map<?, ?>) obj;
+    if (size() != otherMap.size()) {
+      return false;
+    }
+
+    for (Entry<?, ?> entry : otherMap.entrySet()) {
+      Object otherKey = entry.getKey();
+      Object otherValue = entry.getValue();
+      if (!containsKey(otherKey)) {
+        return false;
+      }
+      if (otherValue != get(otherKey)) {
+        return false;
+      }
+    }
+    return true;
   }
 
   @Override
-  public V put(K key, V value) {
-    // TODO(jat): implement
-    return null;
+  public int hashCode() {
+    int hashCode = 0;
+    for (Entry<K, V> entry : entrySet()) {
+      hashCode += System.identityHashCode(entry.getKey());
+      hashCode += System.identityHashCode(entry.getValue());
+    }
+    return hashCode;
   }
 
   @Override
-  public V remove(Object key) {
-    // TODO(jat): implement
-    return null;
+  protected boolean equals(Object value1, Object value2) {
+    return value1 == value2;
   }
 
+  @Override
+  protected int getHashCode(Object key) {
+    return Impl.getHashCode(key);
+  }
 }
diff --git a/user/test/com/google/gwt/emultest/EmulSuite.java b/user/test/com/google/gwt/emultest/EmulSuite.java
index 731a474..002d560 100644
--- a/user/test/com/google/gwt/emultest/EmulSuite.java
+++ b/user/test/com/google/gwt/emultest/EmulSuite.java
@@ -38,6 +38,7 @@
 import com.google.gwt.emultest.java.util.EnumSetTest;
 import com.google.gwt.emultest.java.util.HashMapTest;
 import com.google.gwt.emultest.java.util.HashSetTest;
+import com.google.gwt.emultest.java.util.IdentityHashMapTest;
 import com.google.gwt.emultest.java.util.StackTest;
 import com.google.gwt.junit.tools.GWTTestSuite;
 
@@ -77,6 +78,7 @@
     suite.addTestSuite(EnumSetTest.class);
     suite.addTestSuite(HashMapTest.class);
     suite.addTestSuite(HashSetTest.class);
+    suite.addTestSuite(IdentityHashMapTest.class);
     suite.addTestSuite(StackTest.class);
     // $JUnit-END$
 
diff --git a/user/test/com/google/gwt/emultest/java/util/IdentityHashMapTest.java b/user/test/com/google/gwt/emultest/java/util/IdentityHashMapTest.java
new file mode 100644
index 0000000..6bddf46
--- /dev/null
+++ b/user/test/com/google/gwt/emultest/java/util/IdentityHashMapTest.java
@@ -0,0 +1,709 @@
+/*
+ * Copyright 2006 Google Inc.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.emultest.java.util;
+
+import com.google.gwt.core.client.GWT;
+
+import org.apache.commons.collections.TestMap;
+
+import java.util.Collection;
+import java.util.IdentityHashMap;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Set;
+import java.util.Map.Entry;
+
+/**
+ * Tests <code>IdentityHashMap</code>.
+ */
+public class IdentityHashMapTest extends TestMap {
+
+  /**
+   * A class that is equal to all other instances of itself; used to ensure that
+   * identity rather than equality is being checked.
+   */
+  private static class Foo {
+    @Override
+    public boolean equals(Object obj) {
+      return obj instanceof Foo;
+    }
+
+    @Override
+    public int hashCode() {
+      return 0;
+    }
+  }
+
+  private static final int CAPACITY_16 = 16;
+  private static final int CAPACITY_NEG_ONE_HALF = -1;
+  private static final int CAPACITY_ZERO = 0;
+  private static final Integer INTEGER_1 = new Integer(1);
+  private static final Integer INTEGER_11 = new Integer(11);
+  private static final Integer INTEGER_2 = new Integer(2);
+  private static final Integer INTEGER_22 = new Integer(22);
+  private static final Integer INTEGER_3 = new Integer(3);
+  private static final Integer INTEGER_33 = new Integer(33);
+  private static final Integer INTEGER_ZERO_KEY = new Integer(0);
+  private static final String INTEGER_ZERO_VALUE = "integer zero";
+  private static final String KEY_1 = "key1";
+  private static final String KEY_2 = "key2";
+  private static final String KEY_3 = "key3";
+  private static final String KEY_4 = "key4";
+  private static final String KEY_KEY = "key";
+  private static final String KEY_TEST_CONTAINS_KEY = "testContainsKey";
+  private static final String KEY_TEST_CONTAINS_VALUE = "testContainsValue";
+  private static final String KEY_TEST_ENTRY_SET = "testEntrySet";
+  private static final String KEY_TEST_GET = "testGet";
+  private static final String KEY_TEST_KEY_SET = "testKeySet";
+  private static final String KEY_TEST_PUT = "testPut";
+  private static final String KEY_TEST_REMOVE = "testRemove";
+  private static final float LOAD_FACTOR_NEG_ONE = -1.0F;
+  private static final float LOAD_FACTOR_ONE_HALF = 0.5F;
+  private static final float LOAD_FACTOR_ONE_TENTH = 0.1F;
+  private static final float LOAD_FACTOR_ZERO = 0.0F;
+  private static final Object ODD_ZERO_KEY = new Object() {
+    public int hashCode() {
+      return 0;
+    }
+  };
+  private static final String ODD_ZERO_VALUE = "odd zero";
+  private static final int SIZE_ONE = 1;
+  private static final int SIZE_THREE = 3;
+  private static final int SIZE_TWO = 2;
+  private static final int SIZE_ZERO = 0;
+  private static final String STRING_ZERO_KEY = "0";
+  private static final String STRING_ZERO_VALUE = "string zero";
+  private static final String VALUE_1 = "val1";
+  private static final String VALUE_2 = "val2";
+  private static final String VALUE_3 = "val3";
+  private static final String VALUE_4 = "val4";
+  private static final String VALUE_TEST_CONTAINS_DOES_NOT_EXIST = "does not exist";
+  private static final Integer VALUE_TEST_CONTAINS_KEY = new Integer(5);
+  private static final String VALUE_TEST_ENTRY_SET_1 = KEY_TEST_ENTRY_SET
+      + " - value1";
+  private static final String VALUE_TEST_ENTRY_SET_2 = KEY_TEST_ENTRY_SET
+      + " - value2";
+  private static final String VALUE_TEST_GET = KEY_TEST_GET + " - Value";
+  private static final String VALUE_TEST_KEY_SET = KEY_TEST_KEY_SET
+      + " - value";
+  private static final String VALUE_TEST_PUT_1 = KEY_TEST_PUT + " - value 1";
+  private static final String VALUE_TEST_PUT_2 = KEY_TEST_PUT + " - value 2";
+  private static final String VALUE_TEST_REMOVE = KEY_TEST_REMOVE + " - value";
+  private static final String VALUE_VAL = "value";
+
+  /**
+   * Check the state of a newly constructed, empty IdentityHashMap.
+   * 
+   * @param hashMap
+   */
+  private static void checkEmptyHashMapAssumptions(IdentityHashMap hashMap) {
+    assertNotNull(hashMap);
+    assertTrue(hashMap.isEmpty());
+
+    assertNotNull(hashMap.values());
+    assertTrue(hashMap.values().isEmpty());
+    assertTrue(hashMap.values().size() == 0);
+
+    assertNotNull(hashMap.keySet());
+    assertTrue(hashMap.keySet().isEmpty());
+    assertTrue(hashMap.keySet().size() == 0);
+
+    assertNotNull(hashMap.entrySet());
+    assertTrue(hashMap.entrySet().isEmpty());
+    assertTrue(hashMap.entrySet().size() == 0);
+
+    assertNotNull(hashMap.entrySet().iterator());
+    assertFalse(hashMap.entrySet().iterator().hasNext());
+  }
+
+  public String getModuleName() {
+    return "com.google.gwt.emultest.EmulSuite";
+  }
+
+  public void testAddWatch() {
+    IdentityHashMap m = new IdentityHashMap();
+    m.put("watch", "watch");
+    assertEquals(m.get("watch"), "watch");
+  }
+
+  public void testAddEqualKeys() {
+    final IdentityHashMap expected = new IdentityHashMap();
+    assertEquals(expected.size(), 0);
+    iterateThrough(expected);
+    expected.put(new Long(45), new Object());
+    assertEquals(expected.size(), 1);
+    iterateThrough(expected);
+    expected.put(new Integer(45), new Object());
+    assertNotSame(new Integer(45), new Long(45));
+    assertEquals(expected.size(), 2);
+    iterateThrough(expected);
+  }
+
+  /*
+   * Test method for 'java.util.IdentityHashMap.clear()'
+   */
+  public void testClear() {
+    IdentityHashMap hashMap = new IdentityHashMap();
+    checkEmptyHashMapAssumptions(hashMap);
+
+    hashMap.put("Hello", "Bye");
+    assertFalse(hashMap.isEmpty());
+    assertTrue(hashMap.size() == SIZE_ONE);
+
+    hashMap.clear();
+    assertTrue(hashMap.isEmpty());
+    assertTrue(hashMap.size() == 0);
+  }
+
+  /*
+   * Test method for 'java.util.IdentityHashMap.clone()'
+   */
+  public void testClone() {
+    IdentityHashMap srcMap = new IdentityHashMap();
+    checkEmptyHashMapAssumptions(srcMap);
+
+    // Check empty clone behavior
+    IdentityHashMap dstMap = (IdentityHashMap) srcMap.clone();
+    assertNotNull(dstMap);
+    assertEquals(dstMap.size(), srcMap.size());
+    // assertTrue(dstMap.values().toArray().equals(srcMap.values().toArray()));
+    assertTrue(dstMap.keySet().equals(srcMap.keySet()));
+    assertTrue(dstMap.entrySet().equals(srcMap.entrySet()));
+
+    // Check non-empty clone behavior
+    srcMap.put(KEY_1, VALUE_1);
+    srcMap.put(KEY_2, VALUE_2);
+    srcMap.put(KEY_3, VALUE_3);
+    dstMap = (IdentityHashMap) srcMap.clone();
+    assertNotNull(dstMap);
+    assertEquals(dstMap.size(), srcMap.size());
+
+    assertTrue(dstMap.keySet().equals(srcMap.keySet()));
+
+    assertTrue(dstMap.entrySet().equals(srcMap.entrySet()));
+  }
+
+  /*
+   * Test method for 'java.util.IdentityHashMap.containsKey(Object)'
+   */
+  public void testContainsKey() {
+    IdentityHashMap hashMap = new IdentityHashMap();
+    checkEmptyHashMapAssumptions(hashMap);
+
+    assertFalse(hashMap.containsKey(KEY_TEST_CONTAINS_KEY));
+    hashMap.put(KEY_TEST_CONTAINS_KEY, VALUE_TEST_CONTAINS_KEY);
+    assertTrue(hashMap.containsKey(KEY_TEST_CONTAINS_KEY));
+    assertFalse(hashMap.containsKey(VALUE_TEST_CONTAINS_DOES_NOT_EXIST));
+
+    assertFalse(hashMap.containsKey(null));
+    hashMap.put(null, VALUE_TEST_CONTAINS_KEY);
+    assertTrue(hashMap.containsKey(null));
+  }
+
+  /*
+   * Test method for 'java.util.IdentityHashMap.containsValue(Object)'
+   */
+  public void testContainsValue() {
+    IdentityHashMap hashMap = new IdentityHashMap();
+    checkEmptyHashMapAssumptions(hashMap);
+
+    assertFalse("check contains of empty map",
+        hashMap.containsValue(VALUE_TEST_CONTAINS_KEY));
+    hashMap.put(KEY_TEST_CONTAINS_VALUE, VALUE_TEST_CONTAINS_KEY);
+    assertTrue("check contains of map with element",
+        hashMap.containsValue(VALUE_TEST_CONTAINS_KEY));
+    assertFalse("check contains of map other element",
+        hashMap.containsValue(VALUE_TEST_CONTAINS_DOES_NOT_EXIST));
+
+    if (useNullValue()) {
+      assertFalse(hashMap.containsValue(null));
+    }
+    hashMap.put(KEY_TEST_CONTAINS_VALUE, null);
+    assertTrue(hashMap.containsValue(null));
+  }
+
+  /*
+   * Test method for 'java.util.IdentityHashMap.entrySet()'
+   */
+  public void testEntrySet() {
+    IdentityHashMap hashMap = new IdentityHashMap();
+    checkEmptyHashMapAssumptions(hashMap);
+
+    Set entrySet = hashMap.entrySet();
+    assertNotNull(entrySet);
+
+    // Check that the entry set looks right
+    hashMap.put(KEY_TEST_ENTRY_SET, VALUE_TEST_ENTRY_SET_1);
+    entrySet = hashMap.entrySet();
+    assertEquals(entrySet.size(), SIZE_ONE);
+    Iterator itSet = entrySet.iterator();
+    Map.Entry entry = (Map.Entry) itSet.next();
+    assertEquals(entry.getKey(), KEY_TEST_ENTRY_SET);
+    assertEquals(entry.getValue(), VALUE_TEST_ENTRY_SET_1);
+
+    // Check that entries in the entrySet are update correctly on overwrites
+    hashMap.put(KEY_TEST_ENTRY_SET, VALUE_TEST_ENTRY_SET_2);
+    entrySet = hashMap.entrySet();
+    assertEquals(entrySet.size(), SIZE_ONE);
+    itSet = entrySet.iterator();
+    entry = (Map.Entry) itSet.next();
+    assertEquals(entry.getKey(), KEY_TEST_ENTRY_SET);
+    assertEquals(entry.getValue(), VALUE_TEST_ENTRY_SET_2);
+
+    // Check that entries are updated on removes
+    hashMap.remove(KEY_TEST_ENTRY_SET);
+    checkEmptyHashMapAssumptions(hashMap);
+  }
+
+  /*
+   * Used to test the entrySet remove method.
+   */
+  public void testEntrySetRemove() {
+    IdentityHashMap hashMap = new IdentityHashMap();
+    hashMap.put("A", "B");
+    IdentityHashMap dummy = new IdentityHashMap();
+    dummy.put("A", "b");
+    Entry bogus = (Entry) dummy.entrySet().iterator().next();
+    Set entrySet = hashMap.entrySet();
+    boolean removed = entrySet.remove(bogus);
+    assertEquals(removed, false);
+    assertEquals(hashMap.get("A"), "B");
+  }
+
+  /*
+   * Test method for 'java.util.AbstractMap.equals(Object)'
+   */
+  public void testEquals() {
+    IdentityHashMap hashMap = new IdentityHashMap();
+    checkEmptyHashMapAssumptions(hashMap);
+
+    hashMap.put(KEY_KEY, VALUE_VAL);
+
+    IdentityHashMap copyMap = (IdentityHashMap) hashMap.clone();
+
+    assertTrue(hashMap.equals(copyMap));
+    hashMap.put(VALUE_VAL, KEY_KEY);
+    assertFalse(hashMap.equals(copyMap));
+  }
+
+  /*
+   * Test method for 'java.lang.Object.finalize()'.
+   */
+  public void testFinalize() {
+    // no tests for finalize
+  }
+
+  /*
+   * Test method for 'java.util.IdentityHashMap.get(Object)'.
+   */
+  public void testGet() {
+    IdentityHashMap hashMap = new IdentityHashMap();
+    checkEmptyHashMapAssumptions(hashMap);
+
+    assertNull(hashMap.get(KEY_TEST_GET));
+    hashMap.put(KEY_TEST_GET, VALUE_TEST_GET);
+    assertNotNull(hashMap.get(KEY_TEST_GET));
+
+    assertNull(hashMap.get(null));
+    hashMap.put(null, VALUE_TEST_GET);
+    assertNotNull(hashMap.get(null));
+
+    hashMap.put(null, null);
+    assertNull(hashMap.get(null));
+  }
+
+  /*
+   * Test method for 'java.util.AbstractMap.hashCode()'.
+   */
+  public void testHashCode() {
+    IdentityHashMap hashMap = new IdentityHashMap();
+    checkEmptyHashMapAssumptions(hashMap);
+
+    // Check that hashCode changes
+    int hashCode1 = hashMap.hashCode();
+    hashMap.put(KEY_KEY, VALUE_VAL);
+    int hashCode2 = hashMap.hashCode();
+
+    assertTrue(hashCode1 != hashCode2);
+  }
+
+  /*
+   * Test method for 'java.util.IdentityHashMap.IdentityHashMap()'.
+   */
+  public void testHashMap() {
+    IdentityHashMap hashMap = new IdentityHashMap();
+    checkEmptyHashMapAssumptions(hashMap);
+  }
+
+  /*
+   * Test method for 'java.util.IdentityHashMap.IdentityHashMap(int)'
+   */
+  public void testHashMapInt() {
+    IdentityHashMap hashMap = new IdentityHashMap(CAPACITY_16);
+    checkEmptyHashMapAssumptions(hashMap);
+
+    // TODO(mmendez): how do we verify capacity?
+    boolean failed = true;
+    try {
+      new IdentityHashMap(-SIZE_ONE);
+    } catch (Throwable ex) {
+      if (ex instanceof IllegalArgumentException) {
+        failed = false;
+      }
+    }
+
+    if (failed) {
+      fail("Failure testing new IdentityHashMap(-1)");
+    }
+
+    IdentityHashMap zeroSizedHashMap = new IdentityHashMap(0);
+    assertNotNull(zeroSizedHashMap);
+  }
+
+  /*
+   * Test method for 'java.util.IdentityHashMap.IdentityHashMap(Map)'
+   */
+  public void testHashMapMap() {
+    IdentityHashMap srcMap = new IdentityHashMap();
+    assertNotNull(srcMap);
+    checkEmptyHashMapAssumptions(srcMap);
+
+    srcMap.put(INTEGER_1, INTEGER_11);
+    srcMap.put(INTEGER_2, INTEGER_22);
+    srcMap.put(INTEGER_3, INTEGER_33);
+
+    IdentityHashMap hashMap = new IdentityHashMap(srcMap);
+    assertFalse(hashMap.isEmpty());
+    assertTrue(hashMap.size() == SIZE_THREE);
+
+    Collection valColl = hashMap.values();
+    assertTrue(valColl.contains(INTEGER_11));
+    assertTrue(valColl.contains(INTEGER_22));
+    assertTrue(valColl.contains(INTEGER_33));
+
+    Collection keyColl = hashMap.keySet();
+    assertTrue(keyColl.contains(INTEGER_1));
+    assertTrue(keyColl.contains(INTEGER_2));
+    assertTrue(keyColl.contains(INTEGER_3));
+  }
+
+  /**
+   * Test that the implementation differs from a standard map in demanding
+   * identity.
+   */
+  public void testIdentity() {
+    IdentityHashMap hashMap = new IdentityHashMap();
+    checkEmptyHashMapAssumptions(hashMap);
+
+    Foo foo1 = new Foo();
+    assertNull(hashMap.get(foo1));
+    hashMap.put(foo1, VALUE_1);
+    assertNotNull(hashMap.get(foo1));
+    assertSame(VALUE_1, hashMap.get(foo1));
+
+    Foo foo2 = new Foo();
+    assertNull(hashMap.get(foo2));
+  }
+
+  /**
+   * Test that the implementation differs from a standard map in demanding
+   * identity.
+   */
+  public void testIdentityBasedEquality() {
+    IdentityHashMap hashMap1 = new IdentityHashMap();
+    checkEmptyHashMapAssumptions(hashMap1);
+
+    IdentityHashMap hashMap2 = new IdentityHashMap();
+    checkEmptyHashMapAssumptions(hashMap2);
+
+    hashMap1.put(new Foo(), VALUE_1);
+    hashMap2.put(new Foo(), VALUE_1);
+    assertFalse(hashMap1.equals(hashMap2));
+  }
+
+  /**
+   * Test that the implementation differs from a standard map in demanding
+   * identity.
+   */
+  public void testIdentityBasedHashCode() {
+    IdentityHashMap hashMap1 = new IdentityHashMap();
+    checkEmptyHashMapAssumptions(hashMap1);
+
+    IdentityHashMap hashMap2 = new IdentityHashMap();
+    checkEmptyHashMapAssumptions(hashMap2);
+
+    hashMap1.put(new Foo(), VALUE_1);
+    hashMap2.put(new Foo(), VALUE_1);
+    if (GWT.isScript()) {
+      // Only reliable in web mode since hosted mode can have identity hash
+      // collisions.
+      assertFalse(hashMap1.hashCode() == hashMap2.hashCode());
+    }
+  }
+
+  /*
+   * Test method for 'java.util.AbstractMap.isEmpty()'
+   */
+  public void testIsEmpty() {
+    IdentityHashMap srcMap = new IdentityHashMap();
+    checkEmptyHashMapAssumptions(srcMap);
+
+    IdentityHashMap dstMap = new IdentityHashMap();
+    checkEmptyHashMapAssumptions(dstMap);
+
+    dstMap.putAll(srcMap);
+    assertTrue(dstMap.isEmpty());
+
+    dstMap.put(KEY_KEY, VALUE_VAL);
+    assertFalse(dstMap.isEmpty());
+
+    dstMap.remove(KEY_KEY);
+    assertTrue(dstMap.isEmpty());
+    assertEquals(dstMap.size(), 0);
+  }
+
+  public void testKeysConflict() {
+    IdentityHashMap hashMap = new IdentityHashMap();
+
+    hashMap.put(STRING_ZERO_KEY, STRING_ZERO_VALUE);
+    hashMap.put(INTEGER_ZERO_KEY, INTEGER_ZERO_VALUE);
+    hashMap.put(ODD_ZERO_KEY, ODD_ZERO_VALUE);
+    assertEquals(hashMap.get(INTEGER_ZERO_KEY), INTEGER_ZERO_VALUE);
+    assertEquals(hashMap.get(ODD_ZERO_KEY), ODD_ZERO_VALUE);
+    assertEquals(hashMap.get(STRING_ZERO_KEY), STRING_ZERO_VALUE);
+    hashMap.remove(INTEGER_ZERO_KEY);
+    assertEquals(hashMap.get(ODD_ZERO_KEY), ODD_ZERO_VALUE);
+    assertEquals(hashMap.get(STRING_ZERO_KEY), STRING_ZERO_VALUE);
+    assertEquals(hashMap.get(INTEGER_ZERO_KEY), null);
+    hashMap.remove(ODD_ZERO_KEY);
+    assertEquals(hashMap.get(INTEGER_ZERO_KEY), null);
+    assertEquals(hashMap.get(ODD_ZERO_KEY), null);
+    assertEquals(hashMap.get(STRING_ZERO_KEY), STRING_ZERO_VALUE);
+    hashMap.remove(STRING_ZERO_KEY);
+    assertEquals(hashMap.get(INTEGER_ZERO_KEY), null);
+    assertEquals(hashMap.get(ODD_ZERO_KEY), null);
+    assertEquals(hashMap.get(STRING_ZERO_KEY), null);
+    assertEquals(hashMap.size(), 0);
+  }
+
+  /*
+   * Test method for 'java.util.IdentityHashMap.keySet()'
+   */
+  public void testKeySet() {
+    IdentityHashMap hashMap = new IdentityHashMap();
+    checkEmptyHashMapAssumptions(hashMap);
+
+    Set keySet = hashMap.keySet();
+    assertNotNull(keySet);
+    assertTrue(keySet.isEmpty());
+    assertTrue(keySet.size() == 0);
+
+    hashMap.put(KEY_TEST_KEY_SET, VALUE_TEST_KEY_SET);
+
+    assertTrue(keySet.size() == SIZE_ONE);
+    assertTrue(keySet.contains(KEY_TEST_KEY_SET));
+    assertFalse(keySet.contains(VALUE_TEST_KEY_SET));
+    assertFalse(keySet.contains(KEY_TEST_KEY_SET.toUpperCase()));
+  }
+
+  /*
+   * Test method for 'java.util.IdentityHashMap.put(Object, Object)'
+   */
+  public void testPut() {
+    IdentityHashMap hashMap = new IdentityHashMap();
+    checkEmptyHashMapAssumptions(hashMap);
+
+    assertNull(hashMap.put(KEY_TEST_PUT, VALUE_TEST_PUT_1));
+    assertEquals(hashMap.put(KEY_TEST_PUT, VALUE_TEST_PUT_2), VALUE_TEST_PUT_1);
+    assertNull(hashMap.put(null, VALUE_TEST_PUT_1));
+    assertEquals(hashMap.put(null, VALUE_TEST_PUT_2), VALUE_TEST_PUT_1);
+  }
+
+  /**
+   * Test method for 'java.util.IdentityHashMap.putAll(Map)'.
+   */
+  public void testPutAll() {
+    IdentityHashMap srcMap = new IdentityHashMap();
+    checkEmptyHashMapAssumptions(srcMap);
+
+    srcMap.put(KEY_1, VALUE_1);
+    srcMap.put(KEY_2, VALUE_2);
+    srcMap.put(KEY_3, VALUE_3);
+
+    // Make sure that the data is copied correctly
+    IdentityHashMap dstMap = new IdentityHashMap();
+    checkEmptyHashMapAssumptions(dstMap);
+
+    dstMap.putAll(srcMap);
+    assertEquals(srcMap.size(), dstMap.size());
+    assertTrue(dstMap.containsKey(KEY_1));
+    assertTrue(dstMap.containsValue(VALUE_1));
+    assertFalse(dstMap.containsKey(KEY_1.toUpperCase()));
+    assertFalse(dstMap.containsValue(VALUE_1.toUpperCase()));
+
+    assertTrue(dstMap.containsKey(KEY_2));
+    assertTrue(dstMap.containsValue(VALUE_2));
+    assertFalse(dstMap.containsKey(KEY_2.toUpperCase()));
+    assertFalse(dstMap.containsValue(VALUE_2.toUpperCase()));
+
+    assertTrue(dstMap.containsKey(KEY_3));
+    assertTrue(dstMap.containsValue(VALUE_3));
+    assertFalse(dstMap.containsKey(KEY_3.toUpperCase()));
+    assertFalse(dstMap.containsValue(VALUE_3.toUpperCase()));
+
+    // Check that an empty map does not blow away the contents of the
+    // destination map
+    IdentityHashMap emptyMap = new IdentityHashMap();
+    checkEmptyHashMapAssumptions(emptyMap);
+    dstMap.putAll(emptyMap);
+    assertTrue(dstMap.size() == srcMap.size());
+
+    // Check that put all overwrite any existing mapping in the destination map
+    srcMap.put(KEY_1, VALUE_2);
+    srcMap.put(KEY_2, VALUE_3);
+    srcMap.put(KEY_3, VALUE_1);
+
+    dstMap.putAll(srcMap);
+    assertEquals(dstMap.size(), srcMap.size());
+    assertEquals(dstMap.get(KEY_1), VALUE_2);
+    assertEquals(dstMap.get(KEY_2), VALUE_3);
+    assertEquals(dstMap.get(KEY_3), VALUE_1);
+
+    // Check that a putAll does adds data but does not remove it
+
+    srcMap.put(KEY_4, VALUE_4);
+    dstMap.putAll(srcMap);
+    assertEquals(dstMap.size(), srcMap.size());
+    assertTrue(dstMap.containsKey(KEY_4));
+    assertTrue(dstMap.containsValue(VALUE_4));
+    assertEquals(dstMap.get(KEY_1), VALUE_2);
+    assertEquals(dstMap.get(KEY_2), VALUE_3);
+    assertEquals(dstMap.get(KEY_3), VALUE_1);
+    assertEquals(dstMap.get(KEY_4), VALUE_4);
+
+    dstMap.putAll(dstMap);
+  }
+
+  /**
+   * Test method for 'java.util.IdentityHashMap.remove(Object)'.
+   */
+  public void testRemove() {
+    IdentityHashMap hashMap = new IdentityHashMap();
+    checkEmptyHashMapAssumptions(hashMap);
+
+    assertNull(hashMap.remove(null));
+    hashMap.put(null, VALUE_TEST_REMOVE);
+    assertNotNull(hashMap.remove(null));
+
+    hashMap.put(KEY_TEST_REMOVE, VALUE_TEST_REMOVE);
+    assertEquals(hashMap.remove(KEY_TEST_REMOVE), VALUE_TEST_REMOVE);
+    assertNull(hashMap.remove(KEY_TEST_REMOVE));
+  }
+
+  /**
+   * Test method for 'java.util.IdentityHashMap.size()'.
+   */
+  public void testSize() {
+    IdentityHashMap hashMap = new IdentityHashMap();
+    checkEmptyHashMapAssumptions(hashMap);
+
+    // Test size behavior on put
+    assertEquals(hashMap.size(), SIZE_ZERO);
+    hashMap.put(KEY_1, VALUE_1);
+    assertEquals(hashMap.size(), SIZE_ONE);
+    hashMap.put(KEY_2, VALUE_2);
+    assertEquals(hashMap.size(), SIZE_TWO);
+    hashMap.put(KEY_3, VALUE_3);
+    assertEquals(hashMap.size(), SIZE_THREE);
+
+    // Test size behavior on remove
+    hashMap.remove(KEY_1);
+    assertEquals(hashMap.size(), SIZE_TWO);
+    hashMap.remove(KEY_2);
+    assertEquals(hashMap.size(), SIZE_ONE);
+    hashMap.remove(KEY_3);
+    assertEquals(hashMap.size(), SIZE_ZERO);
+
+    // Test size behavior on putAll
+    hashMap.put(KEY_1, VALUE_1);
+    hashMap.put(KEY_2, VALUE_2);
+    hashMap.put(KEY_3, VALUE_3);
+    IdentityHashMap srcMap = new IdentityHashMap(hashMap);
+    hashMap.putAll(srcMap);
+    assertEquals(hashMap.size(), SIZE_THREE);
+
+    // Test size behavior on clear
+    hashMap.clear();
+    assertEquals(hashMap.size(), SIZE_ZERO);
+  }
+
+  /**
+   * Test method for 'java.util.AbstractMap.toString()'.
+   */
+  public void testToString() {
+    IdentityHashMap hashMap = new IdentityHashMap();
+    checkEmptyHashMapAssumptions(hashMap);
+    hashMap.put(KEY_KEY, VALUE_VAL);
+    String entryString = makeEntryString(KEY_KEY, VALUE_VAL);
+    assertTrue(entryString.equals(hashMap.toString()));
+  }
+
+  /**
+   * Test method for 'java.util.AbstractMap.values()'.
+   */
+  public void testValues() {
+    IdentityHashMap hashMap = new IdentityHashMap();
+    checkEmptyHashMapAssumptions(hashMap);
+
+    assertNotNull(hashMap.values());
+
+    hashMap.put(KEY_KEY, VALUE_VAL);
+
+    Collection valColl = hashMap.values();
+    assertNotNull(valColl);
+    assertEquals(valColl.size(), SIZE_ONE);
+
+    Iterator itVal = valColl.iterator();
+    String val = (String) itVal.next();
+    assertEquals(val, VALUE_VAL);
+  }
+
+  protected Map makeEmptyMap() {
+    return new IdentityHashMap();
+  }
+
+  protected Map makeConfirmedMap() {
+    return new IdentityHashMap();
+  }
+
+  @Override
+  protected boolean useNullValue() {
+    // The JRE IdentityHashMap always thinks it has a null value.
+    return false;
+  }
+
+  private Iterator iterateThrough(final IdentityHashMap expected) {
+    Iterator iter = expected.entrySet().iterator();
+    for (int i = 0; i < expected.size(); i++) {
+      iter.next();
+    }
+    return iter;
+  }
+
+  private String makeEntryString(final String key, final String value) {
+    return "{" + key + "=" + value + "}";
+  }
+}
diff --git a/user/test/org/apache/commons/collections/TestMap.java b/user/test/org/apache/commons/collections/TestMap.java
index 10c8749..7c7f97e 100644
--- a/user/test/org/apache/commons/collections/TestMap.java
+++ b/user/test/org/apache/commons/collections/TestMap.java
@@ -282,6 +282,10 @@
      */
     protected abstract Map makeEmptyMap();
 
+    protected Map makeConfirmedMap() {
+      return new HashMap();
+    }
+
     /**
      *  Return a new, populated map.  The mappings in the map should match the
      *  keys and values returned from {@link #getSampleKeys()} and {@link
@@ -882,7 +886,7 @@
     protected void resetEmpty() {
         this.map = makeEmptyMap();
         views();
-        this.confirmed = new HashMap();
+        this.confirmed = makeConfirmedMap();
     }
 
 
@@ -893,7 +897,7 @@
     protected void resetFull() {
         this.map = makeFullMap();
         views();
-        this.confirmed = new HashMap();
+        this.confirmed = makeConfirmedMap();
         Object[] k = getSampleKeys();
         Object[] v = getSampleValues();
         for (int i = 0; i < k.length; i++) {