Updated patch: (a) incorporates ConstantMapTest, (b) Reverts the mappings
change I introduced.

Amit

On Thu, Oct 30, 2008 at 12:38 PM, Scott Blum <[EMAIL PROTECTED]> wrote:

> Sweet.
>
>
> On Thu, Oct 30, 2008 at 12:32 PM, Amit Manjhi <[EMAIL PROTECTED]>wrote:
>
>> ConcurrentModificationException. I will handle the exception :).
>>
>> On second thoughts, I did see the utility of adding equals check in the
>> other direction. The most recent patch contains the checks. I also
>> discovered the missing equals and hashCode methods, which I also added in
>> the patch. I will add the updates to ConstantMapTest and check the
>> difference in mappings, and send you an updated patch shortly.
>>
>> Amit
>>
>>
>> On Thu, Oct 30, 2008 at 12:21 PM, Scott Blum <[EMAIL PROTECTED]> wrote:
>>
>>> On Wed, Oct 29, 2008 at 8:45 PM, Amit Manjhi <[EMAIL PROTECTED]>wrote:
>>>
>>>> Thanks for the feedback, Scott.
>>>>
>>>> On Wed, Oct 29, 2008 at 8:22 PM, Scott Blum <[EMAIL PROTECTED]> wrote:
>>>>
>>>>> I18NSuite: why is the test commented out?
>>>>>
>>>>
>>>> Since ConstantMap now uses Jsni, it needs a Gwt environment. There did
>>>> not seem an obvious way for the ConstantMapTest to extend both MapTestBase
>>>> and GWTTestCase.
>>>>
>>>
>>> Ahh... hmm.  Well, ConstantMapTest isn't doing much right now, because it
>>> needs to implement makeFullMap() to really do anything.  I played around
>>> with the idea of subclassing ConstantMap and overriding the JSNI methods to
>>> use an internal hash map.  Doing so uncovered at least one bug... our map
>>> entries don't implement equals, hashCode, toString.  Here's what I did, you
>>> can build on this:
>>>
>>> public class ConstantMapTest extends MapTestBase {
>>>
>>>   private static final class ConstantMapNoJsni extends ConstantMap {
>>>     private Map<String, String> noJsniImpl;
>>>
>>>     private ConstantMapNoJsni(String[] keys, String[] values) {
>>>       super(keys, values);
>>>     }
>>>
>>>     @Override
>>>     public String get(String key) {
>>>       return noJsniImpl.get(key);
>>>     }
>>>
>>>     @Override
>>>     protected void putImpl(String key, String value) {
>>>       noJsniImpl.put(key, value);
>>>     }
>>>
>>>     @Override
>>>     protected void init() {
>>>       noJsniImpl = new HashMap<String, String>();
>>>     }
>>>   }
>>>
>>>   @Override
>>>   protected Map<String, String> makeEmptyMap() {
>>>     return Collections.unmodifiableMap(new ConstantMapNoJsni(new String[]
>>> {},
>>>         new String[] {}));
>>>   }
>>>
>>>   @Override
>>>   protected Map<String, String> makeFullMap() {
>>>     String[] keys = Arrays.asList(getSampleKeys()).toArray(new
>>> String[0]);
>>>     String[] values = Arrays.asList(getSampleValues()).toArray(new
>>> String[0]);
>>>     return Collections.unmodifiableMap(new ConstantMapNoJsni(keys,
>>> values));
>>>   }
>>>
>>>   @Override
>>>   protected boolean isRemoveModifiable() {
>>>     return false;
>>>   }
>>>
>>>   @Override
>>>   protected boolean useNullKey() {
>>>     return false;
>>>   }
>>>
>>>   @Override
>>>   protected boolean useNullValue() {
>>>     return false;
>>>   }
>>> }
>>>
>>>
>>>
>>>> - Line 194, 295, etc should be assertEquals instead of assertTrue unless
>>>>> there's some compelling reason not to.  Also, it would be good to test
>>>>> equality in both directions in all of these cases
>>>>>
>>>> Will change it to assertEquals. But I don't see the need to check in
>>>> both directions since we can assume Map.equals to be commutative.
>>>>
>>>
>>> Actually, we should NOT assume equals is commutative.. we need to test
>>> that it's actually commutative!  For example, the fact that our map entries
>>> don't implement equals at all is an example of this... equality works in one
>>> direction but not the other.
>>>
>>>
>>>> - Reason for the changes starting on lines 439 and 511?
>>>>>
>>>>
>>>> 511: Previously the order of the map was guaranteed. Now it can be any
>>>> order.
>>>>
>>>
>>> Are we looking at the same thing?  This is what I'm seeing:
>>>
>>> Before:
>>> assertEquals("{innerInner=4.321, outer=outer}", extendsAnotherInner);
>>>
>>> After:
>>>
>>> assertTrue("{innerInner=4.321, outer=outer}".equals(extendsAnotherInner)
>>>         || "{innerInner=outer,
>>> outer=4.321}".equals(extendsAnotherInner));
>>>
>>> This actually looks like a change in mappings, not merely a change in
>>> order.
>>>
>>>
>>
>

