Add tests for TreeMap and TreeSet, change TreeSet.add implementation to be
more efficient, fix integer overflow in AbstractSet.hashCode.

Patch by: jat
Review by: scottb (desk review)



git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@2938 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/user/super/com/google/gwt/emul/java/util/AbstractSet.java b/user/super/com/google/gwt/emul/java/util/AbstractSet.java
index fde5328..dc30516 100644
--- a/user/super/com/google/gwt/emul/java/util/AbstractSet.java
+++ b/user/super/com/google/gwt/emul/java/util/AbstractSet.java
@@ -58,6 +58,8 @@
       E next = iter.next();
       if (next != null) {
         hashCode += next.hashCode();
+        // handle int overflow by coercing to int
+        hashCode = ~~hashCode;
       }
     }
     return hashCode;
diff --git a/user/super/com/google/gwt/emul/java/util/TreeSet.java b/user/super/com/google/gwt/emul/java/util/TreeSet.java
index afeaa98..b4bce7f 100644
--- a/user/super/com/google/gwt/emul/java/util/TreeSet.java
+++ b/user/super/com/google/gwt/emul/java/util/TreeSet.java
@@ -63,13 +63,8 @@
 
   @Override
   public boolean add(E o) {
-    if (map.containsKey(o)) {
-      // must not change map for equal keys
-      return false;
-    }
     // Use "this" as a convenient non-null value to store in the map
-    map.put(o, o);
-    return true;
+    return map.put(o, this) == null;
   }
 
   @Override
diff --git a/user/test/com/google/gwt/emultest/java/util/TreeMapTest.java b/user/test/com/google/gwt/emultest/java/util/TreeMapTest.java
index 50ba8b5..9e3e0f0 100644
--- a/user/test/com/google/gwt/emultest/java/util/TreeMapTest.java
+++ b/user/test/com/google/gwt/emultest/java/util/TreeMapTest.java
@@ -20,7 +20,9 @@
 
 import org.apache.commons.collections.TestMap;
 
+import java.util.ArrayList;
 import java.util.Collection;
+import java.util.Collections;
 import java.util.Comparator;
 import java.util.HashMap;
 import java.util.Iterator;
@@ -132,6 +134,12 @@
     assertEquals(expected.values().toArray(), actual.values().toArray());
   }
 
+  // Use JSNI to call a special method on our implementation of TreeMap.
+  @SuppressWarnings("unchecked") // raw Map
+  private static native void callAssertCorrectness(Map map) /*-{
+    map.@java.util.TreeMap::assertCorrectness()();
+  }-*/;
+
   /**
    * Create the expected return of toString for a Map containing only the passed
    * key and value.
@@ -155,6 +163,7 @@
   private boolean isPutSupported = true;
   private boolean isRemoveSupported = true;
 
+  @Override
   public String getModuleName() {
     return "com.google.gwt.emultest.EmulSuite";
   }
@@ -893,6 +902,29 @@
     assertEquals(1, keySet.size());
   }
 
+  public void testKeySetIteratorRemove() {
+    Map<K, V> map = makeFullMap();
+    resetFull();
+    ArrayList<K> keys = new ArrayList<K>();
+    for (Object key : getSampleKeys()) {
+      keys.add((K) key);
+    }
+    Comparator<? super K> cmp = ((TreeMap<K, V>) map).comparator();
+    if (cmp != null) {
+      Collections.sort(keys, cmp);
+    } else {
+      Collections.sort(keys);
+    }
+    Iterator<K> it = map.keySet().iterator();
+    for (K key : keys) {
+      assertTrue(it.hasNext());
+      K rem = it.next();
+      it.remove();
+      assertEquals(key, rem);
+    }
+    assertEquals(0, map.size());
+  }
+
   /**
    * Test method for 'java.util.SortedMap.lastKey()'.
    * 
@@ -1810,6 +1842,11 @@
 
   protected abstract Object getConflictingValue();
 
+  @Override
+  protected void gwtSetUp() throws Exception {
+    setComparator(null);
+  }
+
   protected boolean isNaturalOrder() {
     return comparator == null;
   }
@@ -1825,8 +1862,12 @@
   }
 
   @Override
-  protected void gwtSetUp() throws Exception {
-    setComparator(null);
+  protected void verifyMap() {
+    if (GWT.isScript()) {
+      // Verify red-black correctness in our implementation
+      callAssertCorrectness(map);
+    }
+    super.verifyMap();
   }
 
   Map<K, V> createMap() {
diff --git a/user/test/com/google/gwt/emultest/java/util/TreeSetIntegerTest.java b/user/test/com/google/gwt/emultest/java/util/TreeSetIntegerTest.java
index 96e04a1..e74c783 100644
--- a/user/test/com/google/gwt/emultest/java/util/TreeSetIntegerTest.java
+++ b/user/test/com/google/gwt/emultest/java/util/TreeSetIntegerTest.java
@@ -15,11 +15,75 @@
  */
 package com.google.gwt.emultest.java.util;
 
+import java.util.Comparator;
+import java.util.Iterator;
+import java.util.TreeSet;
+
 /**
- * Tests <code>TreeMap</code> with a <code>Comparator</code>.
+ * Tests <code>TreeSet</code> with a <code>Comparator</code>.
  */
 public class TreeSetIntegerTest extends TreeSetTest<Integer> {
 
+  /**
+   * Used to test updating a set to make sure it doesn't replace
+   * an equal value.
+   */
+  private static class Record {
+    public int key;
+    public int extra;
+    
+    public Record(int key, int extra) {
+      this.key = key;
+      this.extra = extra;
+    }
+  }
+  
+  private static class RecordCompare implements Comparator<Record> {
+    // Handle nulls as less than any other key
+    public int compare(Record r1, Record r2) {
+      if (r1 == null) {
+        return r2 == null ? 0 : -1;
+      }
+      if (r2 == null) {
+        return 1;
+      }
+      return r1.key - r2.key;
+    }
+  }
+
+  /**
+   * Verify nulls are handled properly.
+   */
+  public void testAdd_null() {
+    TreeSet<Record> set = new TreeSet<Record>(new RecordCompare());
+    set.add(new Record(10, 1));
+    set.add(new Record(2, 2));
+    set.add(null);
+    set.add(new Record(7, 7));
+    Iterator<Record> it = set.iterator();
+    assertTrue(it.hasNext());
+    assertNull(it.next());
+    assertTrue(it.hasNext());
+    assertEquals(2, it.next().key);
+    assertTrue(it.hasNext());
+    assertEquals(7, it.next().key);
+    assertTrue(it.hasNext());
+    assertEquals(10, it.next().key);
+    assertFalse(it.hasNext());
+  }
+  
+  /**
+   * Verify that Set.add doesn't replace an existing entry that compares equal.
+   */
+  public void testAdd_overwrite() {
+    TreeSet<Record> set = new TreeSet<Record>(new RecordCompare());
+    assertTrue(set.add(new Record(1, 1)));
+    assertTrue(set.add(new Record(2, 2)));
+    assertFalse(set.add(new Record(1, -1)));
+    Record first = set.first();
+    assertEquals(1, first.extra);
+  }
+
   @Override
   Integer getGreaterThanMaximumKey() {
     return Integer.MAX_VALUE;