Abstract out a CurrencyData interface and make it available to NumberFormat,
in addition to just supplying a currency code.  This allows applications to
use exactly the same currency data as used on the server for consistent
formatting.  Also makes CurrencyList a public API as it is the primary way
to get CurrencyData instances.

Patch by: jat
Review by: andreasst


git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@7294 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/user/src/com/google/gwt/i18n/I18N.gwt.xml b/user/src/com/google/gwt/i18n/I18N.gwt.xml
index 8829121..8f9f256 100644
--- a/user/src/com/google/gwt/i18n/I18N.gwt.xml
+++ b/user/src/com/google/gwt/i18n/I18N.gwt.xml
@@ -81,7 +81,7 @@
     <when-type-is class="com.google.gwt.i18n.client.impl.LocaleInfoImpl" />
   </generate-with>
   <generate-with class="com.google.gwt.i18n.rebind.CurrencyListGenerator">
-    <when-type-is class="com.google.gwt.i18n.client.impl.CurrencyList" />
+    <when-type-is class="com.google.gwt.i18n.client.CurrencyList" />
   </generate-with>
   
   <!--
diff --git a/user/src/com/google/gwt/i18n/client/CurrencyData.java b/user/src/com/google/gwt/i18n/client/CurrencyData.java
new file mode 100644
index 0000000..e4eda01
--- /dev/null
+++ b/user/src/com/google/gwt/i18n/client/CurrencyData.java
@@ -0,0 +1,78 @@
+/*
+ * 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 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;
+
+/**
+ * Information about a currency.
+ */
+public interface CurrencyData {
+
+  /**
+   * @return the ISO4217 code for this currency
+   */
+  String getCurrencyCode();
+
+  /**
+   * @return the default symbol to use for this currency
+   */
+  String getCurrencySymbol();
+
+  /**
+   * @return the default number of decimal positions for this currency
+   */
+  int getDefaultFractionDigits();
+
+  /**
+   * @return the default symbol to use for this currency, intended to be
+   * recognizable in most locales.  If such a symbol is not available, it is
+   * acceptable to return the same value as {@link #getCurrencySymbol()}.
+   */
+  String getPortableCurrencySymbol();
+
+  /**
+   * @return true if this currency is deprecated and should not be returned by
+   * default in currency lists.
+   */
+  boolean isDeprecated();
+
+  /**
+   * @return true if there should always be a space between the currency symbol
+   * and the number, false if there should be no space.  Ignored unless
+   * {@link #isSpacingFixed()} returns true.
+   */
+  boolean isSpaceForced();
+
+  /**
+   * @return true if the spacing between the currency symbol and the number is
+   * fixed regardless of locale defaults.  In this case, spacing will be
+   * determined by {@link #isSpaceForced()}.
+   */
+  boolean isSpacingFixed();
+
+  /**
+   * @return true if the position of the currency symbol relative to the number
+   * is fixed regardless of locale defaults.  In this case, the position will be
+   * determined by {@link #isSymbolPrefix()}.
+   */
+  boolean isSymbolPositionFixed();
+
+  /**
+   * @return true if the currency symbol should go before the number, false if
+   * it should go after the number.  This is ignored unless
+   * {@link #isSymbolPositionFixed()} is true.
+   */
+  boolean isSymbolPrefix();
+}
diff --git a/user/src/com/google/gwt/i18n/client/impl/CurrencyList.java b/user/src/com/google/gwt/i18n/client/CurrencyList.java
similarity index 85%
rename from user/src/com/google/gwt/i18n/client/impl/CurrencyList.java
rename to user/src/com/google/gwt/i18n/client/CurrencyList.java
index b28762f..8fb5980 100644
--- a/user/src/com/google/gwt/i18n/client/impl/CurrencyList.java
+++ b/user/src/com/google/gwt/i18n/client/CurrencyList.java
@@ -14,7 +14,7 @@
  * the License.
  */
 