--~--~---------~--~----~------------~-------~--~----~
http://groups.google.com/group/Google-Web-Toolkit-Contributors
-~----------~----~----~----~------~----~------~--~---

Index: user/test/com/google/gwt/i18n/ConstantMapTest.java
===================================================================
--- user/test/com/google/gwt/i18n/ConstantMapTest.java  (revision 3885)
+++ user/test/com/google/gwt/i18n/ConstantMapTest.java  (working copy)
@@ -17,19 +17,71 @@
 
 import com.google.gwt.i18n.client.impl.ConstantMap;
 
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
 import java.util.Map;
+import java.util.Set;
 
 /**
- * TODO: document me.
+ * Test ConstantMap using Apache's tests.
  */
 public class ConstantMapTest extends MapTestBase {
 
-  protected Map makeEmptyMap() {
-    return new ConstantMap();
+  private static final class ConstantMapNoJsni extends ConstantMap {
+    private Map<String, String> noJsniImpl;
+
+    private ConstantMapNoJsni(String[] keys, String[] values) {
+      super(keys, values);
+    }
+
+    @Override
+    public String get(String key) {
+      return noJsniImpl.get(key);
+    }
+
+    @Override
+    public Set<String> keySet() {
+      return noJsniImpl.keySet(); 
+    }
+    
+    @Override
+    protected void init() {
+      noJsniImpl = new HashMap<String, String>();
+    }
+    
+    @Override
+    protected void putImpl(String key, String value) {
+      noJsniImpl.put(key, value);
+    }
   }
 
+  @Override
   protected boolean isRemoveModifiable() {
     return false;
   }
 
+  @Override
+  protected Map<String, String> makeEmptyMap() {
+    return Collections.unmodifiableMap(new ConstantMapNoJsni(new String[] {},
+        new String[] {}));
+  }
+
+  @Override
+  protected Map<String, String> makeFullMap() {
+    String[] keys = Arrays.asList(getSampleKeys()).toArray(new String[0]);
+    String[] values = Arrays.asList(getSampleValues()).toArray(new String[0]);
+    return Collections.unmodifiableMap(new ConstantMapNoJsni(keys, values));
+  }
+
+  @Override
+  protected boolean useNullKey() {
+    return false;
+  }
+
+  @Override
+  protected boolean useNullValue() {
+    return false;
+  }
 }
+
Index: user/test/com/google/gwt/i18n/I18NSuite.java
===================================================================
--- user/test/com/google/gwt/i18n/I18NSuite.java        (revision 3885)
+++ user/test/com/google/gwt/i18n/I18NSuite.java        (working copy)
@@ -54,7 +54,7 @@
     suite.addTestSuite(DateTimeParse_zh_CN_Test.class);
     suite.addTestSuite(I18NTest.class);
     suite.addTestSuite(I18N2Test.class);
-    suite.addTestSuite(LocaleInfo_ar_Test.class);    
+    suite.addTestSuite(LocaleInfo_ar_Test.class);
     suite.addTestSuite(LocaleInfoTest.class);
     suite.addTestSuite(MessageFormatParserTest.class);
     suite.addTestSuite(NumberFormat_ar_Test.class);
Index: user/test/com/google/gwt/i18n/client/I18NTest.java
===================================================================
--- user/test/com/google/gwt/i18n/client/I18NTest.java  (revision 3885)
+++ user/test/com/google/gwt/i18n/client/I18NTest.java  (working copy)
@@ -20,7 +20,6 @@
 import com.google.gwt.i18n.client.gen.Colors;
 import com.google.gwt.i18n.client.gen.Shapes;
 import com.google.gwt.i18n.client.gen.TestMessages;
-import com.google.gwt.i18n.client.impl.ConstantMap;
 import com.google.gwt.i18n.client.resolutiontest.Inners;
 import com.google.gwt.i18n.client.resolutiontest.Inners.ExtendsInnerInner;
 import com.google.gwt.i18n.client.resolutiontest.Inners.HasInner;
@@ -37,6 +36,7 @@
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Date;
+import java.util.HashMap;
 import java.util.Iterator;
 import java.util.Map;
 import java.util.MissingResourceException;
@@ -52,7 +52,8 @@
     return "com.google.gwt.i18n.I18NTest";
   }
 
