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");