I18N constant maps are now guaranteed to iterate in declaration order.  This allows a better degree of user control, and fixes inconsistencies between hosted and web mode.

Review by: jat (desk)


git-svn-id: https://google-web-toolkit.googlecode.com/svn/releases/1.6@4608 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/user/src/com/google/gwt/i18n/client/Constants.java b/user/src/com/google/gwt/i18n/client/Constants.java
index c5be987..8417853 100644
--- a/user/src/com/google/gwt/i18n/client/Constants.java
+++ b/user/src/com/google/gwt/i18n/client/Constants.java
@@ -64,7 +64,7 @@
  * 
  * {@example com.google.gwt.examples.i18n.NumberFormatConstantsAnnot}
  * </p>
- *
+ * 
  * <p>
  * It is also possible to change the property name bound to a constant accessor
  * using the {@code @Key} annotation. For example,
@@ -152,7 +152,8 @@
  * the constant accessor <code>someMap()</code> would return a
  * <code>Map</code> that maps <code>"a"</code> onto <code>"X"</code>,
  * <code>"b"</code> onto <code>"Y"</code>, and <code>"c"</code> onto
- * <code>"Z"</code>.
+ * <code>"Z"</code>. Iterating through this <code>Map</code> will return
+ * the keys or entries in declaration order.
  * </p>
  * 
  * <p>The benefit of using annotations, aside from not having to switch to
diff --git a/user/src/com/google/gwt/i18n/client/impl/ConstantMap.java b/user/src/com/google/gwt/i18n/client/impl/ConstantMap.java
index 0ecec17..f683e97 100644
--- a/user/src/com/google/gwt/i18n/client/impl/ConstantMap.java
+++ b/user/src/com/google/gwt/i18n/client/impl/ConstantMap.java
@@ -20,10 +20,9 @@
 import java.util.AbstractMap;
 import java.util.AbstractSet;
 import java.util.Arrays;
-import java.util.Collections;
-import java.util.HashMap;
 import java.util.Iterator;
 import java.util.Map;
+import java.util.NoSuchElementException;
 import java.util.Set;
 
 /**
@@ -33,10 +32,62 @@
  */
 public class ConstantMap extends AbstractMap<String, String> {
 
-  /**
-   * A cache of a synthesized entry set.
-   */
-  private Set<Map.Entry<String, String>> entries;
+  private static final class EntryImpl implements Entry<String, String> {
+    private final String key;
+    private final String value;
+
+    private EntryImpl(String key, String value) {
+      assert (key != null);
+      assert (value != null);
+      this.key = key;
+      this.value = value;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+      if (o instanceof Map.Entry) {
+        Map.Entry<?, ?> other = (Map.Entry<?, ?>) o;
+        // Key and value known to be non-null.
+        if (key.equals(other.getKey()) && value.equals(other.getValue())) {
+          return true;
+        }
+      }
+      return false;
+    }
+
+    public String getKey() {
+      return key;
+    }
+
+    public String getValue() {
+      return value;
+    }
+
+    /**
+     * Calculate the hash code using Sun's specified algorithm.
+     */
+    @Override
+    public int hashCode() {
+      int keyHash = 0;
+      int valueHash = 0;
+      if (getKey() != null) {
+        keyHash = getKey().hashCode();
+      }
+      if (getValue() != null) {
+        valueHash = getValue().hashCode();
+      }
+      return keyHash ^ valueHash;
+    }
+
+    public String setValue(String value) {
+      throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public String toString() {
+      return key + "=" + value;
+    }
+  }
 
   /**
    * The original set of keys.
@@ -56,6 +107,8 @@
     init();
 
     for (int i = 0; i < keys.length; ++i) {
+      assert keys[i] != null;
+      assert values[i] != null;
       putImpl(keys[i], values[i]);
     }
   }
@@ -67,14 +120,48 @@
 
   @Override
   public Set<Map.Entry<String, String>> entrySet() {
-    if (entries == null) {
-      Map<String, String> copy = new HashMap<String, String>();
-      for (String key : keys) {
-        copy.put(key, get(key));
+    return new AbstractSet<Entry<String, String>>() {
+      @Override
+      public boolean contains(Object o) {
+        if (!(o instanceof Entry)) {
+          return false;
+        }
+        Entry<?, ?> other = (Entry<?, ?>) o;
+        String value = get(other.getKey());
+        if (value != null && value.equals(other.getValue())) {
+          return true;
+        }
+        return false;
       }
-      entries = Collections.unmodifiableMap(copy).entrySet();
-    }
-    return entries;
+
+      @Override
+      public Iterator<Map.Entry<String, String>> iterator() {
+        return new Iterator<Entry<String, String>>() {
+          private int next = 0;
+
+          public boolean hasNext() {
+            return next < ConstantMap.this.size();
+          }
+
+          public Entry<String, String> next() {
+            if (!hasNext()) {
+              throw new NoSuchElementException();
+            }
+            String key = keys[next++];
+            return new EntryImpl(key, get(key));
+          }
+
+          public void remove() {
+            throw new UnsupportedOperationException();
+          }
+        };
+      }
+
+      @Override
+      public int size() {
+        return ConstantMap.this.size();
+      }
+    };
   }
 
   @Override
diff --git a/user/src/com/google/gwt/i18n/rebind/ConstantsMapMethodCreator.java b/user/src/com/google/gwt/i18n/rebind/ConstantsMapMethodCreator.java
index a192966..ee6fa28 100644
--- a/user/src/com/google/gwt/i18n/rebind/ConstantsMapMethodCreator.java
+++ b/user/src/com/google/gwt/i18n/rebind/ConstantsMapMethodCreator.java
@@ -22,8 +22,8 @@
 import com.google.gwt.i18n.rebind.AbstractResource.ResourceList;
 import com.google.gwt.user.rebind.AbstractGeneratorClassCreator;
 
-import java.util.SortedMap;
-import java.util.TreeMap;
+import java.util.LinkedHashMap;
+import java.util.Map;
 
 /**
  * Creator for methods of the form Map getX() .
@@ -81,7 +81,8 @@
 
     String[] keys = ConstantsStringArrayMethodCreator.split(keyString);
     ResourceList resources = getResources();
-    SortedMap<String, String> map = new TreeMap<String, String>();
+    // Use a LinkedHashMap to preserve declaration order (but remove dups).
+    Map<String, String> map = new LinkedHashMap<String, String>();
     for (String key : keys) {
       if (key.length() == 0) {
         continue;