-  @SuppressWarnings("unchecked") // intentional test of raw map
+  @SuppressWarnings("unchecked")
+  // intentional test of raw map
   public void testAnnotatedConstants() {
     TestAnnotatedConstants c = GWT.create(TestAnnotatedConstants.class);
     assertEquals(14, c.fourteen());
@@ -189,161 +190,146 @@
     TestConstants types = (TestConstants) GWT.create(TestConstants.class);
 
     Map<String, String> map = types.mapABCD();
-    assertEquals(4, map.size());
-    assertEquals("valueA", map.get("keyA"));
-    assertEquals("valueB", map.get("keyB"));
-    assertEquals("valueC", map.get("keyC"));
-    assertEquals("valueD", map.get("keyD"));
-
+    Map<String, String> expectedMap = getMapFromArrayUsingASimpleRule(new 
String[] {
+        "A", "B", "C", "D"});
     assertNull(map.get("bogus"));
+    compareMapsComprehensively(map, expectedMap);
+    
+    /*
+     * Test if the returned map can be modified in any way. Things are working
+     * as expected if exceptions are thrown in each case.
+     */
+    String failureMessage = "Should have thrown UnsupportedOperationException";
+    /* test map operations */
+    try {
+      map.remove("keyA");
+      fail(failureMessage + " on map.remove");
+    } catch (UnsupportedOperationException e) {
+    }
+    try {
+      map.put("keyA", "allA");
+      fail(failureMessage + "on map.put of existing key");
+    } catch (UnsupportedOperationException e) {
+    }
+    try {
+      map.put("keyZ", "allZ");
+      fail(failureMessage + "on map.put of new key");
+    } catch (UnsupportedOperationException e) {
+    }
+    try {
+      map.clear();
+      fail(failureMessage + " on map.clear");
+    } catch (UnsupportedOperationException e) {
+    }
 
-    Set<String> keys = map.keySet();
-    Iterator<String> keyIter = keys.iterator();
-    assertEquals("keyA", keyIter.next());
-    assertEquals("keyB", keyIter.next());
-    assertEquals("keyC", keyIter.next());
-    assertEquals("keyD", keyIter.next());
-    assertFalse(keyIter.hasNext());
+    /* test map.keySet() operations */
+    try {
+      map.keySet().add("keyZ");
+      fail(failureMessage + " on map.keySet().add");
+    } catch (UnsupportedOperationException e) {
+    }
+    try {
+      map.keySet().remove("keyA");
+      fail(failureMessage + " on map.keySet().remove");
+    } catch (UnsupportedOperationException e) {
+    }
+    try {
+      map.keySet().clear();
+      fail(failureMessage + " on map.keySet().clear");
+    } catch (UnsupportedOperationException e) {
+    }
 
-    Collection<String> values = map.values();
-    Iterator<String> valueIter = values.iterator();
-    assertEquals("valueA", valueIter.next());
-    assertEquals("valueB", valueIter.next());
-    assertEquals("valueC", valueIter.next());
-    assertEquals("valueD", valueIter.next());
-    assertFalse(keyIter.hasNext());
-
+    /* test map.values() operations */
     try {
-      map.remove("keyA");
-      fail("Should have thrown UnsupportedOperationException");
+      map.values().add("valueZ");
+      fail(failureMessage + " on map.values().add");
     } catch (UnsupportedOperationException e) {
-      // good if an exception was caught
     }
+    try {
+      map.values().remove("valueA");
+      fail(failureMessage + " on map.values().clear()");
+    } catch (UnsupportedOperationException e) {
+    }
+    try {
+      map.values().clear();
+      fail(failureMessage + " on map.values().clear()");
+    } catch (UnsupportedOperationException e) {
+    }
 
+    /* test map.entrySet() operations */
+    Map.Entry<String, String> firstEntry = map.entrySet().iterator().next();
     try {
-      keys.clear();
-      fail("Should have thrown UnsupportedOperationException");
+      map.entrySet().clear();
+      fail(failureMessage + "on map.entrySet().clear");
     } catch (UnsupportedOperationException e) {
-      // good if an exception was caught
     }
-
-    // TODO: fixme -- values are supposed to be backed by the map and should
-    // fail if modified
-    // try {
-    // Iterator nonmutableIter = keys.iterator();
-    // nonmutableIter.next();
-    // nonmutableIter.remove();
-    // fail("Should have thrown UnsupportedOperationException");
-    // } catch (UnsupportedOperationException e) {
-    // // good if an exception was caught
-    // }
+    try {
+      map.entrySet().remove(firstEntry);
+      fail(failureMessage + " on map.entrySet().remove");
+    } catch (UnsupportedOperationException e) {
+    }
+    try {
+      map.entrySet().add(firstEntry);
+      fail(failureMessage + "on map.entrySet().add");
+    } catch (UnsupportedOperationException e) {
+    }
+    try {
+      firstEntry.setValue("allZ");
+      fail(failureMessage + "on firstEntry.setValue");
+    } catch (UnsupportedOperationException e) {
+    }
+    try {
+      map.clear();
+      fail(failureMessage + " on map.clear");
+    } catch (UnsupportedOperationException e) {
+    }
   }
 
   /**
-   * Tests focus on just the key order, since ABCD exercises the map.
+   * Tests exercise the cache.
    */
   public void testConstantMapBACD() {
     TestConstants types = (TestConstants) GWT.create(TestConstants.class);
-
-    ConstantMap map = (ConstantMap) types.mapBACD();
-
-    Set<String> keys = map.keySet();
-    Iterator<String> keyIter = keys.iterator();
-    assertEquals("keyB", keyIter.next());
-    assertEquals("keyA", keyIter.next());
-    assertEquals("keyC", keyIter.next());
-    assertEquals("keyD", keyIter.next());
-
-    Collection<String> values = map.values();
-    Iterator<String> valueIter = values.iterator();
-    assertEquals("valueB", valueIter.next());
-    assertEquals("valueA", valueIter.next());
-    assertEquals("valueC", valueIter.next());
-    assertEquals("valueD", valueIter.next());
+    Map<String, String> map = types.mapBACD();
+    Map<String, String> expectedMap = getMapFromArrayUsingASimpleRule(new 
String[] {
+        "B", "A", "C", "D"});
+    compareMapsComprehensively(map, expectedMap);
   }
 
   /**
-   * Tests focus on correctness of entries, since ABCD exercises the map.
+   * Tests exercise the cache.
    */
   public void testConstantMapBBB() {
     TestConstants types = (TestConstants) GWT.create(TestConstants.class);
-
-    ConstantMap map = (ConstantMap) types.mapBBB();
-
-    assertEquals(1, map.size());
-
-    Set<String> keys = map.keySet();
-    assertEquals(1, keys.size());
-    Iterator<String> keyIter = keys.iterator();
-    assertEquals("keyB", keyIter.next());
-
-    Collection<String> values = map.values();
-    assertEquals(1, values.size());
-    Iterator<String> valueIter = values.iterator();
-    assertEquals("valueB", valueIter.next());
+    Map<String, String> map = types.mapBBB();
+    Map<String, String> expectedMap = getMapFromArrayUsingASimpleRule(new 
String[] {"B"});
+    compareMapsComprehensively(map, expectedMap);
   }
 
   /**
-   * Tests focus on just the key order, since ABCD exercises the map.
+   * Tests exercise the cache and check if Map works as the declared return
+   * type.
    */
