blob: 42936804b38c43ffac40505d35eabde06bb806ec [file] [log] [blame]
/*
* 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.
*/
package com.google.gwt.i18n.rebind;
import com.google.gwt.core.ext.BadPropertyValueException;
import com.google.gwt.core.ext.Generator;
import com.google.gwt.core.ext.GeneratorContext;
import com.google.gwt.core.ext.PropertyOracle;
import com.google.gwt.core.ext.TreeLogger;
import com.google.gwt.core.ext.UnableToCompleteException;
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.user.rebind.ClassSourceFileComposerFactory;
import com.google.gwt.user.rebind.SourceWriter;
import org.apache.tapestry.util.text.LocalizedProperties;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
import java.io.UnsupportedEncodingException;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
/**
* Generator used to generate a localized version of CurrencyList, which
* contains the list of currencies (with names, symbols, and other information)
* localized to the current locale.
*/
public class CurrencyListGenerator extends Generator {
private static final String CURRENCY_DATA = CurrencyData.class.getCanonicalName();
private static final String CURRENCY_LIST = CurrencyList.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";
/**
* 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";
/**
* Prefix for properties files containing number formatting constants for
* each 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/NumberConstants";
/**
* The token representing the locale property controlling Localization.
*/
private static final String PROP_LOCALE = "locale";
/**
* Generate an implementation for the given type.
*
* @param logger error logger
* @param context generator context
* @param typeName target type name
* @return generated class name
* @throws UnableToCompleteException
*/
@Override
public final String generate(TreeLogger logger, GeneratorContext context,
String typeName) throws UnableToCompleteException {
assert CURRENCY_LIST.equals(typeName);
TypeOracle typeOracle = context.getTypeOracle();
// Get the current locale.
PropertyOracle propertyOracle = context.getPropertyOracle();
String locale = "default";
try {
locale = propertyOracle.getPropertyValue(logger, PROP_LOCALE);
} catch (BadPropertyValueException e) {
logger.log(TreeLogger.WARN, "locale property not defined, using defaults", e);
}
JClassType targetClass;
try {
targetClass = typeOracle.getType(typeName);
} catch (NotFoundException e) {
logger.log(TreeLogger.ERROR, "No such type", e);
throw new UnableToCompleteException();
}
String packageName = targetClass.getPackage().getName();
String className = targetClass.getName().replace('.', '_') + "_";
if (!locale.equals("default")) {
className += locale;
}
PrintWriter pw = context.tryCreate(logger, packageName, className);
if (pw != null) {
ClassSourceFileComposerFactory factory = new ClassSourceFileComposerFactory(
packageName, className);
factory.setSuperclass(targetClass.getQualifiedSourceName());
factory.addImport(CURRENCY_LIST);
factory.addImport(CURRENCY_DATA);
SourceWriter writer = factory.createSourceWriter(context, pw);
// Load property files for this locale, handling inheritance properly.
LocalizedProperties currencyData = readProperties(logger, CURRENCY_DATA_PREFIX, locale);
LocalizedProperties currencyExtra = readProperties(logger, CURRENCY_EXTRA_PREFIX, locale);
LocalizedProperties numberConstants = readProperties(logger, NUMBER_CONSTANTS_PREFIX, locale);
// Get default currency code, set defaults in case it isn't found.
String defCurrencyCode = numberConstants.getProperty("defCurrencyCode");
if (defCurrencyCode == null) {
defCurrencyCode = "USD";
}
// Sort for deterministic output.
Set<Object> 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];
}
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.outdent();
writer.println("};");
writer.outdent();
writer.println("}-*/;");
writer.println();
writer.println("@Override");
writer.println("public 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;
}
/**
* Backslash-escape any double quotes in the supplied string.
*
* @param str string to quote
* @return string with double quotes backslash-escaped.
*/
private String quote(String str) {
return str.replace("\"", "\\\"");
}
/**
* 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,
String locale) throws UnableToCompleteException {
LocalizedProperties props = new LocalizedProperties();
ClassLoader classLoader = getClass().getClassLoader();
readProperties(logger, classLoader, propFilePrefix, props);
if (locale.equals("default")) {
return props;
}
int idx = -1;
while ((idx = locale.indexOf('_', idx + 1)) >= 0) {
readProperties(logger, classLoader, propFilePrefix + "_" + locale.substring(0, idx), props);
}
if (locale.length() > 0) {
readProperties(logger, classLoader, propFilePrefix + "_" + locale, props);
}
return props;
}
/**
* Load a single localized properties file, adding to an existing LocalizedProperties object.
*
* @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
* @throws UnableToCompleteException if an error occurs reading the file
*/
private void 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");
}
} 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();
}
}
}
}
}