Replaced the HashMap implementation with a faster JSNI one.

git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@381 8db76d5a-ed1c-0410-87a9-c151d255dfc7
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 7293bf0..4d64ea9 100644
--- a/user/super/com/google/gwt/emul/java/util/AbstractMap.java
+++ b/user/super/com/google/gwt/emul/java/util/AbstractMap.java
@@ -16,7 +16,7 @@
 package java.util;
 
 /**
- * Abstract base class for map implementations.
+ * See Sun's JDK 1.4 Javadoc for documentation.
  */
 public abstract class AbstractMap implements Map {
 
@@ -47,18 +47,15 @@
     if (obj == this) {
       return true;
     }
-
     if (!(obj instanceof Map)) {
       return false;
     }
-
     Map otherMap = ((Map) obj);
     Set keys = keySet();
     Set otherKeys = otherMap.keySet();
     if (!keys.equals(otherKeys)) {
       return false;
     }
-
     for (Iterator iter = keys.iterator(); iter.hasNext();) {
       Object key = iter.next();
       Object value = get(key);
@@ -67,7 +64,6 @@
         return false;
       }
     }
-
     return true;
   }
 
@@ -125,7 +121,10 @@
   }
 
   public void putAll(Map t) {
-    throw new UnsupportedOperationException(MSG_CANNOT_MODIFY);
+    for (Iterator iter = t.entrySet().iterator(); iter.hasNext();) {
+      Map.Entry e = (Map.Entry) iter.next();
+      put(e.getKey(), e.getValue());
+    }
   }
 
   public Object remove(Object key) {
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 0835c2d..f50574f 100644
--- a/user/super/com/google/gwt/emul/java/util/HashMap.java
+++ b/user/super/com/google/gwt/emul/java/util/HashMap.java
@@ -13,20 +13,58 @@
  * License for the specific language governing permissions and limitations under
  * the License.
  */
+
 package java.util;
 
-/**
- * Implements a hash table mapping object keys onto object values.
- */
-public class HashMap extends AbstractMap implements Map, Cloneable {
+import com.google.gwt.core.client.JavaScriptObject;
 
-  private static class ImplMapEntry implements Map.Entry {
-    private boolean inUse;
+/**
+ * 
+ * See Sun's JDK 1.4 documentation for documentation on the <code>HashMap</code>
+ * API.
+ * 
+ * This implementation of <code>HashMap</code> uses JavaScript native data
+ * structures to provide high performance in web mode, using string keys to
+ * index strings, and sparse int keys (via hashCode()) to index everything else.
+ */
+public class HashMap extends AbstractMap {
+  /*
+   * Implementation notes:  
+   *   String keys must be handled specially because we need only store one value 
+   *     for a given string key. 
+   *   String keys can collide with integer ones, as JavaScript treats '0' and 0 
+   *     as the same key.  We resolve this by appending an 'S' on each string key.
+   *   Integer keys are used for everything but Strings, as we get an integer by 
+   *     taking the hashcode, and storing the value there.  Since several keys
+   *     may have the same hashcode, we store a list of key/value pairs at the  
+   *     index of the hashcode.  Javascript arrays are sparse, so this usage costs 
+   *     nothing in performance.  Also, String and integer keys can coexist in the
+   *     same data structure, which reduces storage overhead for small HashMaps.
+   *   Things to pursue:
+   *     Benchmarking shared data store (with the 'S' append) vs one structure for 
+   *       Strings and another for everything else).
+   *     Benchmarking the effect of gathering the common parts of get/put/remove 
+   *       into a shared method.  These methods would have several parameters, and 
+   *       some extra control flow.
+   */
+
+  /**
+   * Implementation of <code>HashMap</code> entry.
+   */
+  private static class EntryImpl implements Map.Entry {
 
     private Object key;
 
     private Object value;
 
+    /**
+     * Constructor for <code>EntryImpl</code>.
+     */
+    public EntryImpl(Object key, Object value) {
+      this.key = key;
+      this.value = value;
+    }
+
     public boolean equals(Object a) {
       if (a instanceof Map.Entry) {
         Map.Entry s = (Map.Entry) a;
@@ -46,6 +84,9 @@
       return value;
     }
 
+    /**
+     * Calculate the hash code using Sun's specified algorithm.
+     */
     public int hashCode() {
       int keyHash = 0;
       int valueHash = 0;
@@ -64,6 +105,13 @@
       return old;
     }
 
+    public String toString() {
+      return getKey() + "=" + getValue();
+    }
+
+    /**
+     * Checks to see of a == b correctly handling the case where a is null.
+     */
     private boolean equalsWithNullCheck(Object a, Object b) {
       if (a == b) {
         return true;
@@ -74,343 +122,372 @@
       }
     }
   }
+  
+  /**
+   * Iterator for <code>EntrySetImpl</code>.
+   */
+  private final class EntrySetImplIterator implements Iterator {
+    Object last = null;
 
-  private class ImplMapEntryIterator implements Iterator {
+    private final Iterator iter;
 
     /**
-     * Always points at the next full slot; equal to <code>entries.length</code>
-     * if we're at the end.
+     * Constructor for <code>EntrySetIterator</code>.
      */
-    private int i = 0;
-
-    /**
-     * Always points to the last element returned by next, or <code>-1</code>
-     * if there is no last element.
-     */
-    private int last = -1;
-
-    public ImplMapEntryIterator() {
-      maybeAdvanceToFullSlot();
+    public EntrySetImplIterator() {
+      final List l = new ArrayList();
+      addAllFromJavascriptObject(l, map, BOTH_POS);
+      final Iterator lstIter = l.iterator();
+      this.iter = lstIter;
     }
 
     public boolean hasNext() {
-      return (i < entries.length);
+      return iter.hasNext();
     }
 
     public Object next() {
-      if (!hasNext()) {
-        throw new NoSuchElementException();
-      }
-      last = i++;
-      maybeAdvanceToFullSlot();
-      return entries[last];
+      last = iter.next();
+      return last;
     }
 
     public void remove() {
-      if (last < 0) {
-        throw new IllegalStateException();
+      if (last == null) {
+        throw new IllegalStateException("Must call next() before remove().");
+      } else {
+        iter.remove();
+        HashMap.this.remove(((Entry) last).getKey());
       }
-      // do the remove
-      entries[last].inUse = false;
-      --fullSlots;
-      last = -1;
     }
+  }
 
-    /**
-     * Ensure that <code>i</code> points at a full slot; or is equal to
-     * <code>entries.length</code> if we're at the end.
-     */
-    private void maybeAdvanceToFullSlot() {
-      for (; i < entries.length; ++i) {
-        if (entries[i] != null && entries[i].inUse) {
-          return;
+  /**
+   * Keys are stored in the first position of each pair within the JavaScript
+   * dictionary. This constant encodes that fact.
+   */
+  private static final int KEYS_POS = 0;
+
+  /**
+   * Values are stored in the second position of each pair within the JavaScript
+   * dictionary. This constant encodes that fact.
+   */
+  private static final int VALUES_POS = 1;
+
+  /**
+   * This constant is used when new mapping entries should be generated from the
+   * JavaScript item.
+   */
+  private static final int BOTH_POS = 2;
+
+  protected static Map.Entry createEntry(Object key, Object value) {
+    return new EntryImpl(key, value);
+  }
+
+  // Used by JSNI for key processing, not private to avoid eclipse "unused
+  // method" warning.
+  static String asString(Object o) {
+    // All keys must either be entirely numeric, or end with 'S' or be
+    // the sentinel value "null".
+
+    if (o instanceof String) {
+      // Mark all string keys so they do not conflict with numeric ones.
+      return ((String) o) + "S";
+    } else if (o == null) {
+      return "null";
+    } else {
+      return null;
+    }
+  }
+
+  /**
+   * Finds the <code>JavaScriptObject</code> associated with the given
+   * non-string key.  Used in JSNI.
+   */
+  private static native JavaScriptObject findNode(HashMap me, Object key) /*-{
+    var KEYS_POS = 0;
+    var map = me.@java.util.HashMap::map;
+    var k = key.@java.lang.Object::hashCode()();
+    var candidates = map[k];
+    if (candidates != null) {
+      for (var index in candidates) {
+        var candidate = candidates[index];
+        if (candidate[KEYS_POS].@java.lang.Object::equals(Ljava/lang/Object;)(key)) {
+          return [k, index];
         }
       }
     }
-  }
+    return null;
+  }-*/;
 
   /**
-   * Number of physically empty slots in {@link #entries}.
+   * Underlying JavaScript map.
    */
-  private int emptySlots;
+  private JavaScriptObject map;
 
-  /**
-   * The underlying data store.
-   */
-  private ImplMapEntry[] entries;
-
-  /**
-   * Number of logically full slots in {@link #entries}.
-   */
-  private int fullSlots;
-
-  /**
-   * A number between 0 and 1, exclusive. Used to calculated the new
-   * {@link #threshold} at which this map will be rehashed.
-   */
-  private float loadFactor;
-
-  /**
-   * Always equal to {@link #entries}.length * {@link #loadFactor}. When the
-   * number of non-empty slots exceeds this number, a rehash is performed.
-   */
-  private int threshold;
+  private int currentSize = 0;
 
   public HashMap() {
-    this(16);
+    init();
   }
 
-  public HashMap(int initialCapacity) {
-    this(initialCapacity, 0.75f);
+  public HashMap(HashMap toBeCopied) {
+    this();
+    this.putAll(toBeCopied);
   }
 
-  public HashMap(int initialCapacity, float loadFactor) {
-    if (initialCapacity < 0 || loadFactor <= 0) {
+  public HashMap(int ignored) {
+    // This implementation of HashMap has no need of initial capacities.
+    this(ignored, 0);
+  }
+
+  public HashMap(int ignored, float alsoIgnored) {
+    this();
+    // 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");
     }
-
-    if (initialCapacity == 0) {
-      // Rather than have to check for 0 every time we rehash, bumping 0 to 1
-      // here.
-      initialCapacity = 1;
-    }
-
-    /*
-     * This implementation does not used linked lists in each slot. It is
-     * physically impossible to store more entries than we have slots, so
-     * fLoadFactor must be < 1 or we'll run out of slots.
-     */
-    if (loadFactor > 0.9f) {
-      loadFactor = 0.9f;
-    }
-
-    this.loadFactor = loadFactor;
-    realloc(initialCapacity);
-  }
-
-  public HashMap(Map m) {
-    this(m.size());
-    putAll(m);
   }
 
   public void clear() {
-    fullSlots = 0;
-    realloc(entries.length);
+    init();
+    currentSize = 0;
   }
 
   public Object clone() {
     return new HashMap(this);
   }
 
-  public boolean containsKey(Object key) {
-    int i = implFindSlot(key);
-    if (i >= 0) {
-      ImplMapEntry entry = entries[i];
-      if (entry != null && entry.inUse) {
-        return true;
-      }
+  public native boolean containsKey(Object key) /*-{
+    var k = @java.util.HashMap::asString(Ljava/lang/Object;)(key);
+    if (k == null) {
+      var location = @java.util.HashMap::findNode(Ljava/util/HashMap;Ljava/lang/Object;)(this, key);
+      return (location != null);
+    } else {
+      return this.@java.util.HashMap::map[k] !== undefined;
     }
-    return false;
-  }
+  }-*/;
 
   public boolean containsValue(Object value) {
-    return super.containsValue(value);
+    return values().contains(value);
   }
 
   public Set entrySet() {
     return new AbstractSet() {
+      public boolean contains(Object entryObj) {
+        Entry entry = (Entry) entryObj;
+        if (entry != null) {
+          Object key = entry.getKey();
+          Object value = entry.getValue();
+          // If the value is null, we only want to return true if the found
+          // value equals null AND the HashMap
+          // contains the given key.
+          if (value != null || HashMap.this.containsKey(key)) {
+            Object foundValue = HashMap.this.get(key);
+            if (value == null) {
+              return foundValue == null;
+            } else {
+              return value.equals(foundValue);
+            }
+          }
+        }
+        return false;
+      }
+
       public Iterator iterator() {
-        return new ImplMapEntryIterator();
+        return new EntrySetImplIterator();
+      }
+
+      public boolean remove(Object entry) {
+        if (contains(entry)) {
+          Object key = ((Entry) entry).getKey();
+          HashMap.this.remove(key);
+          return true;
+        } else {
+          return false;
+        }
       }
 
       public int size() {
-        return fullSlots;
+        return HashMap.this.size();
       }
     };
   }
 
-  public Object get(Object key) {
-    int i = implFindSlot(key);
-    if (i >= 0) {
-      ImplMapEntry entry = entries[i];
-      if (entry != null && entry.inUse) {
-        return entry.value;
+  public native Object get(Object key) /*-{
+    var KEYS_POS = 0;
+    var VALUES_POS = 1;
+    var k = @java.util.HashMap::asString(Ljava/lang/Object;)(key);
+    if (k != null) {
+      var current = this.@java.util.HashMap::map[k];
+      if (current === undefined) {
+        return null;
+      } else { 
+        return current;
+      }
+    } else {    
+      k = key.@java.lang.Object::hashCode()();
+    }
+    var candidates = this.@java.util.HashMap::map[k];
+    if (candidates == null) { 
+      return null;
+    }  
+  
+    // Used because candidates may develop holes as deletions are performed.
+    for (var i in candidates) {
+      if (candidates[i][KEYS_POS].@java.lang.Object::equals(Ljava/lang/Object;)(key)) {
+        return candidates[i][VALUES_POS];
       }
     }
-    return null;
+    return null;    
+  }-*/;
+
+  public boolean isEmpty() {
+    return size() == 0;
   }
 
-  public int hashCode() {
-    int accum = 0;
-    Iterator elements = entrySet().iterator();
-    while (elements.hasNext()) {
-      accum += elements.next().hashCode();
+  public native Object put(Object key, Object value) /*-{
+    var KEYS_POS = 0;
+    var VALUES_POS = 1;
+    var previous = null;
+    var k = @java.util.HashMap::asString(Ljava/lang/Object;)(key);
+    if (k != null) {
+      previous = this.@java.util.HashMap::map[k];
+      this.@java.util.HashMap::map[k] = value;
+      if (previous === undefined) {
+        this.@java.util.HashMap::currentSize++;
+        return null;
+      } else { 
+        return previous;
+      }
+    } else {
+      k = key.@java.lang.Object::hashCode()();
     }
-    return accum;
-  }
-
-  public Set keySet() {
-    return super.keySet();
-  }
-
-  public Object put(Object key, Object value) {
-    /*
-     * If the number of non-empty slots exceeds the threshold, rehash.
-     */
-    if ((entries.length - emptySlots) >= threshold) {
-      implRehash();
+    var candidates = this.@java.util.HashMap::map[k];
+    if (candidates == null) { 
+      candidates = [];
+      this.@java.util.HashMap::map[k] = candidates;
+    }  
+  
+    // Used because candidates may develop holes as deletions are performed.
+    for (var i in candidates) {
+      if (candidates[i][KEYS_POS].@java.lang.Object::equals(Ljava/lang/Object;)(key)) {
+        previous = candidates[i][VALUES_POS];
+        candidates[i] = [key, value]; 
+        return previous;
+      }
     }
-    return implPutNoRehash(key, value);
-  }
+    this.@java.util.HashMap::currentSize++;
+    candidates[candidates.length] = [key,value];
+    return null;    
+  }-*/;
 
-  public void putAll(Map m) {
-    Set entrySet = m.entrySet();
-    for (Iterator iter = entrySet.iterator(); iter.hasNext();) {
-      Map.Entry entry = (Map.Entry) iter.next();
+  public void putAll(Map otherMap) {
+    Iterator iter = otherMap.entrySet().iterator();
+    while (iter.hasNext()) {
+      Map.Entry entry = (Entry) iter.next();
       put(entry.getKey(), entry.getValue());
     }
   }
 
-  public Object remove(Object key) {
-    int i = implFindSlot(key);
-    if (i >= 0) {
-      ImplMapEntry entry = entries[i];
-      if (entry != null && entry.inUse) {
-        entry.inUse = false;
-        --fullSlots;
-        return entry.getValue();
-      }
-    }
-    return null;
-  }
-
-  public int size() {
-    return fullSlots;
-  }
-
-  /**
-   * Finds the i slot with the matching key or the first empty slot. This method
-   * intentionally ignores whether or not an entry is marked "in use" because
-   * the table implements "lazy deletion", meaning that every object keeps its
-   * intrinsic location, whether or not it is considered still in the table or
-   * not.
-   * 
-   * @return the i of the slot in which either the entry having the key was
-   *         found or in which the entry would go; -1 if the hash table is
-   *         totally full
-   */
-  private int implFindSlot(Object key) {
-    int hashCode = (key != null ? key.hashCode() : 7919);
-    hashCode = (hashCode < 0 ? -hashCode : hashCode);
-    int capacity = entries.length;
-    int startIndex = (hashCode % capacity);
-    int slotIndex = startIndex;
-    int stopIndex = capacity;
-    for (int i = 0; i < 2; ++i) {
-      for (; slotIndex < stopIndex; ++slotIndex) {
-        Map.Entry entry = entries[slotIndex];
-        if (entry == null) {
-          return slotIndex;
-        }
-
-        Object testKey = entry.getKey();
-        if (key == null ? testKey == null : key.equals(testKey)) {
-          return slotIndex;
-        }
-      }
-
-      // Wrap around
-      //
-      slotIndex = 0;
-      stopIndex = startIndex;
-    }
-    // The hash table is totally full and the matching item was not found.
-    //
-    return -1;
-  }
-
-  /**
-   * Implements 'put' with the assumption that there will definitely be room for
-   * the new item.
-   */
-  private Object implPutNoRehash(Object key, Object value) {
-    // No need to check for (i == -1) because rehash would've made the array big
-    // enough to find a slot
-    int i = implFindSlot(key);
-    if (entries[i] != null) {
-      // Just updating the value that was already there.
-      // Remember that the existing entry might have been deleted.
-      //
-      ImplMapEntry entry = entries[i];
-
-      Object old = null;
-      if (entry.inUse) {
-        old = entry.value;
+  public native Object remove(Object key) /*-{
+    var VALUES_POS = 1;
+    var map = this.@java.util.HashMap::map;
+    var k = @java.util.HashMap::asString(Ljava/lang/Object;)(key);
+    var previous = null;
+    if (k != null) {
+      previous = map[k];
+      delete map[k];
+      if (previous !== undefined) {
+        this.@java.util.HashMap::currentSize--;
+        return previous;
       } else {
-        ++fullSlots;
+        return null;
       }
-
-      entry.value = value;
-      entry.inUse = true;
-      return old;
-    } else {
-      // This a brand new (key, value) pair.
-      //
-      ++fullSlots;
-      --emptySlots;
-      ImplMapEntry entry = new ImplMapEntry();
-      entry.key = key;
-      entry.value = value;
-      entry.inUse = true;
-      entries[i] = entry;
+    }
+    var location = @java.util.HashMap::findNode(Ljava/util/HashMap;Ljava/lang/Object;)(this, key);
+    if (location == null) {
       return null;
     }
+    this.@java.util.HashMap::currentSize--;
+    var hashCode = location[0];
+    var index = location[1];
+    var previous = map[hashCode][index][VALUES_POS];
+    map[hashCode].splice(index,1);
+    if (map[hashCode].length > 0) {
+      // Not the only cell for this hashCode, so no deletion.
+      return previous;
+    }
+    delete map[hashCode];
+    return previous;
+  }-*/;
+
+  public int size() {
+    return currentSize;
+  }
+
+  public Collection values() {
+    List values = new Vector();
+    addAllValuesFromJavascriptObject(values, map);
+    return values;
+  }
+
+  // Package protected to remove eclipse warning marker.
+  void addAllKeysFromJavascriptObject(Collection source,
+      JavaScriptObject javaScriptObject) {
+    addAllFromJavascriptObject(source, javaScriptObject, KEYS_POS);
   }
 
   /**
-   * Implements a rehash. The underlying array store may or may not be doubled.
+   * Adds all keys, values, or entries to the collection depending upon
+   * typeToAdd.
    */
-  private void implRehash() {
-    // Save the old entry array.
-    //
-    ImplMapEntry[] oldEntries = entries;
-
-    /*
-     * Allocate a new entry array. If the actual number or full slots exceeds
-     * the load factor, double the array size. Otherwise just rehash, clearing
-     * out all the empty slots.
-     */
-    int capacity = oldEntries.length;
-    if (fullSlots > threshold) {
-      capacity *= 2;
-    }
-
-    realloc(capacity);
-
-    // Now put all the in-use entries from the old array into the new array.
-    //
-    for (int i = 0, n = oldEntries.length; i < n; ++i) {
-      ImplMapEntry oldEntry = oldEntries[i];
-      if (oldEntry != null && oldEntry.inUse) {
-        int slot = implFindSlot(oldEntry.key);
-        // the array should be big enough to find a slot no matter what
-        assert (slot >= 0);
-        entries[slot] = oldEntry;
+  private native void addAllFromJavascriptObject(Collection source,
+      JavaScriptObject javaScriptObject, int typeToAdd)
+  /*-{
+    var KEYS_POS = 0;
+    var VALUES_POS = 1;
+    var BOTH_POS = 2;
+    var map = this.@java.util.HashMap::map;
+    for (var hashCode in javaScriptObject) {
+      var entry = null;
+      if (hashCode == "null" || hashCode.charAt(hashCode.length - 1) == 'S') {
+        var key = null;
+        if (typeToAdd != VALUES_POS && hashCode != "null") {
+          key = hashCode.substring(0, hashCode.length - 1);
+        }
+         if (typeToAdd == KEYS_POS) {
+          entry = key
+        } else if (typeToAdd == VALUES_POS) {
+          entry = map[hashCode];
+        } else if (typeToAdd == BOTH_POS) {
+          entry = 
+            @java.util.HashMap::createEntry(Ljava/lang/Object;Ljava/lang/Object;)(
+            key, map[hashCode]);
+        } 
+        source.@java.util.Collection::add(Ljava/lang/Object;)(entry);
+      } else {
+        var candidates = map[hashCode];
+        for (var index in candidates) { 
+          if (typeToAdd != BOTH_POS) {
+            entry = candidates[index][typeToAdd];
+          } else {
+            entry = 
+              @java.util.HashMap::createEntry(Ljava/lang/Object;Ljava/lang/Object;)(
+              candidates[index][0], candidates[index][1]);
+          }
+          source.@java.util.Collection::add(Ljava/lang/Object;)(entry);
+        }
       }
     }
+  }-*/;
+
+  private void addAllValuesFromJavascriptObject(Collection source,
+      JavaScriptObject javaScriptObject) {
+    addAllFromJavascriptObject(source, javaScriptObject, VALUES_POS);
   }
 
-  /**
-   * Set entries to a new array of the given capacity. Automatically recomputes
-   * threshold and emptySlots.
-   * 
-   * @param capacity
-   */
-  private void realloc(int capacity) {
-    threshold = (int) (capacity * loadFactor);
-    emptySlots = capacity - fullSlots;
-    entries = new ImplMapEntry[capacity];
-  }
+  private native void init() /*-{
+    this.@java.util.HashMap::map = [];
+  }-*/;
 
 }
diff --git a/user/test/com/google/gwt/emultest/java/util/HashMapTest.java b/user/test/com/google/gwt/emultest/java/util/HashMapTest.java
index f23929c..68eb8f9 100644
--- a/user/test/com/google/gwt/emultest/java/util/HashMapTest.java
+++ b/user/test/com/google/gwt/emultest/java/util/HashMapTest.java
@@ -1,13 +1,89 @@
-// Copyright 2006 Google Inc. All Rights Reserved.
+/*
+ * 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.HashMap;
 import java.util.Iterator;
 import java.util.Map;
 import java.util.Set;
+import java.util.Map.Entry;
 
-public class HashMapTest extends EmulTestBase {
+/**
+ * Tests <code>HashMap</code>.
+ */
+public class HashMapTest extends TestMap {
+  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 HashMap.
    * 
@@ -37,6 +113,25 @@
     return "com.google.gwt.emultest.EmulSuite";
   }
 
+  public void testAddWatch() {
+    HashMap m = new HashMap();
+    m.put("watch", "watch");
+    assertEquals(m.get("watch"), "watch");
+  }
+
+  public void testAddEqualKeys() {
+    final HashMap expected = new HashMap();
+    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.HashMap.clear()'
    */
@@ -46,7 +141,7 @@
 
     hashMap.put("Hello", "Bye");
     assertFalse(hashMap.isEmpty());
-    assertTrue(hashMap.size() == 1);
+    assertTrue(hashMap.size() == SIZE_ONE);
 
     hashMap.clear();
     assertTrue(hashMap.isEmpty());
@@ -68,17 +163,10 @@
     assertTrue(dstMap.keySet().equals(srcMap.keySet()));
     assertTrue(dstMap.entrySet().equals(srcMap.entrySet()));
 
-    final String KEY1 = "1";
-    final String VAL1 = "2";
-    final String KEY2 = "3";
-    final String VAL2 = "4";
-    final String KEY3 = "5";
-    final String VAL3 = "6";
-
     // Check non-empty clone behavior
-    srcMap.put(KEY1, VAL1);
-    srcMap.put(KEY2, VAL2);
-    srcMap.put(KEY3, VAL3);
+    srcMap.put(KEY_1, VALUE_1);
+    srcMap.put(KEY_2, VALUE_2);
+    srcMap.put(KEY_3, VALUE_3);
     dstMap = (HashMap) srcMap.clone();
     assertNotNull(dstMap);
     assertEquals(dstMap.size(), srcMap.size());
@@ -92,20 +180,16 @@
    * Test method for 'java.util.HashMap.containsKey(Object)'
    */
   public void testContainsKey() {
-    final String KEY = "testContainsKey";
-    final Integer VAL = new Integer(5);
-    final String NON_EXISTANT_KEY = "does not exist";
-
     HashMap hashMap = new HashMap();
     checkEmptyHashMapAssumptions(hashMap);
 
-    assertFalse(hashMap.containsKey(KEY));
-    hashMap.put(KEY, VAL);
-    assertTrue(hashMap.containsKey(KEY));
-    assertFalse(hashMap.containsKey(NON_EXISTANT_KEY));
+    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, VAL);
+    hashMap.put(null, VALUE_TEST_CONTAINS_KEY);
     assertTrue(hashMap.containsKey(null));
   }
 
@@ -113,20 +197,16 @@
    * Test method for 'java.util.HashMap.containsValue(Object)'
    */
   public void testContainsValue() {
-    final String KEY = "testContainsValue";
-    final Integer VAL = new Integer(5);
-    final String NON_EXISTANT_KEY = "does not exist";
-
     HashMap hashMap = new HashMap();
     checkEmptyHashMapAssumptions(hashMap);
 
-    assertFalse(hashMap.containsValue(VAL));
-    hashMap.put(KEY, VAL);
-    assertTrue(hashMap.containsValue(VAL));
-    assertFalse(hashMap.containsValue(NON_EXISTANT_KEY));
+    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));
 
     assertFalse(hashMap.containsValue(null));
-    hashMap.put(KEY, null);
+    hashMap.put(KEY_TEST_CONTAINS_VALUE, null);
     assertTrue(hashMap.containsValue(null));
   }
 
@@ -140,33 +220,44 @@
     Set entrySet = hashMap.entrySet();
     assertNotNull(entrySet);
 
-    final String KEY = "testEntrySet";
-    final String VAL1 = KEY + " - value1";
-    final String VAL2 = KEY + " - value2";
-
     // Check that the entry set looks right
-    hashMap.put(KEY, VAL1);
+    hashMap.put(KEY_TEST_ENTRY_SET, VALUE_TEST_ENTRY_SET_1);
     entrySet = hashMap.entrySet();
-    assertEquals(entrySet.size(), 1);
+    assertEquals(entrySet.size(), SIZE_ONE);
     Iterator itSet = entrySet.iterator();
     Map.Entry entry = (Map.Entry) itSet.next();
-    assertEquals(entry.getKey(), KEY);
-    assertEquals(entry.getValue(), VAL1);
+    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, VAL2);
+    // 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(), 1);
+    assertEquals(entrySet.size(), SIZE_ONE);
     itSet = entrySet.iterator();
     entry = (Map.Entry) itSet.next();
-    assertEquals(entry.getKey(), KEY);
-    assertEquals(entry.getValue(), VAL2);
+    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);
+    hashMap.remove(KEY_TEST_ENTRY_SET);
     checkEmptyHashMapAssumptions(hashMap);
   }
 
+  /* 
+   * Used to test the entrySet remove method.
+   */
+  public void testEntrySetRemove() {
+    HashMap hashMap = new HashMap();
+    hashMap.put("A","B");
+    HashMap dummy = new HashMap();
+    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)'
    */
@@ -174,48 +265,43 @@
     HashMap hashMap = new HashMap();
     checkEmptyHashMapAssumptions(hashMap);
 
-    final String KEY = "key";
-    final String VAL = "val";
-
-    hashMap.put(KEY, VAL);
+    hashMap.put(KEY_KEY, VALUE_VAL);
 
     HashMap copyMap = (HashMap) hashMap.clone();
 
     assertTrue(hashMap.equals(copyMap));
-    hashMap.put(VAL, KEY);
+    hashMap.put(VALUE_VAL, KEY_KEY);
     assertFalse(hashMap.equals(copyMap));
   }
 
   /*
-   * Test method for 'java.lang.Object.finalize()'
+   * Test method for 'java.lang.Object.finalize()'.
    */
   public void testFinalize() {
+    // no tests for finalize
   }
 
   /*
-   * Test method for 'java.util.HashMap.get(Object)'
+   * Test method for 'java.util.HashMap.get(Object)'.
    */
   public void testGet() {
-    final String KEY = "testGet";
-    final String VAL = KEY + " - Value";
-
     HashMap hashMap = new HashMap();
     checkEmptyHashMapAssumptions(hashMap);
-
-    assertNull(hashMap.get(KEY));
-    hashMap.put(KEY, VAL);
-    assertNotNull(hashMap.get(KEY));
+    
+    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, VAL);
+    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()'
+   * Test method for 'java.util.AbstractMap.hashCode()'.
    */
   public void testHashCode() {
     HashMap hashMap = new HashMap();
@@ -223,14 +309,14 @@
 
     // Check that hashCode changes
     int hashCode1 = hashMap.hashCode();
-    hashMap.put("key", "val");
+    hashMap.put(KEY_KEY, VALUE_VAL);
     int hashCode2 = hashMap.hashCode();
 
     assertTrue(hashCode1 != hashCode2);
   }
 
   /*
-   * Test method for 'java.util.HashMap.HashMap()'
+   * Test method for 'java.util.HashMap.HashMap()'.
    */
   public void testHashMap() {
     HashMap hashMap = new HashMap();
@@ -241,17 +327,17 @@
    * Test method for 'java.util.HashMap.HashMap(int)'
    */
   public void testHashMapInt() {
-    final int CAPACITY = 16;
-    HashMap hashMap = new HashMap(CAPACITY);
+    HashMap hashMap = new HashMap(CAPACITY_16);
     checkEmptyHashMapAssumptions(hashMap);
 
     // TODO(mmendez): how do we verify capacity?
     boolean failed = true;
     try {
-      new HashMap(-1);
+      new HashMap(-SIZE_ONE);
     } catch (Throwable ex) {
-      if (ex instanceof IllegalArgumentException)
+      if (ex instanceof IllegalArgumentException) {
         failed = false;
+      }
     }
 
     if (failed) {
@@ -266,10 +352,8 @@
    * Test method for 'java.util.HashMap.HashMap(int, float)'
    */
   public void testHashMapIntFloat() {
-    final int CAPACITY = 16;
-    final float LOAD_FACTOR = 0.5F;
 
-    HashMap hashMap = new HashMap(CAPACITY, LOAD_FACTOR);
+    HashMap hashMap = new HashMap(CAPACITY_16, LOAD_FACTOR_ONE_HALF);
     checkEmptyHashMapAssumptions(hashMap);
 
     // TODO(mmendez): how do we verify capacity and load factor?
@@ -277,10 +361,11 @@
     // Test new HashMap(-1, 0.0F)
     boolean failed = true;
     try {
-      new HashMap(-1, 0.0F);
+      new HashMap(CAPACITY_NEG_ONE_HALF, LOAD_FACTOR_ZERO);
     } catch (Throwable ex) {
-      if (ex instanceof IllegalArgumentException)
+      if (ex instanceof IllegalArgumentException) {
         failed = false;
+      }
     }
 
     if (failed) {
@@ -290,10 +375,11 @@
     // Test new HashMap(0, -1.0F)
     failed = true;
     try {
-      new HashMap(0, -1.0F);
+      new HashMap(CAPACITY_ZERO, LOAD_FACTOR_NEG_ONE);
     } catch (Throwable ex) {
-      if (ex instanceof IllegalArgumentException)
+      if (ex instanceof IllegalArgumentException) {
         failed = false;
+      }
     }
 
     if (failed) {
@@ -301,7 +387,7 @@
     }
 
     // Test new HashMap(0,0F);
-    hashMap = new HashMap(0, 0.1F);
+    hashMap = new HashMap(CAPACITY_ZERO, LOAD_FACTOR_ONE_TENTH);
     assertNotNull(hashMap);
   }
 
@@ -313,30 +399,23 @@
     assertNotNull(srcMap);
     checkEmptyHashMapAssumptions(srcMap);
 
-    final Object KEY1 = new Integer(1);
-    final Object VAL1 = new Integer(1);
-    final Object KEY2 = new Integer(2);
-    final Object VAL2 = new Integer(2);
-    final Object KEY3 = new Integer(3);
-    final Object VAL3 = new Integer(3);
+    srcMap.put(INTEGER_1, INTEGER_11);
+    srcMap.put(INTEGER_2, INTEGER_22);
+    srcMap.put(INTEGER_3, INTEGER_33);
 
-    srcMap.put(KEY1, VAL1);
-    srcMap.put(KEY2, VAL2);
-    srcMap.put(KEY3, VAL3);
-
-    HashMap hashMap = new HashMap(srcMap);
+    HashMap hashMap = cloneHashMap(srcMap);
     assertFalse(hashMap.isEmpty());
-    assertTrue(hashMap.size() == 3);
+    assertTrue(hashMap.size() == SIZE_THREE);
 
     Collection valColl = hashMap.values();
-    assertTrue(valColl.contains(VAL1));
-    assertTrue(valColl.contains(VAL2));
-    assertTrue(valColl.contains(VAL3));
+    assertTrue(valColl.contains(INTEGER_11));
+    assertTrue(valColl.contains(INTEGER_22));
+    assertTrue(valColl.contains(INTEGER_33));
 
     Collection keyColl = hashMap.keySet();
-    assertTrue(keyColl.contains(KEY1));
-    assertTrue(keyColl.contains(KEY2));
-    assertTrue(keyColl.contains(KEY3));
+    assertTrue(keyColl.contains(INTEGER_1));
+    assertTrue(keyColl.contains(INTEGER_2));
+    assertTrue(keyColl.contains(INTEGER_3));
   }
 
   /*
@@ -352,16 +431,38 @@
     dstMap.putAll(srcMap);
     assertTrue(dstMap.isEmpty());
 
-    final String KEY = "key";
-    final String VAL = "val";
-    dstMap.put(KEY, VAL);
+    dstMap.put(KEY_KEY, VALUE_VAL);
     assertFalse(dstMap.isEmpty());
 
-    dstMap.remove(KEY);
+    dstMap.remove(KEY_KEY);
     assertTrue(dstMap.isEmpty());
     assertEquals(dstMap.size(), 0);
   }
 
+  public void testKeysConflict() {
+    HashMap hashMap = new HashMap(); 
+    
+    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.HashMap.keySet()'
    */
@@ -374,14 +475,12 @@
     assertTrue(keySet.isEmpty());
     assertTrue(keySet.size() == 0);
 
-    final String KEY = "testKeySet";
-    final String VAL = KEY + " - value";
-    hashMap.put(KEY, VAL);
+    hashMap.put(KEY_TEST_KEY_SET, VALUE_TEST_KEY_SET);
 
-    assertTrue(keySet.size() == 1);
-    assertTrue(keySet.contains(KEY));
-    assertFalse(keySet.contains(VAL));
-    assertFalse(keySet.contains(KEY.toUpperCase()));
+    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()));
   }
 
   /*
@@ -391,35 +490,22 @@
     HashMap hashMap = new HashMap();
     checkEmptyHashMapAssumptions(hashMap);
 
-    final String KEY = "testPut";
-    final String VAL1 = KEY + " - value 1";
-    final String VAL2 = KEY + " - value 2";
-
-    assertNull(hashMap.put(KEY, VAL1));
-    assertEquals(hashMap.put(KEY, VAL2), VAL1);
-    assertNull(hashMap.put(null, VAL1));
-    assertEquals(hashMap.put(null, VAL2), VAL1);
+    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.HashMap.putAll(Map)'
+  /**
+   * Test method for 'java.util.HashMap.putAll(Map)'.
    */
   public void testPutAll() {
-    final String KEY1 = "key1";
-    final String VAL1 = "val1";
-    final String KEY2 = "key2";
-    final String VAL2 = "val2";
-    final String KEY3 = "key3";
-    final String VAL3 = "val3";
-    final String KEY4 = "key4";
-    final String VAL4 = "val4";
-
     HashMap srcMap = new HashMap();
     checkEmptyHashMapAssumptions(srcMap);
 
-    srcMap.put(KEY1, VAL1);
-    srcMap.put(KEY2, VAL2);
-    srcMap.put(KEY3, VAL3);
+    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
     HashMap dstMap = new HashMap();
@@ -427,134 +513,119 @@
 
     dstMap.putAll(srcMap);
     assertEquals(srcMap.size(), dstMap.size());
-    assertTrue(dstMap.containsKey(KEY1));
-    assertTrue(dstMap.containsValue(VAL1));
-    assertFalse(dstMap.containsKey(KEY1.toUpperCase()));
-    assertFalse(dstMap.containsValue(VAL1.toUpperCase()));
+    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(KEY2));
-    assertTrue(dstMap.containsValue(VAL2));
-    assertFalse(dstMap.containsKey(KEY2.toUpperCase()));
-    assertFalse(dstMap.containsValue(VAL2.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(KEY3));
-    assertTrue(dstMap.containsValue(VAL3));
-    assertFalse(dstMap.containsKey(KEY3.toUpperCase()));
-    assertFalse(dstMap.containsValue(VAL3.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 dest map
+    // Check that an empty map does not blow away the contents of the destination map
     HashMap emptyMap = new HashMap();
     checkEmptyHashMapAssumptions(emptyMap);
     dstMap.putAll(emptyMap);
     assertTrue(dstMap.size() == srcMap.size());
 
-    // Check that put all overwrite any existing mapping in the dst map
-    srcMap.put(KEY1, VAL2);
-    srcMap.put(KEY2, VAL3);
-    srcMap.put(KEY3, VAL1);
+    // 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(KEY1), VAL2);
-    assertEquals(dstMap.get(KEY2), VAL3);
-    assertEquals(dstMap.get(KEY3), VAL1);
+    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
+    // Check that a putAll does adds data but does not remove it
 
-    srcMap.put(KEY4, VAL4);
+    srcMap.put(KEY_4, VALUE_4);
     dstMap.putAll(srcMap);
     assertEquals(dstMap.size(), srcMap.size());
-    assertTrue(dstMap.containsKey(KEY4));
-    assertTrue(dstMap.containsValue(VAL4));
-    assertEquals(dstMap.get(KEY1), VAL2);
-    assertEquals(dstMap.get(KEY2), VAL3);
-    assertEquals(dstMap.get(KEY3), VAL1);
-    assertEquals(dstMap.get(KEY4), VAL4);
+    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.HashMap.remove(Object)'
+  /**
+   * Test method for 'java.util.HashMap.remove(Object)'.
    */
   public void testRemove() {
-    final String KEY = "testRemove";
-    final String VAL = KEY + " - value";
-
     HashMap hashMap = new HashMap();
     checkEmptyHashMapAssumptions(hashMap);
 
     assertNull(hashMap.remove(null));
-    hashMap.put(null, VAL);
+    hashMap.put(null, VALUE_TEST_REMOVE);
     assertNotNull(hashMap.remove(null));
 
-    hashMap.put(KEY, VAL);
-    assertEquals(hashMap.remove(KEY), VAL);
-    assertNull(hashMap.remove(KEY));
+    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.HashMap.size()'
+  /**
+   * Test method for 'java.util.HashMap.size()'.
    */
   public void testSize() {
-    final String KEY1 = "key1";
-    final String VAL1 = KEY1 + " - value";
-    final String KEY2 = "key2";
-    final String VAL2 = KEY2 + " - value";
-    final String KEY3 = "key3";
-    final String VAL3 = KEY3 + " - value";
-
     HashMap hashMap = new HashMap();
     checkEmptyHashMapAssumptions(hashMap);
 
     // Test size behavior on put
-    assertEquals(hashMap.size(), 0);
-    hashMap.put(KEY1, VAL1);
-    assertEquals(hashMap.size(), 1);
-    hashMap.put(KEY2, VAL2);
-    assertEquals(hashMap.size(), 2);
-    hashMap.put(KEY3, VAL3);
-    assertEquals(hashMap.size(), 3);
+    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(KEY1);
-    assertEquals(hashMap.size(), 2);
-    hashMap.remove(KEY2);
-    assertEquals(hashMap.size(), 1);
-    hashMap.remove(KEY3);
-    assertEquals(hashMap.size(), 0);
+    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(KEY1, VAL1);
-    hashMap.put(KEY2, VAL2);
-    hashMap.put(KEY3, VAL3);
-
-    HashMap srcMap = new HashMap(hashMap);
+    hashMap.put(KEY_1, VALUE_1);
+    hashMap.put(KEY_2, VALUE_2);
+    hashMap.put(KEY_3, VALUE_3);
+    HashMap srcMap = cloneHashMap(hashMap);
     hashMap.putAll(srcMap);
-    assertEquals(hashMap.size(), 3);
+    assertEquals(hashMap.size(), SIZE_THREE);
 
     // Test size behavior on clear
     hashMap.clear();
-    assertEquals(hashMap.size(), 0);
+    assertEquals(hashMap.size(), SIZE_ZERO);
   }
 
-  /*
-   * Test method for 'java.util.AbstractMap.toString()'
+  /**
+   * Test method for 'java.util.AbstractMap.toString()'.
    */
   public void testToString() {
     HashMap hashMap = new HashMap();
     checkEmptyHashMapAssumptions(hashMap);
-
-    final String KEY = "key";
-    final String VAL = "val";
-    final String STR = "{" + KEY + "=" + VAL + "}";
-    hashMap.put(KEY, VAL);
-
-    assertTrue(STR.equals(hashMap.toString()));
+    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()'
+   * Test method for 'java.util.AbstractMap.values()'.
    */
   public void testValues() {
     HashMap hashMap = new HashMap();
@@ -562,17 +633,48 @@
 
     assertNotNull(hashMap.values());
 
-    final String KEY = "key";
-    final String VAL = "val";
-
-    hashMap.put(KEY, VAL);
+    hashMap.put(KEY_KEY, VALUE_VAL);
 
     Collection valColl = hashMap.values();
     assertNotNull(valColl);
-    assertEquals(valColl.size(), 1);
+    assertEquals(valColl.size(), SIZE_ONE);
 
     Iterator itVal = valColl.iterator();
     String val = (String) itVal.next();
-    assertEquals(val, VAL);
+    assertEquals(val, VALUE_VAL);
+  }
+  
+  protected Map makeEmptyMap() {
+    return new HashMap();
+  }
+  
+  /**
+   * This method exists because java 1.5 no longer has HashMap(HashMap), replacing
+   * it with HashMap(Map<? extends K, ? extends V> m).  Nevertheless, we want 
+   * to use it in web mode to test that web mode function.
+   * 
+   * @param hashMap the HashMap to be copied 
+   * @return the copy
+   */
+  private HashMap cloneHashMap(HashMap hashMap) {
+    if (GWT.isScript()) {
+      return new HashMap(hashMap);
+    } else {
+      HashMap m = new HashMap();
+      m.putAll(hashMap);
+      return m;
+    }
+  }
+
+  private Iterator iterateThrough(final HashMap 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/com/google/gwt/emultest/java/util/HashSetTest.java b/user/test/com/google/gwt/emultest/java/util/HashSetTest.java
index 91b757a..4e0e2d6 100644
--- a/user/test/com/google/gwt/emultest/java/util/HashSetTest.java
+++ b/user/test/com/google/gwt/emultest/java/util/HashSetTest.java
@@ -16,8 +16,13 @@
   public void testAddingKeys(){
     Map map = new HashMap();
     Set keys = new HashSet(map.keySet());
-    keys.add(new Object()); // Throws exception in IE6 (web-mode) but not GWT 
-     
+    keys.add(new Object()); // Throws exception in IE6 (web-mode) but not GWT
+  }
+
+  public void testAddWatch() {
+    HashSet s = new HashSet();
+    s.add("watch");
+    assertTrue(s.contains("watch"));
   }
 
   protected Set makeEmptySet() {