-  public void testConstantMapDBCA() {
+  public void testConstantMapDCBA() {
     TestConstants types = (TestConstants) GWT.create(TestConstants.class);
-
-    ConstantMap map = (ConstantMap) types.mapDCBA();
-
-    Set<String> keys = map.keySet();
-    Iterator<String> keyIter = keys.iterator();
-    assertEquals("keyD", keyIter.next());
-    assertEquals("keyC", keyIter.next());
-    assertEquals("keyB", keyIter.next());
-    assertEquals("keyA", keyIter.next());
-
-    Collection<String> values = map.values();
-    Iterator<String> valueIter = values.iterator();
-    assertEquals("valueD", valueIter.next());
-    assertEquals("valueC", valueIter.next());
-    assertEquals("valueB", valueIter.next());
-    assertEquals("valueA", valueIter.next());
+    Map<String, String> map = types.mapDCBA();
+    Map<String, String> expectedMap = getMapFromArrayUsingASimpleRule(new 
String[] {
+        "D", "C", "B", "A"});
+    compareMapsComprehensively(map, expectedMap);
   }
 
   /**
-   * Tests focus on correctness of entries, since ABCD exercises the map.
+   * Tests exercise the cache and check if Map works as the declared return
+   * type.
    */
   public void testConstantMapXYZ() {
     TestConstants types = (TestConstants) GWT.create(TestConstants.class);
-
-    ConstantMap map = (ConstantMap) types.mapXYZ();
-
-    assertEquals(3, map.size());
-
-    Set<String> keys = map.keySet();
-    assertEquals(3, keys.size());
-    Iterator<String> keyIter = keys.iterator();
-    assertEquals("keyX", keyIter.next());
-    assertEquals("keyY", keyIter.next());
-    assertEquals("keyZ", keyIter.next());
-
-    Collection<String> values = map.values();
-    assertEquals(3, values.size());
-    Iterator<String> valueIter = values.iterator();
-    assertEquals("valueZ", valueIter.next());
-    assertEquals("valueZ", valueIter.next());
-    assertEquals("valueZ", valueIter.next());
-
-    Set<Map.Entry<String, String>> entries = map.entrySet();
-    assertEquals(3, entries.size());
-    Iterator<Map.Entry<String, String>> entryIter = entries.iterator();
-    Map.Entry<String, String> entry;
-
-    entry = entryIter.next();
-    assertEquals("keyX", entry.getKey());
-    assertEquals("valueZ", entry.getValue());
-    entry = entryIter.next();
-    assertEquals("keyY", entry.getKey());
-    assertEquals("valueZ", entry.getValue());
-    entry = entryIter.next();
-    assertEquals("keyZ", entry.getKey());
-    assertEquals("valueZ", entry.getValue());
+    Map<String, String> map = types.mapXYZ();
+    Map<String, String> expectedMap = new HashMap<String, String>();
+    expectedMap.put("keyX", "valueZ");
+    expectedMap.put("keyY", "valueZ");
+    expectedMap.put("keyZ", "valueZ");
+    compareMapsComprehensively(map, expectedMap);
   }
 
   public void testConstantStringArrays() {
@@ -451,28 +437,6 @@
     assertEquals(Integer.MIN_VALUE, types.intMin());
   }
 
-  // Uncomment for desk tests
-  // /**
-  // * Tests focus on correctness of entries, since ABCD exercises the map.
-  // */
-  // public void testConstantMapEmpty() {
-  // TestConstants types = (TestConstants) GWT.create(TestConstants.class);
-  //
-  // ConstantMap map = (ConstantMap) types.mapEmpty();
-  //
-  // assertEquals(0, map.size());
-  //
-  // Set keys = map.keySet();
-  // assertEquals(0, keys.size());
-  // Iterator keyIter = keys.iterator();
-  // assertFalse(keyIter.hasNext());
-  //
-  // Collection values = map.values();
-  // assertEquals(0, values.size());
-  // Iterator valueIter = values.iterator();
-  // assertFalse(valueIter.hasNext());
-  // }
-
   public void testLocalizableInner() {
     // Check simple inner
     LocalizableSimpleInner s = (LocalizableSimpleInner) 
GWT.create(Inners.LocalizableSimpleInner.class);
@@ -537,6 +501,28 @@
     assertEquals("estednay underscoray", m.nestedUnderscore());
   }
 
+  // Uncomment for desk tests
+  // /**
+  // * Tests focus on correctness of entries, since ABCD exercises the map.
+  // */
+  // public void testConstantMapEmpty() {
+  // TestConstants types = (TestConstants) GWT.create(TestConstants.class);
+  //
+  // ConstantMap map = (ConstantMap) types.mapEmpty();
+  //
+  // assertEquals(0, map.size());
+  //
+  // Set keys = map.keySet();
+  // assertEquals(0, keys.size());
+  // Iterator keyIter = keys.iterator();
+  // assertFalse(keyIter.hasNext());
+  //
+  // Collection values = map.values();
+  // assertEquals(0, values.size());
+  // Iterator valueIter = values.iterator();
+  // assertFalse(valueIter.hasNext());
+  // }
+
   public void testShapesFamily() {
     Shapes shapes = (Shapes) GWT.create(Shapes.class);
     // test overload
@@ -588,6 +574,47 @@
     }
   }
 