-package com.google.gwt.i18n.client.impl;
+package com.google.gwt.i18n.client;
 
 import com.google.gwt.core.client.GWT;
 import com.google.gwt.core.client.JavaScriptObject;
@@ -51,19 +51,16 @@
   }
 
   /**
-   * JS Object which contains a map of currency codes to CurrencyData
-   * objects.  Each currency code is prefixed with a ':' to allow
-   * enumeration to find only the values we added, and not values
-   * which various JSVMs add to objects.
+   * JS Object which contains a map of currency codes to CurrencyDataImpl
+   * objects.  Each currency code is assumed to be a valid JS object key.
    */
   protected JavaScriptObject dataMap;
 
   /**
-   * JS Object which contains a map of currency codes to localized
-   * currency names.  This is kept separate from the CurrencyData
-   * map above so that the names can be completely removed by the
-   * compiler if they are not used.  As iteration is not required,
-   * no prefix is added to currency codes in this map.
+   * JS Object which contains a map of currency codes to localized currency
+   * names. This is kept separate from {@link #dataMap} above so that the names
+   * can be completely removed by the compiler if they are not used. Each
+   * currency code is assumed to be a valid JS object key.
    */
   protected JavaScriptObject namesMap;
 
@@ -159,7 +156,7 @@
    * @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.CurrencyList::dataMap[code];
   }-*/;
 
   /**
@@ -169,7 +166,7 @@
    * @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;
+    return this.@com.google.gwt.i18n.client.CurrencyList::namesMap[code] || code;
   }-*/;
 
   /**
@@ -178,7 +175,7 @@
    * Generated implementations override this method.
    */
   protected native void loadCurrencyMap() /*-{
-    this.@com.google.gwt.i18n.client.impl.CurrencyList::dataMap = {
+    this.@com.google.gwt.i18n.client.CurrencyList::dataMap = {
         "USD": [ "USD", "$", 2 ],
         "EUR": [ "EUR", "€", 2 ],
         "GBP": [ "GBP", "UK£", 2 ],
@@ -192,7 +189,7 @@
    * Generated implementations override this method.
    */
   protected native void loadNamesMap() /*-{
-    this.@com.google.gwt.i18n.client.impl.CurrencyList::namesMap = {
+    this.@com.google.gwt.i18n.client.CurrencyList::namesMap = {
         "USD": "US Dollar",
         "EUR": "Euro",
         "GBP": "British Pound Sterling",
@@ -208,7 +205,7 @@
    * @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;
+    var map = this.@com.google.gwt.i18n.client.CurrencyList::dataMap;
     for (var key in override) {
       if (override.hasOwnProperty(key)) {
         map[key] = override[key];
@@ -224,7 +221,7 @@
    * @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;
+    var map = this.@com.google.gwt.i18n.client.CurrencyList::namesMap;
     for (var key in override) {
       if (override.hasOwnProperty(key)) {
         map[key] = override[key];
@@ -237,11 +234,11 @@
    */
   private native void loadCurrencyKeys(ArrayList<String> keys,
       boolean includeDeprecated) /*-{
-    var map = this.@com.google.gwt.i18n.client.impl.CurrencyList::dataMap;
+    var map = this.@com.google.gwt.i18n.client.CurrencyList::dataMap;
     for (var key in map) {
       if (map.hasOwnProperty(key)) {
         if (includeDeprecated
-            || !@com.google.gwt.i18n.client.impl.CurrencyList::isDeprecated(Lcom/google/gwt/i18n/client/impl/CurrencyData;)(map[key])) {
+            || !@com.google.gwt.i18n.client.CurrencyList::isDeprecated(Lcom/google/gwt/i18n/client/CurrencyData;)(map[key])) {
           keys.@java.util.ArrayList::add(Ljava/lang/Object;)(key);
         }
       }
diff --git a/user/src/com/google/gwt/i18n/client/DefaultCurrencyData.java b/user/src/com/google/gwt/i18n/client/DefaultCurrencyData.java
new file mode 100644
index 0000000..23f7456
--- /dev/null
+++ b/user/src/com/google/gwt/i18n/client/DefaultCurrencyData.java
@@ -0,0 +1,92 @@
+/*
+ * 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 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;
+
+/**
+ * A default {@link CurrencyData} implementation, so new methods can be added
+ * to the interface without breaking implementors if a reasonable default is
+ * available.
+ */
+public class DefaultCurrencyData implements CurrencyData {
+
+  private final String currencyCode;
+  private final String currencySymbol;
+  private final int fractionDigits;
+
+  /**
+   * Create a default default {@link CurrencyData} instance, returning {@code
+   * false} for all {@code isFoo} methods, having 2 fractional digits by
+   * default, and using the standard symbol for the portable symbol.
+   * 
+   * @param currencyCode ISO 4217 currency code
+   * @param currencySymbol symbol to use for this currency
+   */
+  public DefaultCurrencyData(String currencyCode, String currencySymbol) {
+    this(currencyCode, currencySymbol, 2);
+  }
+
+  /**
+   * Create a default default {@link CurrencyData} instance, returning {@code
+   * false} for all {@code isFoo} methods and using the standard symbol for the
+   * portable symbol.
+   * 
+   * @param currencyCode ISO 4217 currency code
+   * @param currencySymbol symbol to use for this currency
+   * @param fractionDigits default number of fraction digits
+   */
+  public DefaultCurrencyData(String currencyCode, String currencySymbol,
+      int fractionDigits) {
+    this.currencyCode = currencyCode;
+    this.currencySymbol = currencySymbol;
+    this.fractionDigits = fractionDigits;
+  }
+
+  public String getCurrencyCode() {
+    return currencyCode;
+  }
+
+  public String getCurrencySymbol() {
+    return currencySymbol;
+  }
+
+  public int getDefaultFractionDigits() {
+    return fractionDigits;
+  }
+
+  public String getPortableCurrencySymbol() {
+    return getCurrencySymbol();
+  }
+
+  public boolean isDeprecated() {
+    return false;
+  }
+
+  public boolean isSpaceForced() {
+    return false;
+  }
+
+  public boolean isSpacingFixed() {
+    return false;
+  }
+
+  public boolean isSymbolPositionFixed() {
+    return false;
+  }
+
+  public boolean isSymbolPrefix() {
+    return false;
+  }
+}
diff --git a/user/src/com/google/gwt/i18n/client/NumberFormat.java b/user/src/com/google/gwt/i18n/client/NumberFormat.java
index da7fa1f..7b2ba5c 100644
--- a/user/src/com/google/gwt/i18n/client/NumberFormat.java
+++ b/user/src/com/google/gwt/i18n/client/NumberFormat.java
@@ -17,8 +17,6 @@
 
 import com.google.gwt.core.client.GWT;
 import com.google.gwt.i18n.client.constants.NumberConstants;
