JSONObject's keySet should iterate in an oder that is consistent between hosted and web mode.  This was being caused by the evil iterate-over-HashMap pattern.

Review by: jat (desk)


git-svn-id: https://google-web-toolkit.googlecode.com/svn/releases/1.6@4607 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/user/src/com/google/gwt/json/client/JSONObject.java b/user/src/com/google/gwt/json/client/JSONObject.java
index 6fd985d..17233ff 100644
--- a/user/src/com/google/gwt/json/client/JSONObject.java
+++ b/user/src/com/google/gwt/json/client/JSONObject.java
@@ -15,11 +15,14 @@
  */
 package com.google.gwt.json.client;
 
+import com.google.gwt.core.client.GWT;
 import com.google.gwt.core.client.JavaScriptObject;
 
+import java.util.AbstractSet;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collection;
-import java.util.HashSet;
+import java.util.Iterator;
 import java.util.List;
 import java.util.Set;
 
@@ -39,7 +42,7 @@
   private final JavaScriptObject jsObject;
 
   public JSONObject() {
-    jsObject = JavaScriptObject.createObject();
+    this(JavaScriptObject.createObject());
   }
 
   /**
@@ -110,9 +113,23 @@
    * Returns the set of properties defined on this JSONObject.
    */
   public Set<String> keySet() {
-    HashSet<String> keySet = new HashSet<String>();
-    addAllKeys(keySet);
-    return keySet;
+    final String[] keys = computeKeys();
+    return new AbstractSet<String>() {
+      @Override
+      public boolean contains(Object o) {
+        return (o instanceof String) && containsKey((String) o);
+      }
+
+      @Override
+      public Iterator<String> iterator() {
+        return Arrays.asList(keys).iterator();
+      }
+
+      @Override
+      public int size() {
+        return keys.length;
+      }
+    };
   }
 
   /**
@@ -138,7 +155,8 @@
    * Determines the number of properties on this object.
    */
   public int size() {
-    return keySet().size();
+    // Must always recheck due to foreign changes. :(
+    return computeSize();
   }
 
   /**
@@ -152,8 +170,7 @@
     StringBuffer sb = new StringBuffer();
     sb.append("{");
     boolean first = true;
-    List<String> keys = new ArrayList<String>();
-    addAllKeys(keys);
+    String[] keys = computeKeys();
     for (String key : keys) {
       if (first) {
         first = false;
@@ -180,6 +197,34 @@
     }
   }-*/;
 
+  private String[] computeKeys() {
+    if (GWT.isScript()) {
+      return computeKeys0(new String[0]);
+    } else {
+      List<String> result = new ArrayList<String>();
+      addAllKeys(result);
+      return result.toArray(new String[result.size()]);
+    }
+  }
+
+  private native String[] computeKeys0(String[] result) /*-{
+    var jsObject = this.@com.google.gwt.json.client.JSONObject::jsObject;
+    var i = 0;
+    for (var key in jsObject) {
+      result[i++] = key;
+    }
+    return result;
+  }-*/;
+
+  private native int computeSize() /*-{
+    var jsObject = this.@com.google.gwt.json.client.JSONObject::jsObject;
+    var size = 0;
+    for (var key in jsObject) {
+      ++size;
+    }
+    return size;
+  }-*/;
+
   private native JSONValue get0(String key) /*-{
     var v = this.@com.google.gwt.json.client.JSONObject::jsObject[key];
     var func = @com.google.gwt.json.client.JSONParser::typeMap[typeof v];