Change CurrencyListGenerator to take advantage of inheritance in the generated
classes, to minimize the amount of data seen by the compiler.  Also, add
additional runtime locales tests.

Patch by: jat, scottb
Review by: scottb, jat


git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@5174 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/user/src/com/google/gwt/i18n/client/impl/CurrencyList.java b/user/src/com/google/gwt/i18n/client/impl/CurrencyList.java
index 25af062..9be96b7 100644
--- a/user/src/com/google/gwt/i18n/client/impl/CurrencyList.java
+++ b/user/src/com/google/gwt/i18n/client/impl/CurrencyList.java
@@ -133,7 +133,7 @@
       loadNamesMap();
     }
   }
-
+  
   /**
    * Directly reference an entry in the currency map JSO.
    * 
@@ -141,14 +141,14 @@
    * @return currency data
    */
   protected final native CurrencyData getEntry(String code) /*-{
-    return this.@com.google.gwt.i18n.client.impl.CurrencyList::dataMap[':' + code];
+    return this.@com.google.gwt.i18n.client.impl.CurrencyList::dataMap[code];
   }-*/;
 
   /**
    * Directly reference an entry in the currency names map JSO.
    * 
    * @param code ISO4217 currency code
-   * @return currency name
+   * @return currency name, or the currency code if not known
    */
   protected final native String getNamesEntry(String code) /*-{
     return this.@com.google.gwt.i18n.client.impl.CurrencyList::namesMap[code] || code;
@@ -161,10 +161,10 @@
    */
   protected native void loadCurrencyMap() /*-{
     this.@com.google.gwt.i18n.client.impl.CurrencyList::dataMap = {
-        ":USD": [ "USD", "$", 2 ],
-        ":EUR": [ "EUR", "€", 2 ],
-        ":GBP": [ "GBP", "UK£", 2 ],
-        ":JPY": [ "JPY", "¥", 0 ],
+        "USD": [ "USD", "$", 2 ],
+        "EUR": [ "EUR", "€", 2 ],
+        "GBP": [ "GBP", "UK£", 2 ],
+        "JPY": [ "JPY", "¥", 0 ],
      };
   }-*/;
 
@@ -183,13 +183,45 @@
   }-*/;
 
   /**
+   * Add all entries in {@code override} to the currency data map, replacing
+   * any existing entries.  This is used by subclasses that need to slightly
+   * alter the data used by the parent locale.
+   * 
+   * @param override JS object with currency code -> CurrencyData pairs
+   */
+  protected final native void overrideCurrencyMap(JavaScriptObject override) /*-{
+    var map = this.@com.google.gwt.i18n.client.impl.CurrencyList::dataMap;
+    for (var key in override) {
+      if (override.hasOwnProperty(key)) {
+        map[key] = override[key];
+      }
+    }
+  }-*/;
+
+  /**
+   * Add all entries in {@code override} to the currency name map, replacing
+   * any existing entries.  This is used by subclasses that need to slightly
+   * alter the data used by the parent locale.
+   * 
+   * @param override JS object with currency code -> name pairs
+   */
+  protected final native void overrideNamesMap(JavaScriptObject override) /*-{
+    var map = this.@com.google.gwt.i18n.client.impl.CurrencyList::namesMap;
+    for (var key in override) {
+      if (override.hasOwnProperty(key)) {
+        map[key] = override[key];
+      }
+    }
+  }-*/;
+
+  /**
    * Add currency codes contained in the map to an ArrayList.
    */
   private native void loadCurrencyKeys(ArrayList<String> keys) /*-{
     var map = this.@com.google.gwt.i18n.client.impl.CurrencyList::dataMap;
     for (var key in map) {
-      if (key.charCodeAt(0) == 58) {
-        keys.@java.util.ArrayList::add(Ljava/lang/Object;)(key.substring(1));
+      if (map.hasOwnProperty(key)) {
+        keys.@java.util.ArrayList::add(Ljava/lang/Object;)(key);
       }
     }
   }-*/;
diff --git a/user/src/com/google/gwt/i18n/rebind/CurrencyListGenerator.java b/user/src/com/google/gwt/i18n/rebind/CurrencyListGenerator.java
index 64fc211..3b61416 100644
--- a/user/src/com/google/gwt/i18n/rebind/CurrencyListGenerator.java
+++ b/user/src/com/google/gwt/i18n/rebind/CurrencyListGenerator.java
@@ -36,6 +36,7 @@
 import java.io.PrintWriter;
 import java.io.UnsupportedEncodingException;
 import java.util.Arrays;
+import java.util.Collections;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
@@ -43,6 +44,7 @@
 import java.util.Set;
 import java.util.TreeMap;
 import java.util.Map.Entry;