+  private <T> boolean compare(Collection<T> collection1,
+      Collection<T> collection2) {
+    if (collection1 == null) {
+      return (collection2 == null);
+    }
+    if (collection2 == null) {
+      return false;
+    }
+    if (collection1.size() != collection2.size()) {
+      return false;
+    }
+    for (T element1 : collection1) {
+      boolean found = false;
+      for (T element2 : collection2) {
+        if (element1.equals(element2)) {
+          found = true;
+          break;
+        }
+      }
+      if (!found) {
+        return false;
+      }
+    }
+    return true;
+  }
+
+  // compare the map, entrySet, keySet, and values
+  private void compareMapsComprehensively(Map<String, String> map,
+      Map<String, String> expectedMap) {
+    // checking both directions to verify that the equals implementation is
+    // correct both ways
+    assertEquals(expectedMap, map);
+    assertEquals(map, expectedMap);
+    assertEquals(expectedMap.entrySet(), map.entrySet());
+    assertEquals(map.entrySet(), expectedMap.entrySet());
+    assertEquals(expectedMap.keySet(), map.keySet());
+    assertEquals(map.keySet(), expectedMap.keySet());
+    assertTrue(compare(expectedMap.values(), map.values()));
+    assertTrue(compare(map.values(), expectedMap.values()));
+  }
+
   private native void createDummyDictionaries() /*-{
     $wnd.testDic = new Object();
     $wnd.testDic.formattedMessage = "3 {2},{2},{2}, one {0}, two {1} {1}";
@@ -597,4 +624,12 @@
     $wnd.emptyDic = new Object();
     $wnd.malformedDic = 4;
   }-*/;
