/*
* @(#)GenerateCurrencyData.java 1.7 06/10/10
*
* Copyright 1990-2008 Sun Microsystems, Inc. All Rights Reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License version
* 2 only, as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License version 2 for more details (a copy is
* included at /legal/license.txt).
*
* You should have received a copy of the GNU General Public License
* version 2 along with this work; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA
*
* Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa
* Clara, CA 95054 or visit www.sun.com if you need additional
* information or have any questions.
*/
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.Locale;
import java.util.Properties;
import java.util.TimeZone;
/**
* Reads currency data in properties format from the standard input stream
* and generates an equivalent Java source file on the standard output stream.
* See CurrencyData.properties for the input format description and
* Currency.java for the format descriptions of the generated tables.
*/
public class GenerateCurrencyData {
// input data: currency data obtained from properties on input stream
private static Properties currencyData;
private static String validCurrencyCodes;
private static String currenciesWith0MinorUnitDecimals;
private static String currenciesWith1MinorUnitDecimal;
private static String currenciesWithMinorUnitsUndefined;
// generated data
private static String mainTable;
private static final int maxSpecialCases = 30;
private static int specialCaseCount = 0;
private static long[] specialCaseCutOverTimes = new long[maxSpecialCases];
private static String[] specialCaseOldCurrencies = new String[maxSpecialCases];
private static String[] specialCaseNewCurrencies = new String[maxSpecialCases];
private static int[] specialCaseOldCurrenciesDefaultFractionDigits = new int[maxSpecialCases];
private static int[] specialCaseNewCurrenciesDefaultFractionDigits = new int[maxSpecialCases];
private static final int maxOtherCurrencies = 50;
private static int otherCurrenciesCount = 0;
private static StringBuffer otherCurrencies = new StringBuffer();
private static int[] otherCurrenciesDefaultFractionDigits = new int[maxOtherCurrencies];
// handy constants - must match definitions in java.util.Currency
// number of characters from A to Z
private static final int A_TO_Z = ('Z' - 'A') + 1;
// entry for invalid country codes
private static final int INVALID_COUNTRY_ENTRY = 0x007F;
// entry for countries without currency
private static final int COUNTRY_WITHOUT_CURRENCY_ENTRY = 0x0080;
// mask for simple case country entries
private static final int SIMPLE_CASE_COUNTRY_MASK = 0x0000;
// mask for simple case country entry final character
private static final int SIMPLE_CASE_COUNTRY_FINAL_CHAR_MASK = 0x001F;
// mask for simple case country entry default currency digits
private static final int SIMPLE_CASE_COUNTRY_DEFAULT_DIGITS_MASK = 0x0060;
// shift count for simple case country entry default currency digits
private static final int SIMPLE_CASE_COUNTRY_DEFAULT_DIGITS_SHIFT = 5;
// mask for special case country entries
private static final int SPECIAL_CASE_COUNTRY_MASK = 0x0080;
// mask for special case country index
private static final int SPECIAL_CASE_COUNTRY_INDEX_MASK = 0x001F;
// delta from entry index component in main table to index into special case tables
private static final int SPECIAL_CASE_COUNTRY_INDEX_DELTA = 1;
// mask for distinguishing simple and special case countries
private static final int COUNTRY_TYPE_MASK = SIMPLE_CASE_COUNTRY_MASK | SPECIAL_CASE_COUNTRY_MASK;
// date format for parsing cut-over times
private static SimpleDateFormat format;
public static void main(String[] args) {
format = new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss", Locale.US);
format.setTimeZone(TimeZone.getTimeZone("GMT"));
format.setLenient(false);
try {
readInput();
buildMainAndSpecialCaseTables();
buildOtherTables();
writeOutput();
} catch (Exception e) {
System.err.println("Error: " + e.getMessage());
e.printStackTrace(System.err);
System.exit(1);
}
}
private static void readInput() throws IOException {
currencyData = new Properties();
currencyData.load(System.in);
// initialize other lookup strings
validCurrencyCodes = (String) currencyData.get("all");
currenciesWith0MinorUnitDecimals = (String) currencyData.get("minor0");
currenciesWith1MinorUnitDecimal = (String) currencyData.get("minor1");
currenciesWithMinorUnitsUndefined = (String) currencyData.get("minorUndefined");
if (validCurrencyCodes == null ||
currenciesWith0MinorUnitDecimals == null ||
currenciesWith1MinorUnitDecimal == null ||
currenciesWithMinorUnitsUndefined == null) {
throw new NullPointerException("not all required data is defined in input");
}
}
private static void buildMainAndSpecialCaseTables() throws Exception {
char[] mainTableArray = new char[A_TO_Z*A_TO_Z];
for (int first = 0; first < A_TO_Z; first++) {
for (int second = 0; second < A_TO_Z; second++) {
char firstChar = (char) ('A' + first);
char secondChar = (char) ('A' + second);
String countryCode = (new StringBuffer()).append(firstChar).append(secondChar).toString();
String currencyInfo = (String) currencyData.get(countryCode);
int tableEntry = 0;
if (currencyInfo == null) {
// no entry -> must be invalid ISO 3166 country code
tableEntry = INVALID_COUNTRY_ENTRY;
} else {
int length = currencyInfo.length();
if (length == 0) {
// special case: country without currency
tableEntry = COUNTRY_WITHOUT_CURRENCY_ENTRY;
} else if (length == 3) {
// valid currency
if (currencyInfo.charAt(0) == firstChar && currencyInfo.charAt(1) == secondChar) {
checkCurrencyCode(currencyInfo);
int digits = getDefaultFractionDigits(currencyInfo);
if (digits < 0 || digits > 3) {
throw new RuntimeException("fraction digits out of range for " + currencyInfo);
}
tableEntry = SIMPLE_CASE_COUNTRY_MASK
| (currencyInfo.charAt(2) - 'A')
| (digits << SIMPLE_CASE_COUNTRY_DEFAULT_DIGITS_SHIFT);
} else {
tableEntry = SPECIAL_CASE_COUNTRY_MASK | (makeSpecialCaseEntry(currencyInfo) + SPECIAL_CASE_COUNTRY_INDEX_DELTA);
}
} else {
tableEntry = SPECIAL_CASE_COUNTRY_MASK | (makeSpecialCaseEntry(currencyInfo) + SPECIAL_CASE_COUNTRY_INDEX_DELTA);
}
}
mainTableArray[first * A_TO_Z + second] = (char) tableEntry;
}
}
mainTable = new String(mainTableArray);
}
private static int getDefaultFractionDigits(String currencyCode) {
if (currenciesWith0MinorUnitDecimals.indexOf(currencyCode) != -1) {
return 0;
} else if (currenciesWith1MinorUnitDecimal.indexOf(currencyCode) != -1) {
return 1;
} else if (currenciesWithMinorUnitsUndefined.indexOf(currencyCode) != -1) {
return -1;
} else {
return 2;
}
}
static HashMap specialCaseMap = new HashMap();
private static int makeSpecialCaseEntry(String currencyInfo) throws Exception {
Integer oldEntry = (Integer) specialCaseMap.get(currencyInfo);
if (oldEntry != null) {
return oldEntry.intValue();
}
if (specialCaseCount == maxSpecialCases) {
throw new RuntimeException("too many special cases");
}
if (currencyInfo.length() == 3) {
checkCurrencyCode(currencyInfo);
specialCaseCutOverTimes[specialCaseCount] = Long.MAX_VALUE;
specialCaseOldCurrencies[specialCaseCount] = currencyInfo;
specialCaseOldCurrenciesDefaultFractionDigits[specialCaseCount] = getDefaultFractionDigits(currencyInfo);
specialCaseNewCurrencies[specialCaseCount] = null;
specialCaseNewCurrenciesDefaultFractionDigits[specialCaseCount] = 0;
} else {
int length = currencyInfo.length();
if (currencyInfo.charAt(3) != ';' ||
currencyInfo.charAt(length - 4) != ';') {
throw new RuntimeException("invalid currency info: " + currencyInfo);
}
String oldCurrency = currencyInfo.substring(0, 3);
String newCurrency = currencyInfo.substring(length - 3, length);
checkCurrencyCode(oldCurrency);
checkCurrencyCode(newCurrency);
String timeString = currencyInfo.substring(4, length - 4);
long time = format.parse(timeString).getTime();
if (Math.abs(time - System.currentTimeMillis()) > ((long) 10) * 365 * 24 * 60 * 60 * 1000) {
throw new RuntimeException("time is more than 10 years from present: " + time);
}
specialCaseCutOverTimes[specialCaseCount] = time;
specialCaseOldCurrencies[specialCaseCount] = oldCurrency;
specialCaseOldCurrenciesDefaultFractionDigits[specialCaseCount] = getDefaultFractionDigits(oldCurrency);
specialCaseNewCurrencies[specialCaseCount] = newCurrency;
specialCaseNewCurrenciesDefaultFractionDigits[specialCaseCount] = getDefaultFractionDigits(newCurrency);
}
specialCaseMap.put(currencyInfo, new Integer(specialCaseCount));
return specialCaseCount++;
}
private static void buildOtherTables() {
if (validCurrencyCodes.length() % 4 != 3) {
throw new RuntimeException("\"all\" entry has incorrect size");
}
for (int i = 0; i < (validCurrencyCodes.length() + 1) / 4; i++) {
if (i > 0 && validCurrencyCodes.charAt(i * 4 - 1) != '-') {
throw new RuntimeException("incorrect separator in \"all\" entry");
}
String currencyCode = validCurrencyCodes.substring(i * 4, i * 4 + 3);
checkCurrencyCode(currencyCode);
int tableEntry = mainTable.charAt((currencyCode.charAt(0) - 'A') * A_TO_Z + (currencyCode.charAt(1) - 'A'));
if (tableEntry == INVALID_COUNTRY_ENTRY ||
(tableEntry & SPECIAL_CASE_COUNTRY_MASK) != 0 ||
(tableEntry & SIMPLE_CASE_COUNTRY_FINAL_CHAR_MASK) != (currencyCode.charAt(2) - 'A')) {
if (otherCurrenciesCount == maxOtherCurrencies) {
throw new RuntimeException("too many other currencies");
}
if (otherCurrencies.length() > 0) {
otherCurrencies.append('-');
}
otherCurrencies.append(currencyCode);
otherCurrenciesDefaultFractionDigits[otherCurrenciesCount] = getDefaultFractionDigits(currencyCode);
otherCurrenciesCount++;
}
}
}
private static void checkCurrencyCode(String currencyCode) {
if (currencyCode.length() != 3) {
throw new RuntimeException("illegal length for currency code: " + currencyCode);
}
for (int i = 0; i < 3; i++) {
char aChar = currencyCode.charAt(i);
if ((aChar < 'A' || aChar > 'Z') && !currencyCode.equals("XB5")) {
throw new RuntimeException("currency code contains illegal character: " + currencyCode);
}
}
if (validCurrencyCodes.indexOf(currencyCode) == -1) {
throw new RuntimeException("currency code not listed as valid: " + currencyCode);
}
}
private static void writeOutput() {
System.out.println("package java.util;\n");
System.out.println("class CurrencyData {\n");
writeStaticString("mainTable", mainTable, A_TO_Z);
writeStaticLongArray("scCutOverTimes", specialCaseCutOverTimes, specialCaseCount);
writeStaticStringArray("scOldCurrencies", specialCaseOldCurrencies, specialCaseCount);
writeStaticStringArray("scNewCurrencies", specialCaseNewCurrencies, specialCaseCount);
writeStaticIntArray("scOldCurrenciesDFD", specialCaseOldCurrenciesDefaultFractionDigits, specialCaseCount);
writeStaticIntArray("scNewCurrenciesDFD", specialCaseNewCurrenciesDefaultFractionDigits, specialCaseCount);
writeStaticString("otherCurrencies", otherCurrencies.toString(), otherCurrenciesCount * 4);
writeStaticIntArray("otherCurrenciesDFD", otherCurrenciesDefaultFractionDigits, otherCurrenciesCount);
System.out.println("}\n");
}
private static void writeStaticString(String name, String content, int chunkSize) {
String prefix = " static final String " + name + " = ";
System.out.print(prefix);
System.out.print("\"");
int inChunk = 0;
for (int i = 0; i < content.length(); i++) {
if (inChunk == chunkSize) {
System.out.print("\" +\n");
for (int j = 0; j < prefix.length(); j++) {
System.out.print(" ");
}
System.out.print("\"");
inChunk = 0;
}
writeChar(content.charAt(i));
inChunk++;
}
System.out.println("\";\n");
}
private static void writeStaticStringArray(String name, String[] content, int count) {
System.out.print(" static final String[] " + name + " = { ");
for (int i = 0; i < count; i++) {
if (content[i] == null) {
System.out.print("null");
} else {
writeString(content[i]);
}
System.out.print(", ");
}
System.out.println("};\n");
}
private static void writeStaticIntArray(String name, int[] content, int count) {
System.out.print(" static final int[] " + name + " = { ");
for (int i = 0; i < count; i++) {
System.out.print(content[i]);
System.out.print(", ");
}
System.out.println("};\n");
}
private static void writeStaticLongArray(String name, long[] content, int count) {
System.out.print(" static final long[] " + name + " = { ");
for (int i = 0; i < count; i++) {
System.out.print(content[i]);
System.out.print("L, ");
}
System.out.println("};\n");
}
private static void writeString(String string) {
System.out.print("\"");
for (int i = 0; i < string.length(); i++) {
writeChar(string.charAt(i));
}
System.out.print("\"");
}
private static void writeChar(char aChar) {
if (aChar == '\n') {
System.out.print("\\n");
} else if (aChar == '\r') {
System.out.print("\\r");
} else if (aChar >= '\u0020' && aChar < '\u007F') {
System.out.print(aChar);
} else {
System.out.print("\\u");
String hexString = Integer.toHexString(aChar);
for (int i = 0; i < 4 - hexString.length(); i++) {
System.out.print("0");
}
System.out.print(hexString);
}
}
}