-import com.google.gwt.i18n.client.impl.CurrencyData;
-import com.google.gwt.i18n.client.impl.CurrencyList;
 
 /**
  * Formats and parses numbers using locale-sensitive patterns.
@@ -370,15 +368,29 @@
    * Provides the standard currency format for the default locale using a
    * specified currency.
    * 
+   * @param currencyData currency data to use
+   * @return a <code>NumberFormat</code> capable of producing and consuming
+   *         currency format for the default locale
+   */
+  public static NumberFormat getCurrencyFormat(CurrencyData currencyData) {
+    return new NumberFormat(defaultNumberConstants.currencyPattern(),
+        currencyData, false);
+  }
+  
+  /**
+   * Provides the standard currency format for the default locale using a
+   * specified currency.
+   * 
    * @param currencyCode valid currency code, as defined in 
    *     com.google.gwt.i18n.client.constants.CurrencyCodeMapConstants.properties
    * @return a <code>NumberFormat</code> capable of producing and consuming
    *         currency format for the default locale
+   * @throws IllegalArgumentException if the currency code is unknown
    */
   public static NumberFormat getCurrencyFormat(String currencyCode) {
     // TODO(jat): consider caching values per currency code.
     return new NumberFormat(defaultNumberConstants.currencyPattern(),
-        CurrencyList.get().lookup(currencyCode), false);
+        lookupCurrency(currencyCode), false);
   }
 
   /**
@@ -413,12 +425,27 @@
    * using the specified pattern and currency code.
    * 
    * @param pattern pattern for this formatter
-   * @param currencyCode international currency code
+   * @param currencyData currency data
    * @return a NumberFormat instance
    * @throws IllegalArgumentException if the specified pattern is invalid
    */