+
+  private Map<String, String> getMapFromArrayUsingASimpleRule(String array[]) {
+    Map<String, String> map = new HashMap<String, String>();
+    for (String str : array) {
+      map.put("key" + str, "value" + str);
+    }
+    return map;
+  }
 }
Index: user/src/com/google/gwt/i18n/rebind/ConstantsWithLookupImplCreator.java
===================================================================
--- user/src/com/google/gwt/i18n/rebind/ConstantsWithLookupImplCreator.java     
(revision 3885)
+++ user/src/com/google/gwt/i18n/rebind/ConstantsWithLookupImplCreator.java     
(working copy)
@@ -23,7 +23,6 @@
 import com.google.gwt.core.ext.typeinfo.JType;
 import com.google.gwt.core.ext.typeinfo.TypeOracle;
 import com.google.gwt.core.ext.typeinfo.TypeOracleException;
-import com.google.gwt.i18n.client.impl.ConstantMap;
 import com.google.gwt.i18n.rebind.AbstractResource.ResourceList;
 import com.google.gwt.user.rebind.AbstractMethodCreator;
 import com.google.gwt.user.rebind.SourceWriter;
@@ -125,7 +124,7 @@
       namesToMethodCreators.put("getMap", new LookupMethodCreator(this, 
mapType) {
         @Override
         public String getReturnTypeName() {
-          return ConstantMap.class.getCanonicalName();
+          return ConstantsMapMethodCreator.GENERIC_STRING_MAP_TYPE;
         }
       });
 
