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;