+  public static NumberFormat getFormat(String pattern,
+      CurrencyData currencyData) {
+    return new NumberFormat(pattern, currencyData, true);
+  }
+
+  /**
+   * Gets a custom <code>NumberFormat</code> instance for the default locale
+   * using the specified pattern and currency code.
+   * 
+   * @param pattern pattern for this formatter
+   * @param currencyCode international currency code
+   * @return a NumberFormat instance
+   * @throws IllegalArgumentException if the specified pattern is invalid
+   *     or the currency code is unknown
+   */
   public static NumberFormat getFormat(String pattern, String currencyCode) {
-    return new NumberFormat(pattern, CurrencyList.get().lookup(currencyCode), true);
+    return new NumberFormat(pattern, lookupCurrency(currencyCode), true);
   }
 
   /**
@@ -483,7 +510,8 @@
    * @param orig localized NumberConstants instance
    * @return NumberConstants instance using latin digits/etc
    */
-  protected static NumberConstants createLatinNumberConstants(final NumberConstants orig) {
+  protected static NumberConstants createLatinNumberConstants(
+      final NumberConstants orig) {
     final String groupingSeparator = remapSeparator(
         orig.groupingSeparator());
     final String decimalSeparator = remapSeparator(
@@ -580,6 +608,23 @@
     return "\u00A0";
   }
 
+  /**
+   * Lookup a currency code.
+   * 
+   * @param currencyCode ISO4217 currency code
+   * @return a CurrencyData instance
+   * @throws IllegalArgumentException if the currency code is unknown
+   */
+  private static CurrencyData lookupCurrency(String currencyCode) {
+    CurrencyData currencyData = CurrencyList.get().lookup(currencyCode);
+    if (currencyData == null) {
+      throw new IllegalArgumentException("Currency code " + currencyCode
+          + " is unkown in locale "
+          + LocaleInfo.getCurrentLocale().getLocaleName());
+    }
+    return currencyData;
+  }
+
   private static native String toFixed(double d, int digits) /*-{
     return d.toFixed(digits);
   }-*/;
diff --git a/user/src/com/google/gwt/i18n/client/impl/CurrencyData.java b/user/src/com/google/gwt/i18n/client/impl/CurrencyDataImpl.java
similarity index 87%
rename from user/src/com/google/gwt/i18n/client/impl/CurrencyData.java
rename to user/src/com/google/gwt/i18n/client/impl/CurrencyDataImpl.java
index 56dc686..f5be5b7 100644
--- a/user/src/com/google/gwt/i18n/client/impl/CurrencyData.java
+++ b/user/src/com/google/gwt/i18n/client/impl/CurrencyDataImpl.java
@@ -13,10 +13,10 @@
  * License for the specific language governing permissions and limitations under
  * the License.
  */
-
 package com.google.gwt.i18n.client.impl;
 
 import com.google.gwt.core.client.JavaScriptObject;
+import com.google.gwt.i18n.client.CurrencyData;
 
 /**
  * JSO Overlay type that wraps currency data.
@@ -32,7 +32,8 @@
  *       d6:    spacing around currency symbol is based on d5
  *   3 - portable currency symbol (optional)
  */
-public final class CurrencyData extends JavaScriptObject {
+public final class CurrencyDataImpl extends JavaScriptObject
+    implements CurrencyData {
   
   /**
    * Public so CurrencyListGenerator can get to them. As usual with an impl
@@ -45,33 +46,21 @@
   public static final int SPACING_FIXED_FLAG = 64;
   public static final int DEPRECATED_FLAG = 128;
 
-  protected CurrencyData() {
+  protected CurrencyDataImpl() {
   }
 
-  /**
-   * @return the ISO4217 code for this currency
-   */
   public native String getCurrencyCode() /*-{
     return this[0];
   }-*/;
 
-  /**
-   * @return the default symbol to use for this currency
-   */
   public native String getCurrencySymbol() /*-{
     return this[1];
   }-*/;
 
-  /**
-   * @return the default number of decimal positions for this currency
-   */
   public int getDefaultFractionDigits() {
     return getFlagsAndPrecision() & PRECISION_MASK;
   }
 
-  /**
-   * @return the default symbol to use for this currency
-   */
   public native String getPortableCurrencySymbol() /*-{
     return this[3] || this[1];
   }-*/;
diff --git a/user/src/com/google/gwt/i18n/rebind/CurrencyListGenerator.java b/user/src/com/google/gwt/i18n/rebind/CurrencyListGenerator.java
index 9b82940..c045221 100644
--- a/user/src/com/google/gwt/i18n/rebind/CurrencyListGenerator.java
+++ b/user/src/com/google/gwt/i18n/rebind/CurrencyListGenerator.java
@@ -23,8 +23,8 @@
 import com.google.gwt.core.ext.typeinfo.JClassType;
 import com.google.gwt.core.ext.typeinfo.NotFoundException;
 import com.google.gwt.core.ext.typeinfo.TypeOracle;
-import com.google.gwt.i18n.client.impl.CurrencyData;
-import com.google.gwt.i18n.client.impl.CurrencyList;
+import com.google.gwt.i18n.client.CurrencyList;
+import com.google.gwt.i18n.client.impl.CurrencyDataImpl;
 import com.google.gwt.i18n.shared.GwtLocale;
 import com.google.gwt.user.rebind.ClassSourceFileComposerFactory;
 import com.google.gwt.user.rebind.SourceWriter;
@@ -135,7 +135,7 @@
       }
       int currencyFlags = currencyFractionDigits;
       if (currencyObsolete) {
-        currencyFlags |= CurrencyData.DEPRECATED_FLAG;
+        currencyFlags |= CurrencyDataImpl.DEPRECATED_FLAG;
       }
       String currencyPortableSymbol = "";
       if (extraData != null) {
@@ -148,16 +148,16 @@
         currencyPortableSymbol = extraSplit[0];
         if (extraSplit.length > 1) {
           if (extraSplit[1].contains("SymPrefix")) {
-            currencyFlags |= CurrencyData.POS_FIXED_FLAG;
+            currencyFlags |= CurrencyDataImpl.POS_FIXED_FLAG;
           } else if (extraSplit[1].contains("SymSuffix")) {
-            currencyFlags |= CurrencyData.POS_FIXED_FLAG
-                | CurrencyData.POS_SUFFIX_FLAG;
+            currencyFlags |= CurrencyDataImpl.POS_FIXED_FLAG
+                | CurrencyDataImpl.POS_SUFFIX_FLAG;
           }
           if (extraSplit[1].contains("ForceSpace")) {
-            currencyFlags |= CurrencyData.SPACING_FIXED_FLAG
-                | CurrencyData.SPACE_FORCED_FLAG;
+            currencyFlags |= CurrencyDataImpl.SPACING_FIXED_FLAG
+                | CurrencyDataImpl.SPACE_FORCED_FLAG;
           } else if (extraSplit[1].contains("ForceNoSpace")) {
-            currencyFlags |= CurrencyData.SPACING_FIXED_FLAG;
+            currencyFlags |= CurrencyDataImpl.SPACING_FIXED_FLAG;
           }
         }
         // If a non-empty override is supplied, use it for the currency
@@ -209,19 +209,21 @@
     }
   }
 
-  private static final String CURRENCY_DATA = CurrencyData.class.getCanonicalName();
+  private static final String CURRENCY_DATA = CurrencyDataImpl.class.getCanonicalName();
 
   /**
    * Prefix for properties files containing CLDR-derived currency data for each
    * locale.
    */
-  private static final String CURRENCY_DATA_PREFIX = "com/google/gwt/i18n/client/impl/cldr/CurrencyData";
+  private static final String CURRENCY_DATA_PREFIX =
+      "com/google/gwt/i18n/client/impl/cldr/CurrencyData";
 
   /**
    * Prefix for properties files containing additional flags about currencies
    * each locale, which are not present in CLDR.
    */
-  private static final String CURRENCY_EXTRA_PREFIX = "com/google/gwt/i18n/client/constants/CurrencyExtra";
+  private static final String CURRENCY_EXTRA_PREFIX =
+      "com/google/gwt/i18n/client/constants/CurrencyExtra";
 
   private static final String CURRENCY_LIST = CurrencyList.class.getCanonicalName();
 
@@ -230,7 +232,8 @@
    * locale. We use this only to get the default currency for our current
    * locale.
    */
-  private static final String NUMBER_CONSTANTS_PREFIX = "com/google/gwt/i18n/client/constants/NumberConstantsImpl";
+  private static final String NUMBER_CONSTANTS_PREFIX =
+      "com/google/gwt/i18n/client/constants/NumberConstantsImpl";
 
   /**
    * Backslash-escape any double quotes in the supplied string.
@@ -288,8 +291,8 @@
    * @param locale
    * @return generated class name for the requested locale
    */
-  private String generateLocaleTree(TreeLogger logger,
-      GeneratorContext context, JClassType targetClass, GwtLocale locale) {
+  private String generateLocaleTree(TreeLogger logger, GeneratorContext context,
+      JClassType targetClass, GwtLocale locale) {
     String superClassName = CURRENCY_LIST;
     List<GwtLocale> searchList = locale.getCompleteSearchList();
 
@@ -334,9 +337,9 @@
         defCurrencyCode = lastDefaultCurrencyCode;
       }
       if (!currencyData.isEmpty() || defCurrencyCode != null) {
-        String newClass = generateOneLocale(logger, context, targetClass,
-            search, superClassName, currencies, allCurrencyData,
-            defCurrencyCode);
+        String newClass =
+            generateOneLocale(logger, context, targetClass, search,
+                superClassName, currencies, allCurrencyData, defCurrencyCode);
         superClassName = newClass;
         lastDefaultCurrencyCode = defCurrencyCode;
       }
@@ -355,7 +358,7 @@
    * @param superClassName
    * @param currencies the set of currencies defined in this locale
    * @param allCurrencyData map of currency code -> unparsed CurrencyInfo for
-   *          that code
+   *        that code
    * @param defCurrencyCode default currency code for this locale
    * @return fully-qualified class name generated
    */
@@ -363,14 +366,13 @@
       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();
     PrintWriter pw = context.tryCreate(logger, packageName, className);
     if (pw != null) {
-      ClassSourceFileComposerFactory factory = new ClassSourceFileComposerFactory(
-          packageName, className);
+      ClassSourceFileComposerFactory factory =
+          new ClassSourceFileComposerFactory(packageName, className);
       factory.setSuperclass(superClassName);
       factory.addImport(CURRENCY_LIST);
       factory.addImport(CURRENCY_DATA);
@@ -409,15 +411,16 @@
    * @return fully-qualified class name that was generated
    */
   private String generateRuntimeSelection(TreeLogger logger,
-      GeneratorContext context, JClassType targetClass,
-      GwtLocale compileLocale, Set<GwtLocale> locales) {
+      GeneratorContext context, JClassType targetClass, GwtLocale compileLocale,
+      Set<GwtLocale> locales) {
     String packageName = targetClass.getPackage().getName();
-    String className = targetClass.getName().replace('.', '_') + "_"
-        + compileLocale.getAsString() + "_runtimeSelection";
+    String className =
+        targetClass.getName().replace('.', '_') + "_"
+            + compileLocale.getAsString() + "_runtimeSelection";
     PrintWriter pw = context.tryCreate(logger, packageName, className);
     if (pw != null) {
-      ClassSourceFileComposerFactory factory = new ClassSourceFileComposerFactory(
-          packageName, className);
+      ClassSourceFileComposerFactory factory =
+          new ClassSourceFileComposerFactory(packageName, className);
       factory.setSuperclass(targetClass.getQualifiedSourceName());
       factory.addImport(CURRENCY_LIST);
       factory.addImport(CURRENCY_DATA);
@@ -451,9 +454,11 @@
       writer.println("  return;");
       writer.println("}");
       boolean fetchedLocale = false;
-      Map<String, Set<GwtLocale>> localeMap = new TreeMap<String, Set<GwtLocale>>();
-      String compileLocaleClass = processChildLocale(logger, context,
-          targetClass, localeMap, compileLocale);
+      Map<String, Set<GwtLocale>> localeMap = new TreeMap<String,
+          Set<GwtLocale>>();
+      String compileLocaleClass =
+          processChildLocale(logger, context, targetClass, localeMap,
+              compileLocale);
       if (compileLocaleClass == null) {
         // already gave warning, just use default implementation
         return null;
@@ -499,7 +504,7 @@
   /**
    * Return a map of currency data for the requested locale, or null if there is
    * not one (not that inheritance is not handled here).
-   * 
+   * <p/>
    * The keys are ISO4217 currency codes. The format of the map values is:
    * 
    * <pre>
@@ -531,8 +536,7 @@
    */
   private String getDefaultCurrency(GwtLocale locale) {
     String defCurrencyCode = null;
-    LocalizedProperties numberConstants = getProperties(
-        NUMBER_CONSTANTS_PREFIX, locale);
+    LocalizedProperties numberConstants = getProperties(NUMBER_CONSTANTS_PREFIX, locale);
     if (numberConstants != null) {
       defCurrencyCode = numberConstants.getProperty("defCurrencyCode");
     }
@@ -593,9 +597,9 @@
    * @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) {
+  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) {
@@ -623,11 +627,11 @@
    * method is omitted entirely.
    * 
    * @param allCurrencyData map of currency codes to currency data for the
-   *          current locale, including all inherited currencies data
+   *        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
+   *        listed
    */
   private void writeCurrencyMethod(String className, SourceWriter writer,
       String[] currencies, Map<String, CurrencyInfo> allCurrencyData) {
@@ -644,9 +648,9 @@
         writer.println("@Override");
         writer.println("protected native void loadCurrencyMap() /*-{");
         writer.indent();
-        writer.println("this.@com.google.gwt.i18n.client.impl." + className
+        writer.println("this.@com.google.gwt.i18n.client." + className
             + "::loadSuperCurrencyMap()();");
-        writer.println("this.@com.google.gwt.i18n.client.impl." + className
+        writer.println("this.@com.google.gwt.i18n.client." + className
             + "::overrideCurrencyMap(Lcom/google/gwt/core/client/"
             + "JavaScriptObject;)({");
         writer.indent();
@@ -673,7 +677,7 @@
    * @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
+   *        listed
    */
   private void writeNamesMethod(String className, SourceWriter writer,
       String[] currencies, Map<String, CurrencyInfo> allCurrencyData) {
@@ -692,9 +696,9 @@
           writer.println("@Override");
           writer.println("protected native void loadNamesMap() /*-{");
           writer.indent();
-          writer.println("this.@com.google.gwt.i18n.client.impl." + className
+          writer.println("this.@com.google.gwt.i18n.client." + className
               + "::loadSuperNamesMap()();");
-          writer.println("this.@com.google.gwt.i18n.client.impl." + className
+          writer.println("this.@com.google.gwt.i18n.client." + className
               + "::overrideNamesMap(Lcom/google/gwt/core/"
               + "client/JavaScriptObject;)({");
           writer.indent();
diff --git a/user/test/com/google/gwt/i18n/I18NSuite.java b/user/test/com/google/gwt/i18n/I18NSuite.java
index d5ef1f2..826b1dc 100644
--- a/user/test/com/google/gwt/i18n/I18NSuite.java
+++ b/user/test/com/google/gwt/i18n/I18NSuite.java
@@ -17,6 +17,7 @@
 
 import com.google.gwt.i18n.client.AnnotationsTest;
 import com.google.gwt.i18n.client.ArabicPluralsTest;
+import com.google.gwt.i18n.client.CurrencyTest;
 import com.google.gwt.i18n.client.CustomPluralsTest;
 import com.google.gwt.i18n.client.DateTimeFormat_de_Test;
 import com.google.gwt.i18n.client.DateTimeFormat_en_Test;
@@ -40,7 +41,6 @@
 import com.google.gwt.i18n.client.RuntimeLocalesTest;
 import com.google.gwt.i18n.client.TimeZoneInfoTest;
 import com.google.gwt.i18n.client.TimeZoneTest;
-import com.google.gwt.i18n.client.impl.CurrencyTest;
 import com.google.gwt.i18n.rebind.MessageFormatParserTest;
 import com.google.gwt.i18n.server.GwtLocaleTest;
 import com.google.gwt.i18n.server.RegionInheritanceTest;
diff --git a/user/test/com/google/gwt/i18n/client/impl/CurrencyTest.java b/user/test/com/google/gwt/i18n/client/CurrencyTest.java
similarity index 78%
rename from user/test/com/google/gwt/i18n/client/impl/CurrencyTest.java
rename to user/test/com/google/gwt/i18n/client/CurrencyTest.java
index dd281b2..f91bcd2 100644
--- a/user/test/com/google/gwt/i18n/client/impl/CurrencyTest.java
+++ b/user/test/com/google/gwt/i18n/client/CurrencyTest.java
@@ -13,8 +13,10 @@
  * License for the specific language governing permissions and limitations under
  * the License.
  */
-package com.google.gwt.i18n.client.impl;
+package com.google.gwt.i18n.client;
 
+import com.google.gwt.i18n.client.CurrencyData;
+import com.google.gwt.i18n.client.CurrencyList;
 import com.google.gwt.junit.client.GWTTestCase;
 
 import java.util.Iterator;
@@ -30,6 +32,16 @@
     return "com.google.gwt.i18n.I18NTest_es_MX";
   }
 
+  public void testCustom() {
+    CurrencyData currencyData = new DefaultCurrencyData("CAD", "/", 3);
+    NumberFormat format = NumberFormat.getCurrencyFormat(currencyData);
+    String formatted = format.format(1.23);
+    assertEquals("/\u00A01.230", formatted);
+    format = NumberFormat.getFormat("#0.0000\u00A4", currencyData);
+    formatted = format.format(1234.23);
+    assertEquals("1234.2300/", formatted);
+  }
+
   public void testIterator() {
     CurrencyList list = CurrencyList.get();
     boolean found = false;
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
index f3dfe62..e4a9d37 100644
--- 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
@@ -18,8 +18,6 @@
 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;
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 2cba95f..05ef720 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
@@ -18,8 +18,6 @@
 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;
diff --git a/user/test/com/google/gwt/i18n/client/I18N_nb_Test.java b/user/test/com/google/gwt/i18n/client/I18N_nb_Test.java
index d4fb01d..ede08e8 100644
--- a/user/test/com/google/gwt/i18n/client/I18N_nb_Test.java
+++ b/user/test/com/google/gwt/i18n/client/I18N_nb_Test.java
@@ -16,8 +16,6 @@
 package com.google.gwt.i18n.client;
 
 import com.google.gwt.core.client.GWT;
-import com.google.gwt.i18n.client.impl.CurrencyData;
-import com.google.gwt.i18n.client.impl.CurrencyList;
 import com.google.gwt.junit.client.GWTTestCase;
 
 /**