Index: user/src/com/google/gwt/i18n/rebind/ConstantsMapMethodCreator.java
===================================================================
--- user/src/com/google/gwt/i18n/rebind/ConstantsMapMethodCreator.java  
(revision 3885)
+++ user/src/com/google/gwt/i18n/rebind/ConstantsMapMethodCreator.java  
(working copy)
@@ -26,6 +26,9 @@
  * Creator for methods of the form Map getX() .
  */
 class ConstantsMapMethodCreator extends AbstractLocalizableMethodCreator {
+
+  static final String GENERIC_STRING_MAP_TYPE = 
"java.util.Map<java.lang.String, java.lang.String>";
+
   /**
    * Constructor for localizable returnType method creator.
    * 
@@ -59,12 +62,12 @@
     enableCache();
     // check cache for array
     String constantMapClassName = ConstantMap.class.getCanonicalName();
-    println(constantMapClassName + " args = (" + constantMapClassName + ") 
cache.get("
-        + wrap(methodName) + ");");
+    println(GENERIC_STRING_MAP_TYPE + " args = (" + GENERIC_STRING_MAP_TYPE
+        + ") cache.get(" + wrap(methodName) + ");");
     // if not found create Map
     println("if (args == null) {");
     indent();
-    println("args = new " + constantMapClassName + "();");
+    println("args = new " + constantMapClassName + "(new String[] {");
     String value;
     try {
       value = resourceList.getRequiredStringExt(key, null);
@@ -73,17 +76,30 @@
       throw e;
     }
     String[] args = ConstantsStringArrayMethodCreator.split(value);
-
+    
+    indent();
+    indent();
     for (int i = 0; i < args.length; i++) {
+      // prepend the keys with ":" since they will be stored in a JSO
+      println(wrap(":" + args[i]) + ", ");
+    }
+    outdent();
+    println("},");
+    indent();
+    println("new String[] {");
+    for (int i = 0; i < args.length; i++) {
       try {
-        key = args[i];
-        String keyValue = getResources().getString(key);
-        println("args.put(" + wrap(key) + ", " + wrap(keyValue) + ");");
+        String keyValue = getResources().getString(args[i]);
+        println(wrap(keyValue) + ",");
       } catch (MissingResourceException e) {
         e.setDuring("implementing map");
         throw e;
       }
     }
+    outdent();
+    println("});");
+    outdent();
+    println("args = java.util.Collections.unmodifiableMap(args);");
     println("cache.put(" + wrap(methodName) + ", args);");
     outdent();
     println("};");
Index: user/src/com/google/gwt/i18n/client/impl/ConstantMap.java
===================================================================
--- user/src/com/google/gwt/i18n/client/impl/ConstantMap.java   (revision 3885)
+++ user/src/com/google/gwt/i18n/client/impl/ConstantMap.java   (working copy)
@@ -15,33 +15,43 @@
  */
 package com.google.gwt.i18n.client.impl;
 
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.HashMap;
-import java.util.Iterator;
+import com.google.gwt.core.client.JavaScriptObject;
+
+import java.util.AbstractMap;
+import java.util.HashSet;
 import java.util.Map;
 import java.util.Set;
 
 /**
- * Read only Map used when returning <code>Constants</code> maps. Preserves
- * order. ConstantMap should only be created or modified by GWT, as constant
- * maps are constructed using a very stereotyped algorithm, which allows
- * <code>ConstantMap</code> to maintain order with very little code. In
- * specific, no elements are every removed from them and all elements are added
- * before the first user operation.
+ * Map used when creating <code>Constants</code> maps. This class is to be used
+ * only by the GWT code. The map is immediately wrapped in
+ * Collections.unmodifiableMap(..) preventing any changes after construction.
  */
-public class ConstantMap extends HashMap<String, String> {
+public class ConstantMap extends AbstractMap<String, String> {
 
-  private static class DummyMapEntry implements Map.Entry<String, String> {
+  private static class MapEntry implements Map.Entry<String, String> {
     private final String key;
-
     private final String value;
 
-    DummyMapEntry(String key, String value) {
+    MapEntry(String key, String value) {
       this.key = key;
       this.value = value;
     }
 
+    /**
+     * Implement equals as per Sun's specification.
+     */
+    @Override
+    public boolean equals(Object o) {
+      if (!(o instanceof Entry)) {
+        return false;
+      }
+      Entry e2 = (Entry) o;
+      return (key == null ? e2.getKey() == null : key.equals(e2.getKey()))
+          && (value == null ? e2.getValue() == null
+              : value.equals(e2.getValue()));
+    }
+
     public String getKey() {
       return key;
     }
@@ -50,102 +60,107 @@
       return value;
     }
 
+    /**
+     * Calculate the hash code using Sun's specified algorithm.
+     */
+    @Override
+    public final 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 arg0) {
       throw new UnsupportedOperationException();
     }
   }
 
-  private class OrderedConstantSet<T> extends ArrayList<T> implements Set<T> {
-    private class ImmutableIterator implements Iterator<T> {
-      private final Iterator<T> base;
+  // keySet computed on demand.
+  private Set<String> keySet;
 
-      ImmutableIterator(Iterator<T> base) {
-        this.base = base;
-      }
+  /*
+   * Accesses need to be prefixed with ':' to prevent conflict with built-in
+   * JavaScript properties.
+   */
+  // map is used only in jsni methods
+  @SuppressWarnings("unused")
+  private JavaScriptObject map;
 
-      public boolean hasNext() {
-        return base.hasNext();
-      }
+  private Set<Map.Entry<String, String>> entries;
 
-      public T next() {
-        return base.next();
-      }
-
-      public void remove() {
-        throw new UnsupportedOperationException("Immutable set");
-      }
+  public ConstantMap(String keys[], String values[]) {
+    // a separate function so that testing in nonJsni environment is possible
+    init();
+    int i = 0;
+    for (String key : keys) {
+      putImpl(key, values[i++]);
     }
-
-    @Override
-    public void clear() {
-      throw new UnsupportedOperationException("Immutable set");
-    }
-
-    @Override
-    public Iterator<T> iterator() {
-      Iterator<T> base = super.iterator();
-      return new ImmutableIterator(base);
-    }
   }
 
-  private OrderedConstantSet<Map.Entry<String, String>> entries;
-
-  private final OrderedConstantSet<String> keys = new 
OrderedConstantSet<String>();
-
-  private OrderedConstantSet<String> values;
-
   @Override
-  public void clear() {
-    throw unsupported("clear");
+  public boolean containsKey(Object key) {
+    return keySet().contains(key);
   }
 
   @Override
-  public Set<Map.Entry<String, String>> entrySet() {
+  public Set<java.util.Map.Entry<String, String>> entrySet() {
     if (entries == null) {
-      entries = new OrderedConstantSet<Map.Entry<String, String>>();
-      for (int i = 0; i < keys.size(); i++) {
-        String key = keys.get(i);
-        String value = get(key);
-        entries.add(new DummyMapEntry(key, value));
+      entries = new HashSet<Map.Entry<String, String>>();
+      for (String key : keySet()) {
+        entries.add(new MapEntry(key, get(key)));
       }
     }
     return entries;
   }
 
   @Override
-  public Set<String> keySet() {
-    return keys;
+  public String get(Object ob) {
+    if (ob instanceof String) {
+      return get((String) ob);
+    }
+    return null;
   }
 
+  // Prepend ':' to avoid conflicts with built-in Object properties.
+  public native String get(String key) /*-{
+    return [EMAIL PROTECTED]::map[':' + key];
+  }-*/;
+
   @Override
-  public String put(String key, String value) {
-    // We may want to find a more efficient implementation later.
-    boolean exists = keys.contains(key);
-    if (!exists) {
-      keys.add(key);
+  public Set<String> keySet() {
+    if (keySet == null) {
+      keySet = new HashSet<String>();
+      addAllKeysFromJavascriptObject(keySet, map);
     }
-    return super.put(key, value);
+    return keySet;
   }
 
   @Override
-  public String remove(Object key) {
-    throw unsupported("remove");
+  public int size() {
+    return keySet().size();
   }
 
-  @Override
-  public Collection<String> values() {
-    if (values == null) {
-      values = new OrderedConstantSet<String>();
-      for (int i = 0; i < keys.size(); i++) {
-        Object element = keys.get(i);
-        values.add(this.get(element));
-      }
+  protected native void init() /*-{
+    [EMAIL PROTECTED]::map = [];
+  }-*/;
+
+  // Already prepended ':' to avoid conflicts with built-in Object properties.
+  protected native void putImpl(String key, String value) /*-{
+    [EMAIL PROTECTED]::map[key] = value;
+  }-*/;
+
+  // only count keys with ':' prefix
+  private native void addAllKeysFromJavascriptObject(Set<String> s,
+      JavaScriptObject javaScriptObject) /*-{
+    for(var key in javaScriptObject) {
+      if (key.charAt(0) != ':') continue;
+      [EMAIL PROTECTED]::add(Ljava/lang/Object;)(key.substring(1));
     }
-    return values;
-  }
-
-  private UnsupportedOperationException unsupported(String operation) {
-    return new UnsupportedOperationException(operation
-        + " not supported on a constant map");
-  }
+  }-*/;
 }

Reply via email to