+import java.util.regex.Pattern;
 
 /**
  * Generator used to generate a localized version of CurrencyList, which
@@ -51,9 +53,160 @@
  */
 public class CurrencyListGenerator extends Generator {
 
-  private static final String CURRENCY_DATA = CurrencyData.class.getCanonicalName();
+  /**
+   * Immutable collection of data about a currency in a locale, built from the
+   * CurrencyData and CurrencyExtra properties files.
+   */
+  private static class CurrencyInfo {
 
-  private static final String CURRENCY_LIST = CurrencyList.class.getCanonicalName();
+    private static final Pattern SPLIT_VERTICALBAR = Pattern.compile("\\|");
+
+    private final String code;
+
+    private final String displayName;
+
+    private final int flags;
+
+    private final boolean obsolete;
+
+    private final String portableSymbol;
+
+    private final String symbol;
+
+    /**
+     * Create an instance.
+     * 
+     * currencyData format:
+     * 
+     * <pre>
+     *       display name|symbol|decimal digits|not-used-flag
+     * </pre>
+     * 
+     * <ul>
+     * <li>If a symbol is not supplied, the currency code will be used
+     * <li>If # of decimal digits is omitted, 2 is used
+     * <li>If a currency is not generally used, not-used-flag=1
+     * <li>Trailing empty fields can be omitted
+     * <li>If null, use currencyCode as the display name
+     * </ul>
+     * 
+     * extraData format:
+     * 
+     * <pre>
+     *       portable symbol|flags|currency symbol override
+     *     flags are space separated list of:
+     *       At most one of the following:
+     *         SymPrefix     The currency symbol goes before the number,
+     *                       regardless of the normal position for this locale.
+     *         SymSuffix     The currency symbol goes after the number,
+     *                       regardless of the normal position for this locale.
+     *
+     *       At most one of the following:
+     *         ForceSpace    Always add a space between the currency symbol
+     *                       and the number.
+     *         ForceNoSpace  Never add a space between the currency symbol
+     *                       and the number.
+     * </pre>
+     * 
+     * @param currencyCode ISO4217 currency code
+     * @param currencyData entry from a CurrencyData properties file
+     * @param extraData entry from a CurrencyExtra properties file
+     * @throws NumberFormatException
+     */
+    public CurrencyInfo(String currencyCode, String currencyData,
+        String extraData) throws NumberFormatException {
+      code = currencyCode;
+      if (currencyData == null) {
+        currencyData = currencyCode;
+      }
+      String[] currencySplit = SPLIT_VERTICALBAR.split(currencyData);
+      String currencyDisplay = currencySplit[0];
+      String currencySymbol = null;
+      if (currencySplit.length > 1 && currencySplit[1].length() > 0) {
+        currencySymbol = currencySplit[1];
+      }
+      int currencyFractionDigits = 2;
+      if (currencySplit.length > 2 && currencySplit[2].length() > 0) {
+        currencyFractionDigits = Integer.valueOf(currencySplit[2]);
+      }
+      boolean currencyObsolete = false;
+      if (currencySplit.length > 3 && currencySplit[3].length() > 0) {
+        currencyObsolete = Integer.valueOf(currencySplit[3]) != 0;
+      }
+      int currencyFlags = currencyFractionDigits;
+      String currencyPortableSymbol = "";
+      if (extraData != null) {
+        // CurrencyExtra contains up to 3 fields separated by |
+        // 0 - portable currency symbol
+        // 1 - space-separated flags regarding currency symbol
+        // positioning/spacing
+        // 2 - override of CLDR-derived currency symbol
+        String[] extraSplit = SPLIT_VERTICALBAR.split(extraData);
+        currencyPortableSymbol = extraSplit[0];
+        if (extraSplit.length > 1) {
+          if (extraSplit[1].contains("SymPrefix")) {
+            currencyFlags |= CurrencyData.POS_FIXED_FLAG;
+          } else if (extraSplit[1].contains("SymSuffix")) {
+            currencyFlags |= CurrencyData.POS_FIXED_FLAG
+                | CurrencyData.POS_SUFFIX_FLAG;
+          }
+          if (extraSplit[1].contains("ForceSpace")) {
+            currencyFlags |= CurrencyData.SPACING_FIXED_FLAG
+                | CurrencyData.SPACE_FORCED_FLAG;
+          } else if (extraSplit[1].contains("ForceNoSpace")) {
+            currencyFlags |= CurrencyData.SPACING_FIXED_FLAG;
+          }
+        }
+        // If a non-empty override is supplied, use it for the currency
+        // symbol.
+        if (extraSplit.length > 2 && extraSplit[2].length() > 0) {
+          currencySymbol = extraSplit[2];
+        }
+        // If we don't have a currency symbol yet, use the portable symbol if
+        // supplied.
+        if (currencySymbol == null && currencyPortableSymbol.length() > 0) {
+          currencySymbol = currencyPortableSymbol;
+        }
+      }
+      // If all else fails, use the currency code as the symbol.
+      if (currencySymbol == null) {
+        currencySymbol = currencyCode;
+      }
+      displayName = currencyDisplay;
+      symbol = currencySymbol;
+      flags = currencyFlags;
+      portableSymbol = currencyPortableSymbol;
+      obsolete = currencyObsolete;
+    }
+
+    public String getDisplayName() {
+      return displayName;
+    }
+
+    public int getFlags() {
+      return flags;
+    }
+
+    public String getJson() {
+      StringBuilder buf = new StringBuilder();
+      buf.append("[ \"").append(quote(code)).append("\", \"");
+      buf.append(quote(symbol)).append("\", ").append(flags);
+      if (portableSymbol.length() > 0) {
+        buf.append(", \"").append(quote(portableSymbol)).append('\"');
+      }
+      return buf.append(']').toString();
+    }
+
+    public String getSymbol() {
+      return symbol;
+    }
+
+    public boolean isObsolete() {
+      return obsolete;
+    }
+  }
+
+  private static final String CURRENCY_DATA = CurrencyData.class.getCanonicalName();
 
   /**
    * Prefix for properties files containing CLDR-derived currency data for each
@@ -67,6 +220,8 @@
    */
   private static final String CURRENCY_EXTRA_PREFIX = "com/google/gwt/i18n/client/constants/CurrencyExtra";
 
+  private static final String CURRENCY_LIST = CurrencyList.class.getCanonicalName();
+
   /**
    * Prefix for properties files containing number formatting constants for each
    * locale. We use this only to get the default currency for our current
@@ -75,6 +230,16 @@
   private static final String NUMBER_CONSTANTS_PREFIX = "com/google/gwt/i18n/client/constants/NumberConstantsImpl";
 
   /**
+   * Backslash-escape any double quotes in the supplied string.
+   * 
+   * @param str string to quote
+   * @return string with double quotes backslash-escaped.
+   */
+  private static String quote(String str) {
+    return str.replace("\"", "\\\"");
+  }
+
+  /**
    * Generate an implementation for the given type.
    * 
    * @param logger error logger
@@ -102,179 +267,147 @@
       throw new UnableToCompleteException();
     }
     if (runtimeLocales.isEmpty()) {
-      return generateOne(logger, context, targetClass, locale);
+      return generateLocaleTree(logger, context, targetClass, locale);
     }
     CachedGeneratorContext cachedContext = new CachedGeneratorContext(context);
     return generateRuntimeSelection(logger, cachedContext, targetClass, locale,
         runtimeLocales);
   }
-  
-  private String generateOne(TreeLogger logger, GeneratorContext context,
-      JClassType targetClass, GwtLocale locale)
-      throws UnableToCompleteException {
+
+  /**
+   * Generate an implementation class for the requested locale, including all
+   * parent classes along the inheritance chain. The data will be kept at the
+   * location in the inheritance chain where it was defined in properties files.
+   * 
+   * @param logger
+   * @param context
+   * @param targetClass
+   * @param locale
+   * @return generated class name for the requested locale
+   */
+  private String generateLocaleTree(TreeLogger logger,
+      GeneratorContext context, JClassType targetClass, GwtLocale locale) {
+    String superClassName = CURRENCY_LIST;
+    List<GwtLocale> searchList = locale.getCompleteSearchList();
+
+    /**
+     * Map of currency code -> CurrencyInfo for that code.
+     */
+    Map<String, CurrencyInfo> allCurrencyData = new HashMap<String, CurrencyInfo>();
+
+    LocalizedProperties currencyExtra = null;
+    /*
+     * The searchList is guaranteed to be ordered such that subclasses always
+     * precede superclasses. Therefore, we iterate backwards to ensure that
+     * superclasses are always generated first.
+     */
+    String lastDefaultCurrencyCode = null;
+    for (int i = searchList.size(); i-- > 0;) {
+      GwtLocale search = searchList.get(i);
+      LocalizedProperties newExtra = getProperties(CURRENCY_EXTRA_PREFIX,
+          search);
+      if (newExtra != null) {
+        currencyExtra = newExtra;
+      }
+      Map<String, String> currencyData = getCurrencyData(search);
+      Set<String> keySet = currencyData.keySet();
+      String[] currencies = new String[keySet.size()];
+      keySet.toArray(currencies);
+      Arrays.sort(currencies);
+
+      // Go ahead and populate the data map.
+      for (String currencyCode : currencies) {
+        String extraData = currencyExtra == null ? null
+            : currencyExtra.getProperty(currencyCode);
+        allCurrencyData.put(currencyCode, new CurrencyInfo(currencyCode,
+            currencyData.get(currencyCode), extraData));
+      }
+
+      String defCurrencyCode = getDefaultCurrency(search);
+      // If this locale specifies a particular locale, or the one that is
+      // inherited has been changed in this locale, re-specify the default
+      // currency so the method will be generated.
+      if (defCurrencyCode == null && keySet.contains(lastDefaultCurrencyCode)) {
+        defCurrencyCode = lastDefaultCurrencyCode;
+      }
+      if (!currencyData.isEmpty() || defCurrencyCode != null) {
+        String newClass = generateOneLocale(logger, context, targetClass,
+            search, superClassName, currencies, allCurrencyData,
+            defCurrencyCode);
+        superClassName = newClass;
+        lastDefaultCurrencyCode = defCurrencyCode;
+      }
+    }
+    return superClassName;
+  }
+
+  /**
+   * Generate the implementation for a single locale, overriding from its parent
+   * only data that has changed in this locale.
+   * 
+   * @param logger
+   * @param context
+   * @param targetClass
+   * @param locale
+   * @param superClassName
+   * @param currencies the set of currencies defined in this locale
+   * @param allCurrencyData map of currency code -> unparsed CurrencyInfo for
+   *          that code
+   * @param defCurrencyCode default currency code for this locale
+   * @return fully-qualified class name generated
+   */
+  private String generateOneLocale(TreeLogger logger, GeneratorContext context,
+      JClassType targetClass, GwtLocale locale, String superClassName,
+      String[] currencies, Map<String, CurrencyInfo> allCurrencyData,
+      String defCurrencyCode) {
+
     String packageName = targetClass.getPackage().getName();
     String className = targetClass.getName().replace('.', '_') + "_"
-        + locale.getAsString();
+        + locale.getCanonicalForm().getAsString();
     PrintWriter pw = context.tryCreate(logger, packageName, className);
     if (pw != null) {
       ClassSourceFileComposerFactory factory = new ClassSourceFileComposerFactory(
           packageName, className);
-      factory.setSuperclass(targetClass.getQualifiedSourceName());
+      factory.setSuperclass(superClassName);
       factory.addImport(CURRENCY_LIST);
       factory.addImport(CURRENCY_DATA);
       SourceWriter writer = factory.createSourceWriter(context, pw);
-
-      // Load property files for this locale, handling inheritance properly.
-      GwtLocale[] currencyLocale = new GwtLocale[1];
-      LocalizedProperties currencyData = readProperties(logger,
-          CURRENCY_DATA_PREFIX, locale, currencyLocale);
-      GwtLocale[] extraLocale = new GwtLocale[1];
-      LocalizedProperties currencyExtra = readProperties(logger,
-          CURRENCY_EXTRA_PREFIX, locale, extraLocale);
-      GwtLocale[] numberLocale = new GwtLocale[1];
-      LocalizedProperties numberConstants = readProperties(logger,
-          NUMBER_CONSTANTS_PREFIX, locale, numberLocale);
-
-      // Get default currency code, set defaults in case it isn't found.
-      String defCurrencyCode = numberConstants.getProperty("defCurrencyCode");
-      if (defCurrencyCode == null) {
-        defCurrencyCode = "USD";
+      if (currencies.length > 0) {
+        writeCurrencyMethod(className, writer, currencies, allCurrencyData);
+        writeNamesMethod(className, writer, currencies, allCurrencyData);
       }
-
-      // Sort for deterministic output.
-      Set<?> keySet = currencyData.getPropertyMap().keySet();
-      String[] currencies = new String[keySet.size()];
-      keySet.toArray(currencies);
-      Arrays.sort(currencies);
-      Map<String, String> nameMap = new HashMap<String, String>();
-
-      writer.println("@Override");
-      writer.println("protected native void loadCurrencyMap() /*-{");
-      writer.indent();
-      writer.println("this.@com.google.gwt.i18n.client.impl.CurrencyList::dataMap = {");
-      writer.indent();
-      String defCurrencyObject = "[ \"" + quote(defCurrencyCode) + "\", \""
-          + quote(defCurrencyCode) + "\", 2 ]";
-      for (String currencyCode : currencies) {
-        String currencyEntry = currencyData.getProperty(currencyCode);
-        String[] currencySplit = currencyEntry.split("\\|");
-        String currencyDisplay = currencySplit[0];
-        String currencySymbol = null;
-        if (currencySplit.length > 1 && currencySplit[1].length() > 0) {
-          currencySymbol = currencySplit[1];
+      if (defCurrencyCode != null) {
+        CurrencyInfo currencyInfo = allCurrencyData.get(defCurrencyCode);
+        if (currencyInfo == null) {
+          // Synthesize a null info if the specified default wasn't found.
+          currencyInfo = new CurrencyInfo(defCurrencyCode, null, null);
+          allCurrencyData.put(defCurrencyCode, currencyInfo);
         }
-        int currencyFractionDigits = 2;
-        if (currencySplit.length > 2 && currencySplit[2].length() > 0) {
-          try {
-            currencyFractionDigits = Integer.valueOf(currencySplit[2]);
-          } catch (NumberFormatException e) {
-            // Ignore bad values
-            logger.log(TreeLogger.WARN, "Parse of \"" + currencySplit[2]
-                + "\" failed", e);
-          }
-        }
-        boolean currencyObsolete = false;
-        if (currencySplit.length > 3 && currencySplit[3].length() > 0) {
-          try {
-            currencyObsolete = Integer.valueOf(currencySplit[3]) != 0;
-          } catch (NumberFormatException e) {
-            // Ignore bad values
-            logger.log(TreeLogger.WARN, "Parse of \"" + currencySplit[3]
-                + "\" failed", e);
-          }
-        }
-        int currencyFlags = currencyFractionDigits;
-        String extraData = currencyExtra.getProperty(currencyCode);
-        String portableSymbol = "";
-        if (extraData != null) {
-          // CurrencyExtra contains up to 3 fields separated by |
-          // 0 - portable currency symbol
-          // 1 - space-separated flags regarding currency symbol
-          // positioning/spacing
-          // 2 - override of CLDR-derived currency symbol
-          String[] extraSplit = extraData.split("\\|");
-          portableSymbol = extraSplit[0];
-          if (extraSplit.length > 1) {
-            if (extraSplit[1].contains("SymPrefix")) {
-              currencyFlags |= CurrencyData.POS_FIXED_FLAG;
-            } else if (extraSplit[1].contains("SymSuffix")) {
-              currencyFlags |= CurrencyData.POS_FIXED_FLAG
-                  | CurrencyData.POS_SUFFIX_FLAG;
-            }
-            if (extraSplit[1].contains("ForceSpace")) {
-              currencyFlags |= CurrencyData.SPACING_FIXED_FLAG
-                  | CurrencyData.SPACE_FORCED_FLAG;
-            } else if (extraSplit[1].contains("ForceNoSpace")) {
-              currencyFlags |= CurrencyData.SPACING_FIXED_FLAG;
-            }
-          }
-          // If a non-empty override is supplied, use it for the currency
-          // symbol.
-          if (extraSplit.length > 2 && extraSplit[2].length() > 0) {
-            currencySymbol = extraSplit[2];
-          }
-          // If we don't have a currency symbol yet, use the portable symbol if
-          // supplied.
-          if (currencySymbol == null && portableSymbol.length() > 0) {
-            currencySymbol = portableSymbol;
-          }
-        }
-        // If all else fails, use the currency code as the symbol.
-        if (currencySymbol == null) {
-          currencySymbol = currencyCode;
-        }
-        String currencyObject = "[ \"" + quote(currencyCode) + "\", \""
-            + quote(currencySymbol) + "\", " + currencyFlags;
-        if (portableSymbol.length() > 0) {
-          currencyObject += ", \"" + quote(portableSymbol) + "\"";
-        }
-        currencyObject += "]";
-        if (!currencyObsolete) {
-          nameMap.put(currencyCode, currencyDisplay);
-          writer.println("// " + currencyDisplay);
-          writer.println("\":" + quote(currencyCode) + "\": " + currencyObject
-              + ",");
-        }
-        if (currencyCode.equals(defCurrencyCode)) {
-          defCurrencyObject = currencyObject;
-        }
+        writer.println();
+        writer.println("@Override");
+        writer.println("public native CurrencyData getDefault() /*-{");
+        writer.println("  return " + currencyInfo.getJson() + ";");
+        writer.println("}-*/;");
       }
-      writer.outdent();
-      writer.println("};");
-      writer.outdent();
-      writer.println("}-*/;");
-      writer.println();
-      writer.println("@Override");
-      writer.println("protected native void loadNamesMap() /*-{");
-      writer.indent();
-      writer.println("this.@com.google.gwt.i18n.client.impl.CurrencyList::namesMap = {");
-      writer.indent();
-      for (String currencyCode : currencies) {
-        String displayName = nameMap.get(currencyCode);
-        if (displayName != null && !currencyCode.equals(displayName)) {
-          writer.println("\"" + quote(currencyCode) + "\": \""
-              + quote(displayName) + "\",");
-        }
-      }
-      writer.outdent();
-      writer.println("};");
-      writer.outdent();
-      writer.println("}-*/;");
-      writer.println();
-      writer.println("@Override");
-      writer.println("public native CurrencyData getDefault() /*-{");
-      writer.println("  return " + defCurrencyObject + ";");
-      writer.println("}-*/;");
       writer.commit(logger);
     }
     return packageName + "." + className;
   }
 
-  
+  /**
+   * Generate a class which can select between alternate implementations at
+   * runtime based on the runtime locale.
+   * 
+   * @param logger TreeLogger instance for log messages
+   * @param context GeneratorContext for generating source files
+   * @param targetClass class to generate
+   * @param compileLocale the compile-time locale we are generating for
+   * @param locales set of all locales to generate
+   * @return fully-qualified class name that was generated
+   */
   private String generateRuntimeSelection(TreeLogger logger,
       GeneratorContext context, JClassType targetClass,
-      GwtLocale compileLocale, Set<GwtLocale> locales)
-      throws UnableToCompleteException {
+      GwtLocale compileLocale, Set<GwtLocale> locales) {
     String packageName = targetClass.getPackage().getName();
     String className = targetClass.getName().replace('.', '_') + "_"
         + compileLocale.getAsString() + "_runtimeSelection";
@@ -315,8 +448,7 @@
       writer.println("  return;");
       writer.println("}");
       boolean fetchedLocale = false;
-      Map<String, Set<GwtLocale>> localeMap = new TreeMap<String,
-          Set<GwtLocale>>();
+      Map<String, Set<GwtLocale>> localeMap = new TreeMap<String, Set<GwtLocale>>();
       String compileLocaleClass = processChildLocale(logger, context,
           targetClass, localeMap, compileLocale);
       if (compileLocaleClass == null) {
@@ -346,8 +478,7 @@
             writer.println();
             writer.print("    || ");
           }
-          writer.print("\"" + locale.toString()
-              + "\".equals(runtimeLocale)");
+          writer.print("\"" + locale.toString() + "\".equals(runtimeLocale)");
         }
         writer.println(") {");
         writer.println("  instance = new " + generatedClass + "();");
@@ -362,11 +493,108 @@
     return packageName + "." + className;
   }
 
-  private String processChildLocale(TreeLogger logger, GeneratorContext context,
-      JClassType targetClass, Map<String, Set<GwtLocale>> localeMap,
-      GwtLocale locale) throws UnableToCompleteException {
-    String generatedClass = generateOne(logger, context,
-        targetClass, locale);
+  /**
+   * Return a map of currency data for the requested locale, or null if there is
+   * not one (not that inheritance is not handled here).
+   * 
+   * The keys are ISO4217 currency codes. The format of the map values is:
+   * 
+   * <pre>
+   * display name|symbol|decimal digits|not-used-flag
+   * </pre>
+   * 
+   * If a symbol is not supplied, the currency code will be used If # of decimal
+   * digits is omitted, 2 is used If a currency is not generally used,
+   * not-used-flag=1 Trailing empty fields can be omitted
+   * 
+   * @param locale
+   * @return currency data map
+   */
+  @SuppressWarnings("unchecked")
+  private Map<String, String> getCurrencyData(GwtLocale locale) {
+    LocalizedProperties currencyData = getProperties(CURRENCY_DATA_PREFIX,
+        locale);
+    if (currencyData == null) {
+      return Collections.emptyMap();
+    }
+    return currencyData.getPropertyMap();
+  }
+
+  /**
+   * Returns the default currency code for the requested locale.
+   * 
+   * @param locale
+   * @return ISO4217 currency code
+   */
+  private String getDefaultCurrency(GwtLocale locale) {
+    String defCurrencyCode = null;
+    LocalizedProperties numberConstants = getProperties(
+        NUMBER_CONSTANTS_PREFIX, locale);
+    if (numberConstants != null) {
+      defCurrencyCode = numberConstants.getProperty("defCurrencyCode");
+    }
+    if (defCurrencyCode == null && locale.isDefault()) {
+      defCurrencyCode = "USD";
+    }
+    return defCurrencyCode;
+  }
+
+  /**
+   * Load a properties file for a given locale. Note that locale inheritance is
+   * the responsibility of the caller.
+   * 
+   * @param prefix classpath prefix of properties file
+   * @param locale locale to load
+   * @return LocalizedProperties instance containing properties file or null if
+   *         not found.
+   */
+  private LocalizedProperties getProperties(String prefix, GwtLocale locale) {
+    String propFile = prefix;
+    if (!locale.isDefault()) {
+      propFile += "_" + locale.getAsString();
+    }
+    propFile += ".properties";
+    InputStream str = null;
+    ClassLoader classLoader = getClass().getClassLoader();
+    LocalizedProperties props = new LocalizedProperties();
+    try {
+      str = classLoader.getResourceAsStream(propFile);
+      if (str != null) {
+        props.load(str, "UTF-8");
+        return props;
+      }
+    } catch (UnsupportedEncodingException e) {
+      // UTF-8 should always be defined
+      return null;
+    } catch (IOException e) {
+      return null;
+    } finally {
+      if (str != null) {
+        try {
+          str.close();
+        } catch (IOException e) {
+        }
+      }
+    }
+    return null;
+  }
+
+  /**
+   * Generate an implementation for a runtime locale, to be referenced from the
+   * generated runtime selection code.
+   * 
+   * @param logger
+   * @param context
+   * @param targetClass
+   * @param localeMap
+   * @param locale
+   * @return class name of the generated class, or null if failed
+   */
+  private String processChildLocale(TreeLogger logger,
+      GeneratorContext context, JClassType targetClass,
+      Map<String, Set<GwtLocale>> localeMap, GwtLocale locale) {
+    String generatedClass = generateLocaleTree(logger, context, targetClass,
+        locale);
     if (generatedClass == null) {
       logger.log(TreeLogger.ERROR, "Failed to generate "
           + targetClass.getQualifiedSourceName() + " in locale "
@@ -384,84 +612,105 @@
   }
 
   /**
-   * Backslash-escape any double quotes in the supplied string.
+   * Writes a loadCurrencyMap method for the current locale, based on its
+   * currency data and its superclass (if any). As currencies are included in
+   * this method, their names are added to {@code nameMap} for later use.
    * 
-   * @param str string to quote
-   * @return string with double quotes backslash-escaped.
+   * If no new currency data is added for this locale over its superclass, the
+   * method is omitted entirely.
+   * 
+   * @param allCurrencyData map of currency codes to currency data for the
+   *          current locale, including all inherited currencies data
+   * @param className name of the class we are generating
+   * @param writer SourceWriter instance to use for writing the class
+   * @param currencies array of valid currency names in the order they should be
+   *          listed
    */
-  private String quote(String str) {
-    return str.replace("\"", "\\\"");
+  private void writeCurrencyMethod(String className, SourceWriter writer,
+      String[] currencies, Map<String, CurrencyInfo> allCurrencyData) {
+    boolean needHeader = true;
+    for (String currencyCode : currencies) {
+      CurrencyInfo currencyInfo = allCurrencyData.get(currencyCode);
+      if (currencyInfo.isObsolete()) {
+        continue;
+      }
+      if (needHeader) {
+        needHeader = false;
+        writer.println();
+        writer.println("private void loadSuperCurrencyMap() {");
+        writer.println("  super.loadCurrencyMap();");
+        writer.println("}");
+        writer.println();
+        writer.println("@Override");
+        writer.println("protected native void loadCurrencyMap() /*-{");
+        writer.indent();
+        writer.println("this.@com.google.gwt.i18n.client.impl." + className
+            + "::loadSuperCurrencyMap()();");
+        writer.println("this.@com.google.gwt.i18n.client.impl." + className
+            + "::overrideCurrencyMap(Lcom/google/gwt/core/client/"
+            + "JavaScriptObject;)({");
+        writer.indent();
+      }
+      writer.println("// " + currencyInfo.getDisplayName());
+      writer.println("\"" + quote(currencyCode) + "\": "
+          + currencyInfo.getJson() + ",");
+    }
+    if (!needHeader) {
+      writer.outdent();
+      writer.println("});");
+      writer.outdent();
+      writer.println("}-*/;");
+    }
   }
 
   /**
-   * Load a single localized properties file, adding to an existing
-   * LocalizedProperties object.
+   * Writes a loadNamesMap method for the current locale, based on its the
+   * supplied names map and its superclass (if any).
    * 
-   * @param logger TreeLogger instance
-   * @param classLoader class loader to use to find property file
-   * @param propFile property file name
-   * @param props existing LocalizedProperties object to add to
-   * @return true if the properties were successfully read
-   * @throws UnableToCompleteException if an error occurs reading the file
+   * If no new names are added for this locale over its superclass, the method
+   * is omitted entirely.
+   * 
+   * @param className name of the class we are generating
+   * @param writer SourceWriter instance to use for writing the class
+   * @param currencies array of valid currency names in the order they should be
+   *          listed
    */
-  private boolean readProperties(TreeLogger logger, ClassLoader classLoader,
-      String propFile, LocalizedProperties props)
-      throws UnableToCompleteException {
-    propFile += ".properties";
-    InputStream str = null;
-    try {
-      str = classLoader.getResourceAsStream(propFile);
-      if (str != null) {
-        props.load(str, "UTF-8");
-        return true;
+  private void writeNamesMethod(String className, SourceWriter writer,
+      String[] currencies, Map<String, CurrencyInfo> allCurrencyData) {
+    boolean needHeader = true;
+    for (String currencyCode : currencies) {
+      CurrencyInfo currencyInfo = allCurrencyData.get(currencyCode);
+      if (currencyInfo.isObsolete()) {
+        continue;
       }
-    } catch (UnsupportedEncodingException e) {
-      // UTF-8 should always be defined
-      logger.log(TreeLogger.ERROR, "UTF-8 encoding is not defined", e);
-      throw new UnableToCompleteException();
-    } catch (IOException e) {
-      logger.log(TreeLogger.ERROR, "Exception reading " + propFile, e);
-      throw new UnableToCompleteException();
-    } finally {
-      if (str != null) {
-        try {
-          str.close();
-        } catch (IOException e) {
-          logger.log(TreeLogger.ERROR, "Exception closing " + propFile, e);
-          throw new UnableToCompleteException();
+      String displayName = currencyInfo.getDisplayName();
+      if (displayName != null && !currencyCode.equals(displayName)) {
+        if (needHeader) {
+          needHeader = false;
+          writer.println();
+          writer.println("private void loadSuperNamesMap() {");
+          writer.println("  super.loadNamesMap();");
+          writer.println("}");
+          writer.println();
+          writer.println("@Override");
+          writer.println("protected native void loadNamesMap() /*-{");
+          writer.indent();
+          writer.println("this.@com.google.gwt.i18n.client.impl." + className
+              + "::loadSuperNamesMap()();");
+          writer.println("this.@com.google.gwt.i18n.client.impl." + className
+              + "::overrideNamesMap(Lcom/google/gwt/core/"
+              + "client/JavaScriptObject;)({");
+          writer.indent();
         }
+        writer.println("\"" + quote(currencyCode) + "\": \""
+            + quote(displayName) + "\",");
       }
     }
-    return false;
-  }
-
-  /**
-   * Load a chain of localized properties files, starting with the default and
-   * adding locale components so inheritance is properly recognized.
-   * 
-   * @param logger TreeLogger instance
-   * @param propFilePrefix property file name prefix; locale is added to it
-   * @return a LocalizedProperties object containing all properties
-   * @throws UnableToCompleteException if an error occurs reading the file
-   */
-  private LocalizedProperties readProperties(TreeLogger logger,
-      String propFilePrefix, GwtLocale locale, GwtLocale[] foundLocale)
-      throws UnableToCompleteException {
-    LocalizedProperties props = new LocalizedProperties();
-    ClassLoader classLoader = getClass().getClassLoader();
-    List<GwtLocale> searchList = locale.getCompleteSearchList();
-    GwtLocale lastFound = LocaleUtils.getLocaleFactory().fromString(null);
-    for (int i = searchList.size(); i-- > 0; ) {
-      GwtLocale search = searchList.get(i);
-      String propFile = propFilePrefix;
-      if (!search.isDefault()) {
-        propFile += "_" + search.getAsString();
-      }
-      if (readProperties(logger, classLoader, propFile, props)) {
-        lastFound = search;
-      }
+    if (!needHeader) {
+      writer.outdent();
+      writer.println("});");
+      writer.outdent();
+      writer.println("}-*/;");
     }
-    foundLocale[0] = lastFound;
-    return props;
   }
 }
diff --git a/user/src/com/google/gwt/i18n/server/GwtLocaleImpl.java b/user/src/com/google/gwt/i18n/server/GwtLocaleImpl.java
index 448512b..01a89ca 100644
--- a/user/src/com/google/gwt/i18n/server/GwtLocaleImpl.java
+++ b/user/src/com/google/gwt/i18n/server/GwtLocaleImpl.java
@@ -37,6 +37,42 @@
   // the property provider to handle inheritance there.
 
   /**
+   * Maps deprecated language codes to the canonical code.  Strings are always
+   * in pairs, with the first being the canonical code and the second being
+   * a deprecated code which maps to it.
+   * 
+   * Source: http://www.loc.gov/standards/iso639-2/php/code_changes.php
+   * 
+   * TODO: consider building maps if this list grows much.
+   */
+  private static final String[] deprecatedLanguages = new String[] {
+    "he", "iw",   // Hebrew
+    "id", "in",   // Indonesian 
+    "jv", "jw",   // Javanese, typo in original publication 
+    "ro", "mo",   // Moldovian
+    "yi", "ji",   // Yiddish
+  };
+
+  /**
+   * Maps deprecated region codes to the canonical code.  Strings are always
+   * in pairs, with the first being the canonical code and the second being
+   * a deprecated code which maps to it.
+   * 
+   * Note that any mappings which split an old code into multiple new codes
+   * cannot be done automatically (such as cs -> rs/me) -- perhaps we could
+   * have a way of flagging region codes which are no longer valid and allow
+   * an appropriate warning message.
+   * 
+   * Source: http://en.wikipedia.org/wiki/ISO_3166-1
+   * 
+   * TODO: consider building maps if this list grows much.
+   */
+  private static final String[] deprecatedRegions = new String[] {
+    // East Timor - http://www.iso.org/iso/newsletter_v-5_east_timor.pdf
+    "TL", "TP",
+  };
+
+  /**
    * Add in the missing locale of a deprecated pair.
    * 
    * @param factory GwtLocaleFactory to create new instances with
@@ -45,36 +81,18 @@
    */
   private static void addDeprecatedPairs(GwtLocaleFactory factory,
       GwtLocale locale, List<GwtLocale> aliases) {
-    if ("he".equals(locale.getLanguage())) {
-      aliases.add(factory.fromComponents("iw", locale.getScript(),
-          locale.getRegion(), locale.getVariant()));
-    } else if ("iw".equals(locale.getLanguage())) {
-      aliases.add(factory.fromComponents("he", locale.getScript(),
-          locale.getRegion(), locale.getVariant()));
-    } else if ("id".equals(locale.getLanguage())) {
-      aliases.add(factory.fromComponents("in", locale.getScript(),
-          locale.getRegion(), locale.getVariant()));
-    } else if ("in".equals(locale.getLanguage())) {
-      aliases.add(factory.fromComponents("id", locale.getScript(),
-          locale.getRegion(), locale.getVariant()));
-    } else if ("mo".equals(locale.getLanguage())) {
-      aliases.add(factory.fromComponents("ro", locale.getScript(),
-          locale.getRegion(), locale.getVariant()));
-    } else if ("ro".equals(locale.getLanguage())) {
-      aliases.add(factory.fromComponents("mo", locale.getScript(),
-          locale.getRegion(), locale.getVariant()));
-    } else if ("jv".equals(locale.getLanguage())) {
-      aliases.add(factory.fromComponents("jw", locale.getScript(),
-          locale.getRegion(), locale.getVariant()));
-    } else if ("jw".equals(locale.getLanguage())) {
-      aliases.add(factory.fromComponents("jv", locale.getScript(),
-          locale.getRegion(), locale.getVariant()));
-    } else if ("ji".equals(locale.getLanguage())) {
-      aliases.add(factory.fromComponents("yi", locale.getScript(),
-          locale.getRegion(), locale.getVariant()));
-    } else if ("yi".equals(locale.getLanguage())) {
-      aliases.add(factory.fromComponents("ji", locale.getScript(),
-          locale.getRegion(), locale.getVariant()));
+    int n = deprecatedLanguages.length;
+    for (int i = 0; i < n; i += 2) {
+      if (deprecatedLanguages[i].equals(locale.getLanguage())) {
+        aliases.add(factory.fromComponents(deprecatedLanguages[i + 1],
+            locale.getScript(), locale.getRegion(), locale.getVariant()));
+        break;
+      }
+      if (deprecatedLanguages[i + 1].equals(locale.getLanguage())) {
+        aliases.add(factory.fromComponents(deprecatedLanguages[i],
+            locale.getScript(), locale.getRegion(), locale.getVariant()));
+        break;
+      }
     }
   }
 
@@ -159,10 +177,12 @@
         }
       }
     } else if ("no".equals(language)) {
-      if ("BOKMAL".equals(variant)) {
+      if (variant == null || "BOKMAL".equals(variant)) {
         aliases.add(factory.fromComponents("nb", script, region, null));
+        aliases.add(factory.fromComponents("no-bok", script, region, null));
       } else if ("NYNORSK".equals(variant)) {
         aliases.add(factory.fromComponents("nn", script, region, null));
+        aliases.add(factory.fromComponents("no-nyn", script, region, null));
       }
     } else if ("nb".equals(language)) {
       aliases.add(factory.fromComponents("no", script, region, "BOKMAL"));
@@ -262,7 +282,9 @@
     // TODO(jat): more locale aliases? better way to encode them?
     if (cachedAliases == null) {
       cachedAliases = new ArrayList<GwtLocale>();
+      GwtLocale canonicalForm = getCanonicalForm();
       Set<GwtLocale> seen = new HashSet<GwtLocale>();
+      cachedAliases.add(canonicalForm);
       ArrayList<GwtLocale> nextGroup = new ArrayList<GwtLocale>();
       nextGroup.add(this);
       // Account for default script
@@ -283,7 +305,9 @@
             continue;
           }
           seen.add(locale);
-          cachedAliases.add(locale);
+          if (!locale.equals(canonicalForm)) {
+            cachedAliases.add(locale);
+          }
           addDeprecatedPairs(factory, locale, nextGroup);
           addSpecialAliases(factory, locale, nextGroup);
         }
@@ -312,6 +336,77 @@
     return buf.toString();
   }
 
+  /**
+   * Returns this locale in canonical form:
+   * <ul>
+   *  <li>Deprecated language/region tags are replaced with official versions
+   *  <li>
+   * </ul>
+   * 
+   * @return GwtLocale instance 
+   */
+  public GwtLocale getCanonicalForm() {
+    String canonLanguage = language;
+    String canonScript = script;
+    String canonRegion = region;
+    String canonVariant = variant;
+    // Handle deprecated language codes
+    int n = deprecatedLanguages.length;
+    for (int i = 0; i < n; i += 2) {
+      if (deprecatedLanguages[i + 1].equals(canonLanguage)) {
+        canonLanguage = deprecatedLanguages[i];
+        break;
+      }
+    }
+    // Handle deprecated region codes
+    n = deprecatedRegions.length;
+    for (int i = 0; i < n; i += 2) {
+      if (deprecatedRegions[i + 1].equals(canonRegion)) {
+        canonRegion = deprecatedRegions[i];
+        break;
+      }
+    }
+    // Special-case Chinese default scripts
+    if ("zh".equals(canonLanguage)) {
+      if (canonRegion != null) {
+        if ("CN".equals(canonRegion) && "Hans".equals(canonScript)) {
+          canonScript = null;
+        } else if ("TW".equals(canonRegion) && "Hant".equals(canonScript)) {
+          canonScript = null;
+        }
+      } else if ("Hans".equals(canonScript)) {
+        canonRegion = "CN";
+        canonScript = null;
+      } else if ("Hant".equals(canonScript)) {
+        canonRegion = "TW";
+        canonScript = null;
+      }
+    }
+    // Special-case no->nb/nn split
+    if ("no-bok".equals(canonLanguage)) {
+      canonLanguage = "nb";
+      canonVariant = null;
+    } else if ("no-nyn".equals(canonLanguage)) {
+      canonLanguage = "nn";
+      canonVariant = null;
+    } else if ("no".equals(canonLanguage)) {
+      if (canonVariant == null || "BOKMAL".equals(canonVariant)) {
+        canonLanguage = "nb";
+        canonVariant = null;
+      } else if ("NYNORSK".equals(canonVariant)) {
+        canonLanguage = "nn";
+        canonVariant = null;
+      }
+    }
+    // Remove any default script for the language
+    if (canonScript != null && canonScript.equals(
+        DefaultLanguageScripts.getDefaultScript(canonLanguage))) {
+      canonScript = null;
+    }
+    return factory.fromComponents(canonLanguage, canonScript, canonRegion,
+        canonVariant);
+  }
+
   public List<GwtLocale> getCompleteSearchList() {
     // TODO(jat): get zh_Hant to come before zh in search list for zh_TW
     if (cachedSearchList == null) {
diff --git a/user/src/com/google/gwt/i18n/shared/GwtLocale.java b/user/src/com/google/gwt/i18n/shared/GwtLocale.java
index 35ca036..8dd7bb4 100644
--- a/user/src/com/google/gwt/i18n/shared/GwtLocale.java
+++ b/user/src/com/google/gwt/i18n/shared/GwtLocale.java
@@ -32,8 +32,8 @@
   int compareTo(GwtLocale o);
 
   /**
-   * Return the list of aliases for this locale.  The current locale is always
-   * first on the list.
+   * Return the list of aliases for this locale.  The canonical form of the
+   * current locale is always first on the list.
    * 
    * Language/region codes have changed over time, so some systems continue to
    * use the older codes.  Aliases allow GWT to use the official Unicode CLDR
@@ -43,8 +43,41 @@
    */
   List<GwtLocale> getAliases();
   
+  /**
+   * Return the locale as a fixed-format string suitable for use in searching
+   * for localized resources.  The format is language_Script_REGION_VARIANT,
+   * where language is a 2-8 letter code (possibly with 3-letter extensions),
+   * script is a 4-letter code with an initial capital letter, region is a
+   * 2-character country code or a 3-digit region code, and variant is a 5-8
+   * character (may be 4 if the first character is numeric) code.  If a
+   * component is missing, its preceding _ is also omitted.  If this is the
+   * default locale, the empty string will be returned.
+   * 
+   * @return String representing locale
+   */
   String getAsString();
 
+  /**
+   * Returns this locale in canonical form.
+   * <ul>
+   *  <li>Deprecated language/region tags are replaced with official versions
+   *  <li>Default scripts are removed (including region-specific defaults for
+   *      Chinese)
+   *  <li>no/nb/nn are normalized
+   *  <li>Default region for zh_Hans and zh_Hant if none specified
+   * </ul>
+   * 
+   * @return GwtLocale instance 
+   */
+  GwtLocale getCanonicalForm();
+
+  /**
+   * Return the complete list of locales to search for the current locale.
+   * This list will always start with the canonical form of this locale, and
+   * end with "default", and include all appropriate aliases along the way.
+   * 
+   * @return search list
+   */
   List<GwtLocale> getCompleteSearchList();
   
   /**
@@ -56,20 +89,44 @@
    */
   List<GwtLocale> getInheritanceChain();
   
+  /**
+   * @return the language portion of the locale, or null if none.
+   */
   String getLanguage();
 
+  /**
+   * @return the language portion of the locale, or the empty string if none.
+   */
   String getLanguageNotNull();
 
+  /**
+   * @return the region portion of the locale, or null if none.
+   */
   String getRegion();
   
+  /**
+   * @return the region portion of the locale, or the empty string if none.
+   */
   String getRegionNotNull();
 
+  /**
+   * @return the script portion of the locale, or null if none.
+   */
   String getScript();
   
+  /**
+   * @return the script portion of the locale, or the empty string if none.
+   */
   String getScriptNotNull();
   
+  /**
+   * @return the variant portion of the locale, or null if none.
+   */
   String getVariant();
   
+  /**
+   * @return the variant portion of the locale, or the empty string if none.
+   */
   String getVariantNotNull();
 
   /**
@@ -82,8 +139,14 @@
    */
   boolean inheritsFrom(GwtLocale parent);
 
+  /**
+   * @return true if this is the default or root locale.
+   */
   boolean isDefault();
 
+  /**
+   * @return a human readable string -- "default" or the same as getAsString().
+   */
   String toString();
 
   /**
diff --git a/user/test/com/google/gwt/i18n/I18NTest_es_AR_runtime.gwt.xml b/user/test/com/google/gwt/i18n/I18NTest_es_AR_runtime.gwt.xml
new file mode 100644
index 0000000..9cddf0b
--- /dev/null
+++ b/user/test/com/google/gwt/i18n/I18NTest_es_AR_runtime.gwt.xml
@@ -0,0 +1,25 @@
+<!--                                                                        -->
+<!-- Copyright 2009 Google Inc.                                             -->
+<!-- Licensed under the Apache License, Version 2.0 (the "License"); you    -->
+<!-- may not use this file except in compliance with the License. You may   -->
+<!-- may obtain a copy of the License at                                    -->
+<!--                                                                        -->
+<!-- http://www.apache.org/licenses/LICENSE-2.0                             -->
+<!--                                                                        -->
+<!-- Unless required by applicable law or agreed to in writing, software    -->
+<!-- distributed under the License is distributed on an "AS IS" BASIS,      -->
+<!-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or        -->
+<!-- implied. License for the specific language governing permissions and   -->
+<!-- limitations under the License.                                         -->
+
+<module>
+	<!-- Inherit the JUnit support -->
+	<inherits name='com.google.gwt.junit.JUnit'/>
+	<inherits name = 'com.google.gwt.i18n.I18N'/>
+	<!-- Include client-side source for the test cases -->
+	<source path="client"/>
+	<extend-property name="locale" values="es_419"/>
+	<set-property name = "locale" value = "es_419"/>
+	<set-configuration-property name="runtime.locales" value="es_AR,es_MX"/>
+	<public path="public_es_AR"/>
+</module>
diff --git a/user/test/com/google/gwt/i18n/client/I18N_es_AR_RuntimeTest.java b/user/test/com/google/gwt/i18n/client/I18N_es_AR_RuntimeTest.java
new file mode 100644
index 0000000..f3dfe62
--- /dev/null
+++ b/user/test/com/google/gwt/i18n/client/I18N_es_AR_RuntimeTest.java
@@ -0,0 +1,104 @@
+/*
+ * Copyright 2007 Google Inc.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.i18n.client;
+
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.i18n.client.I18N_es_MX_Test.MyConstants;
+import com.google.gwt.i18n.client.I18N_es_MX_Test.MyMessages;
+import com.google.gwt.i18n.client.impl.CurrencyData;
+import com.google.gwt.i18n.client.impl.CurrencyList;
+import com.google.gwt.junit.client.GWTTestCase;
+
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * Tests regional inheritance for es_AR.
+ */
+public class I18N_es_AR_RuntimeTest extends GWTTestCase {
+
+  @Override
+  public String getModuleName() {
+    return "com.google.gwt.i18n.I18NTest_es_AR_runtime";
+  }
+  
+  public void testAvailableLocales() {
+    String[] locales = LocaleInfo.getAvailableLocaleNames();
+    Set<String> localeSet = new HashSet<String>();
+    List<String> localeList = Arrays.asList(locales);
+    localeSet.addAll(localeList);
+    List<String> expectedList = Arrays.asList("default", "es_419", "es_AR",
+        "es_MX");
+    assertEquals(expectedList.size(), localeSet.size());
+    localeSet.removeAll(expectedList);
+    assertEquals(0, localeSet.size());
+  }
+
+  public void testCurrencyNames() {
+    assertEquals("Peso Argentino", CurrencyList.get().lookupName("ARS"));
+    assertEquals("peso mexicano", CurrencyList.get().lookupName("MXN"));
+    assertEquals("dólar estadounidense", CurrencyList.get().lookupName("USD"));
+  }
+
+  public void testDefaultCurrency() {
+    CurrencyData data = CurrencyList.get().getDefault();
+    assertEquals("ARS", data.getCurrencyCode());
+    assertEquals("$", data.getCurrencySymbol());
+    assertEquals(2, data.getDefaultFractionDigits());
+  }
+  
+  public void testOtherCurrency() {
+    CurrencyData ars = CurrencyList.get().lookup("ARS");
+    assertEquals("ARS", ars.getCurrencyCode());
+    assertEquals("$", ars.getCurrencySymbol());
+    assertEquals(2, ars.getDefaultFractionDigits());
+    CurrencyData data = CurrencyList.get().lookup("MXN");
+    assertEquals("MXN", data.getCurrencyCode());
+    assertEquals("MEX$", data.getCurrencySymbol());
+    assertEquals(2, data.getDefaultFractionDigits());
+    CurrencyData usd = CurrencyList.get().lookup("USD");
+    assertEquals("USD", usd.getCurrencyCode());
+    assertEquals("US$", usd.getCurrencySymbol());
+    assertEquals(2, usd.getDefaultFractionDigits());
+    boolean found = false;
+    for (CurrencyData it : CurrencyList.get()) {
+      if ("USD".equals(it.getCurrencyCode())) {
+        assertEquals("US$", it.getCurrencySymbol());
+        assertEquals(2, it.getDefaultFractionDigits());
+        found = true;
+        break;
+      }
+    }
+    assertTrue("Did not find USD in iterator", found);
+  }
+
+  public void testRegionalInheritance() {
+    MyMessages msg = GWT.create(MyMessages.class);
+    assertEquals("es_419", msg.getSourceLocale());
+    MyConstants cst = GWT.create(MyConstants.class);
+    // Since our copile-time locale is es_419 (Latin America), we do
+    // not get es_019 (Central America) in the inheritance chain for
+    // es_AR as only the compile-time locales are used for translation
+    // inheritance.
+    assertEquals("default", cst.getSourceLocale());
+  }
+  
+  public void testRuntimeLocale() {
+    assertEquals("es_AR", LocaleInfo.getCurrentLocale().getLocaleName());
+  }
+}
diff --git a/user/test/com/google/gwt/i18n/client/I18N_es_MX_RuntimeTest.java b/user/test/com/google/gwt/i18n/client/I18N_es_MX_RuntimeTest.java
index 18079e1..2cba95f 100644
--- a/user/test/com/google/gwt/i18n/client/I18N_es_MX_RuntimeTest.java
+++ b/user/test/com/google/gwt/i18n/client/I18N_es_MX_RuntimeTest.java
@@ -49,12 +49,43 @@
     assertEquals(0, localeSet.size());
   }
 
+  public void testCurrencyNames() {
+    assertEquals("peso argentino", CurrencyList.get().lookupName("ARS"));
+    assertEquals("peso mexicano", CurrencyList.get().lookupName("MXN"));
+    assertEquals("dólar estadounidense", CurrencyList.get().lookupName("USD"));
+  }
+
   public void testDefaultCurrency() {
     CurrencyData data = CurrencyList.get().getDefault();
     assertEquals("MXN", data.getCurrencyCode());
     assertEquals("$", data.getCurrencySymbol());
     assertEquals(2, data.getDefaultFractionDigits());
   }
+  
+  public void testOtherCurrency() {
+    CurrencyData ars = CurrencyList.get().lookup("ARS");
+    assertEquals("ARS", ars.getCurrencyCode());
+    assertEquals("Arg$", ars.getCurrencySymbol());
+    assertEquals(2, ars.getDefaultFractionDigits());
+    CurrencyData data = CurrencyList.get().lookup("MXN");
+    assertEquals("MXN", data.getCurrencyCode());
+    assertEquals("$", data.getCurrencySymbol());
+    assertEquals(2, data.getDefaultFractionDigits());
+    CurrencyData usd = CurrencyList.get().lookup("USD");
+    assertEquals("USD", usd.getCurrencyCode());
+    assertEquals("US$", usd.getCurrencySymbol());
+    assertEquals(2, usd.getDefaultFractionDigits());
+    boolean found = false;
+    for (CurrencyData it : CurrencyList.get()) {
+      if ("USD".equals(it.getCurrencyCode())) {
+        assertEquals("US$", it.getCurrencySymbol());
+        assertEquals(2, it.getDefaultFractionDigits());
+        found = true;
+        break;
+      }
+    }
+    assertTrue("Did not find USD in iterator", found);
+  }
 
   public void testRegionalInheritance() {
     MyMessages msg = GWT.create(MyMessages.class);
diff --git a/user/test/com/google/gwt/i18n/public_es_AR/junit.html b/user/test/com/google/gwt/i18n/public_es_AR/junit.html
new file mode 100644
index 0000000..b316a47
--- /dev/null
+++ b/user/test/com/google/gwt/i18n/public_es_AR/junit.html
@@ -0,0 +1,67 @@
+<!--
+Copyright 2008 Google Inc.
+
+Licensed under the Apache License, Version 2.0 (the "License"); you may not
+use this file except in compliance with the License. You may obtain a copy of
+the License at
+
+http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+License for the specific language governing permissions and limitations under
+the License.
+-->
+<html>
+<head>
+<meta name='gwt:onLoadErrorFn' content='junitOnLoadErrorFn'>
+<meta name='gwt:onPropertyErrorFn' content='junitOnPropertyErrorFn'>
+</head>
+<body>
+<script language='javascript'>
+<!--
+// -- BEGIN CHANGE FROM junit/public/junit.html
+// Set the runtime locale for this test
+// TODO(jat): find a better way to do this that doesn't require duplcating
+//     junit.html for each different runtime locale.
+window['__gwt_Locale'] = 'es_AR';
+// -- END CHANGE FROM junit/public/junit.html
+
+function junitOnLoadErrorFn(moduleName) {
+  junitError('Failed to load module "' + moduleName +
+    '".\nPlease see the log for details.');
+}
+
+function junitOnPropertyErrorFn(propName, allowedValues, badValue) {
+  var msg = 'While attempting to load the module, property "' + propName;
+  if (badValue != null) {
+    msg += '" was set to the unexpected value "' + badValue + '"';
+  } else {
+    msg += '" was not specified';
+  }
+  msg += 'Allowed values: ' + allowedValues;
+  junitError(msg);
+}
+
+function junitError(msg) {
+  var xmlHttpRequest = window.XMLHttpRequest ? new XMLHttpRequest() : new ActiveXObject("Microsoft.XMLHTTP");
+  xmlHttpRequest.open('POST', 'junithost/loadError', true);
+  xmlHttpRequest.setRequestHeader('Content-Type', 'text/x-gwt-rpc; charset=utf-8');
+  xmlHttpRequest.send(msg);
+}
+
+function loadSelectionScript() {
+  var moduleName = document.location.href;
+  var pos = moduleName.lastIndexOf('/');
+  moduleName = moduleName.substr(0, pos);
+  pos = moduleName.lastIndexOf('/');
+  moduleName = moduleName.substr(pos + 1);
+  document.write("<script language='javascript' src='" + moduleName + ".nocache.js'></script>");
+}
+loadSelectionScript();
+-->
+</script>
+<iframe src="javascript:''" id='__gwt_historyFrame' style='position:absolute;width:0;height:0;border:0'></iframe>
+</body>
+</html>
diff --git a/user/test/com/google/gwt/i18n/server/GwtLocaleTest.java b/user/test/com/google/gwt/i18n/server/GwtLocaleTest.java
index 3bf1298..ee2eaec 100644
--- a/user/test/com/google/gwt/i18n/server/GwtLocaleTest.java
+++ b/user/test/com/google/gwt/i18n/server/GwtLocaleTest.java
@@ -38,12 +38,13 @@
     assertContainsAndGetPosition(aliases, factory.fromString("zh_Hans_CN"));
     GwtLocale zhHant = factory.fromString("zh_Hant");
     aliases = zhHant.getAliases();
-    assertEquals(aliases.get(0), zhHant);
+    assertEquals(aliases.get(0), factory.fromString("zh_TW"));
     assertContainsAndGetPosition(aliases, factory.fromString("zh_Hant_TW"));
     GwtLocale zhHans = factory.fromString("zh_Hans");
     aliases = zhHans.getAliases();
-    assertEquals(aliases.get(0), zhHans);
+    assertEquals(aliases.get(0), zhCN);
     assertContainsAndGetPosition(aliases, factory.fromString("zh_Hans_CN"));
+    assertContainsAndGetPosition(aliases, zhHans);
     GwtLocale en = factory.fromString("en");
     aliases = en.getAliases();
     assertEquals(aliases.get(0), en);
@@ -56,7 +57,11 @@
     GwtLocale pt = factory.fromString("pt");
     aliases = pt.getAliases();
     assertContainsAndGetPosition(aliases, factory.fromString("pt_BR"));
+    GwtLocale iw = factory.fromString("iw");
+    aliases = iw.getAliases();
     GwtLocale he = factory.fromString("he");
+    assertEquals(aliases.get(0), he);
+    assertContainsAndGetPosition(aliases, factory.fromString("iw_Hebr"));
     aliases = he.getAliases();
     assertContainsAndGetPosition(aliases, factory.fromString("iw_Hebr"));
     GwtLocale id = factory.fromString("id");