Path: blob/aarch64-shenandoah-jdk8u272-b10/jdk/make/src/classes/build/tools/generatecurrencydata/GenerateCurrencyData.java
32287 views
/*1* Copyright (c) 2001, 2015, Oracle and/or its affiliates. All rights reserved.2* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.3*4* This code is free software; you can redistribute it and/or modify it5* under the terms of the GNU General Public License version 2 only, as6* published by the Free Software Foundation. Oracle designates this7* particular file as subject to the "Classpath" exception as provided8* by Oracle in the LICENSE file that accompanied this code.9*10* This code is distributed in the hope that it will be useful, but WITHOUT11* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or12* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License13* version 2 for more details (a copy is included in the LICENSE file that14* accompanied this code).15*16* You should have received a copy of the GNU General Public License version17* 2 along with this work; if not, write to the Free Software Foundation,18* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.19*20* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA21* or visit www.oracle.com if you need additional information or have any22* questions.23*/2425package build.tools.generatecurrencydata;2627import java.io.IOException;28import java.io.FileNotFoundException;29import java.io.DataOutputStream;30import java.io.FileOutputStream;31import java.text.SimpleDateFormat;32import java.util.Date;33import java.util.HashMap;34import java.util.Locale;35import java.util.Properties;36import java.util.TimeZone;3738/**39* Reads currency data in properties format from the file specified in the40* command line and generates a binary data file as specified in the command line.41*42* Output of this tool is a binary file that contains the data in43* the following order:44*45* - magic number (int): always 0x43757244 ('CurD')46* - formatVersion (int)47* - dataVersion (int)48* - mainTable (int[26*26])49* - specialCaseCount (int)50* - specialCaseCutOverTimes (long[specialCaseCount])51* - specialCaseOldCurrencies (String[specialCaseCount])52* - specialCaseNewCurrencies (String[specialCaseCount])53* - specialCaseOldCurrenciesDefaultFractionDigits (int[specialCaseCount])54* - specialCaseNewCurrenciesDefaultFractionDigits (int[specialCaseCount])55* - specialCaseOldCurrenciesNumericCode (int[specialCaseCount])56* - specialCaseNewCurrenciesNumericCode (int[specialCaseCount])57* - otherCurrenciesCount (int)58* - otherCurrencies (String)59* - otherCurrenciesDefaultFractionDigits (int[otherCurrenciesCount])60* - otherCurrenciesNumericCode (int[otherCurrenciesCount])61*62* See CurrencyData.properties for the input format description and63* Currency.java for the format descriptions of the generated tables.64*/65public class GenerateCurrencyData {6667private static DataOutputStream out;6869// input data: currency data obtained from properties on input stream70private static Properties currencyData;71private static String formatVersion;72private static String dataVersion;73private static String validCurrencyCodes;7475// handy constants - must match definitions in java.util.Currency76// magic number77private static final int MAGIC_NUMBER = 0x43757244;78// number of characters from A to Z79private static final int A_TO_Z = ('Z' - 'A') + 1;80// entry for invalid country codes81private static final int INVALID_COUNTRY_ENTRY = 0x0000007F;82// entry for countries without currency83private static final int COUNTRY_WITHOUT_CURRENCY_ENTRY = 0x00000200;84// mask for simple case country entries85private static final int SIMPLE_CASE_COUNTRY_MASK = 0x00000000;86// mask for simple case country entry final character87private static final int SIMPLE_CASE_COUNTRY_FINAL_CHAR_MASK = 0x0000001F;88// mask for simple case country entry default currency digits89private static final int SIMPLE_CASE_COUNTRY_DEFAULT_DIGITS_MASK = 0x000001E0;90// shift count for simple case country entry default currency digits91private static final int SIMPLE_CASE_COUNTRY_DEFAULT_DIGITS_SHIFT = 5;92// maximum number for simple case country entry default currency digits93private static final int SIMPLE_CASE_COUNTRY_MAX_DEFAULT_DIGITS = 9;94// mask for special case country entries95private static final int SPECIAL_CASE_COUNTRY_MASK = 0x00000200;96// mask for special case country index97private static final int SPECIAL_CASE_COUNTRY_INDEX_MASK = 0x0000001F;98// delta from entry index component in main table to index into special case tables99private static final int SPECIAL_CASE_COUNTRY_INDEX_DELTA = 1;100// mask for distinguishing simple and special case countries101private static final int COUNTRY_TYPE_MASK = SIMPLE_CASE_COUNTRY_MASK | SPECIAL_CASE_COUNTRY_MASK;102// mask for the numeric code of the currency103private static final int NUMERIC_CODE_MASK = 0x000FFC00;104// shift count for the numeric code of the currency105private static final int NUMERIC_CODE_SHIFT = 10;106107// generated data108private static int[] mainTable = new int[A_TO_Z * A_TO_Z];109110private static final int maxSpecialCases = 30;111private static int specialCaseCount = 0;112private static long[] specialCaseCutOverTimes = new long[maxSpecialCases];113private static String[] specialCaseOldCurrencies = new String[maxSpecialCases];114private static String[] specialCaseNewCurrencies = new String[maxSpecialCases];115private static int[] specialCaseOldCurrenciesDefaultFractionDigits = new int[maxSpecialCases];116private static int[] specialCaseNewCurrenciesDefaultFractionDigits = new int[maxSpecialCases];117private static int[] specialCaseOldCurrenciesNumericCode = new int[maxSpecialCases];118private static int[] specialCaseNewCurrenciesNumericCode = new int[maxSpecialCases];119120private static final int maxOtherCurrencies = 128;121private static int otherCurrenciesCount = 0;122private static StringBuffer otherCurrencies = new StringBuffer();123private static int[] otherCurrenciesDefaultFractionDigits = new int[maxOtherCurrencies];124private static int[] otherCurrenciesNumericCode= new int[maxOtherCurrencies];125126// date format for parsing cut-over times127private static SimpleDateFormat format;128129// Minor Units130private static String[] currenciesWithDefinedMinorUnitDecimals =131new String[SIMPLE_CASE_COUNTRY_MAX_DEFAULT_DIGITS + 1];132private static String currenciesWithMinorUnitsUndefined;133134public static void main(String[] args) {135136// Look for "-o outputfilename" option137if ( args.length == 2 && args[0].equals("-o") ) {138try {139out = new DataOutputStream(new FileOutputStream(args[1]));140} catch ( FileNotFoundException e ) {141System.err.println("Error: " + e.getMessage());142e.printStackTrace(System.err);143System.exit(1);144}145} else {146System.err.println("Error: Illegal arg count");147System.exit(1);148}149150format = new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss", Locale.US);151format.setTimeZone(TimeZone.getTimeZone("GMT"));152format.setLenient(false);153154try {155readInput();156buildMainAndSpecialCaseTables();157buildOtherTables();158writeOutput();159out.flush();160out.close();161} catch (Exception e) {162System.err.println("Error: " + e.getMessage());163e.printStackTrace(System.err);164System.exit(1);165}166}167168private static void readInput() throws IOException {169currencyData = new Properties();170currencyData.load(System.in);171172// initialize other lookup strings173formatVersion = (String) currencyData.get("formatVersion");174dataVersion = (String) currencyData.get("dataVersion");175validCurrencyCodes = (String) currencyData.get("all");176for (int i = 0; i <= SIMPLE_CASE_COUNTRY_MAX_DEFAULT_DIGITS; i++) {177currenciesWithDefinedMinorUnitDecimals[i]178= (String) currencyData.get("minor"+i);179}180currenciesWithMinorUnitsUndefined = (String) currencyData.get("minorUndefined");181if (formatVersion == null ||182dataVersion == null ||183validCurrencyCodes == null ||184currenciesWithMinorUnitsUndefined == null) {185throw new NullPointerException("not all required data is defined in input");186}187}188189private static void buildMainAndSpecialCaseTables() throws Exception {190for (int first = 0; first < A_TO_Z; first++) {191for (int second = 0; second < A_TO_Z; second++) {192char firstChar = (char) ('A' + first);193char secondChar = (char) ('A' + second);194String countryCode = (new StringBuffer()).append(firstChar).append(secondChar).toString();195String currencyInfo = (String) currencyData.get(countryCode);196int tableEntry = 0;197if (currencyInfo == null) {198// no entry -> must be invalid ISO 3166 country code199tableEntry = INVALID_COUNTRY_ENTRY;200} else {201int length = currencyInfo.length();202if (length == 0) {203// special case: country without currency204tableEntry = COUNTRY_WITHOUT_CURRENCY_ENTRY;205} else if (length == 3) {206// valid currency207if (currencyInfo.charAt(0) == firstChar && currencyInfo.charAt(1) == secondChar) {208checkCurrencyCode(currencyInfo);209int digits = getDefaultFractionDigits(currencyInfo);210if (digits < 0 || digits > SIMPLE_CASE_COUNTRY_MAX_DEFAULT_DIGITS) {211throw new RuntimeException("fraction digits out of range for " + currencyInfo);212}213int numericCode= getNumericCode(currencyInfo);214if (numericCode < 0 || numericCode >= 1000 ) {215throw new RuntimeException("numeric code out of range for " + currencyInfo);216}217tableEntry = SIMPLE_CASE_COUNTRY_MASK218| (currencyInfo.charAt(2) - 'A')219| (digits << SIMPLE_CASE_COUNTRY_DEFAULT_DIGITS_SHIFT)220| (numericCode << NUMERIC_CODE_SHIFT);221} else {222tableEntry = SPECIAL_CASE_COUNTRY_MASK | (makeSpecialCaseEntry(currencyInfo) + SPECIAL_CASE_COUNTRY_INDEX_DELTA);223}224} else {225tableEntry = SPECIAL_CASE_COUNTRY_MASK | (makeSpecialCaseEntry(currencyInfo) + SPECIAL_CASE_COUNTRY_INDEX_DELTA);226}227}228mainTable[first * A_TO_Z + second] = tableEntry;229}230}231}232233private static int getDefaultFractionDigits(String currencyCode) {234for (int i = 0; i <= SIMPLE_CASE_COUNTRY_MAX_DEFAULT_DIGITS; i++) {235if (currenciesWithDefinedMinorUnitDecimals[i] != null &&236currenciesWithDefinedMinorUnitDecimals[i].indexOf(currencyCode) != -1) {237return i;238}239}240241if (currenciesWithMinorUnitsUndefined.indexOf(currencyCode) != -1) {242return -1;243} else {244return 2;245}246}247248private static int getNumericCode(String currencyCode) {249int index = validCurrencyCodes.indexOf(currencyCode);250String numericCode = validCurrencyCodes.substring(index + 3, index + 6);251return Integer.parseInt(numericCode);252}253254static HashMap<String, Integer> specialCaseMap = new HashMap<>();255256private static int makeSpecialCaseEntry(String currencyInfo) throws Exception {257Integer oldEntry = specialCaseMap.get(currencyInfo);258if (oldEntry != null) {259return oldEntry.intValue();260}261if (specialCaseCount == maxSpecialCases) {262throw new RuntimeException("too many special cases");263}264if (currencyInfo.length() == 3) {265checkCurrencyCode(currencyInfo);266specialCaseCutOverTimes[specialCaseCount] = Long.MAX_VALUE;267specialCaseOldCurrencies[specialCaseCount] = currencyInfo;268specialCaseOldCurrenciesDefaultFractionDigits[specialCaseCount] = getDefaultFractionDigits(currencyInfo);269specialCaseOldCurrenciesNumericCode[specialCaseCount] = getNumericCode(currencyInfo);270specialCaseNewCurrencies[specialCaseCount] = null;271specialCaseNewCurrenciesDefaultFractionDigits[specialCaseCount] = 0;272specialCaseNewCurrenciesNumericCode[specialCaseCount] = 0;273} else {274int length = currencyInfo.length();275if (currencyInfo.charAt(3) != ';' ||276currencyInfo.charAt(length - 4) != ';') {277throw new RuntimeException("invalid currency info: " + currencyInfo);278}279String oldCurrency = currencyInfo.substring(0, 3);280String newCurrency = currencyInfo.substring(length - 3, length);281checkCurrencyCode(oldCurrency);282checkCurrencyCode(newCurrency);283String timeString = currencyInfo.substring(4, length - 4);284long time = format.parse(timeString).getTime();285if (Math.abs(time - System.currentTimeMillis()) > ((long) 10) * 365 * 24 * 60 * 60 * 1000) {286throw new RuntimeException("time is more than 10 years from present: " + time);287}288specialCaseCutOverTimes[specialCaseCount] = time;289specialCaseOldCurrencies[specialCaseCount] = oldCurrency;290specialCaseOldCurrenciesDefaultFractionDigits[specialCaseCount] = getDefaultFractionDigits(oldCurrency);291specialCaseOldCurrenciesNumericCode[specialCaseCount] = getNumericCode(oldCurrency);292specialCaseNewCurrencies[specialCaseCount] = newCurrency;293specialCaseNewCurrenciesDefaultFractionDigits[specialCaseCount] = getDefaultFractionDigits(newCurrency);294specialCaseNewCurrenciesNumericCode[specialCaseCount] = getNumericCode(newCurrency);295}296specialCaseMap.put(currencyInfo, new Integer(specialCaseCount));297return specialCaseCount++;298}299300private static void buildOtherTables() {301if (validCurrencyCodes.length() % 7 != 6) {302throw new RuntimeException("\"all\" entry has incorrect size");303}304for (int i = 0; i < (validCurrencyCodes.length() + 1) / 7; i++) {305if (i > 0 && validCurrencyCodes.charAt(i * 7 - 1) != '-') {306throw new RuntimeException("incorrect separator in \"all\" entry");307}308String currencyCode = validCurrencyCodes.substring(i * 7, i * 7 + 3);309int numericCode = Integer.parseInt(310validCurrencyCodes.substring(i * 7 + 3, i * 7 + 6));311checkCurrencyCode(currencyCode);312int tableEntry = mainTable[(currencyCode.charAt(0) - 'A') * A_TO_Z + (currencyCode.charAt(1) - 'A')];313if (tableEntry == INVALID_COUNTRY_ENTRY ||314(tableEntry & SPECIAL_CASE_COUNTRY_MASK) != 0 ||315(tableEntry & SIMPLE_CASE_COUNTRY_FINAL_CHAR_MASK) != (currencyCode.charAt(2) - 'A')) {316if (otherCurrenciesCount == maxOtherCurrencies) {317throw new RuntimeException("too many other currencies");318}319if (otherCurrencies.length() > 0) {320otherCurrencies.append('-');321}322otherCurrencies.append(currencyCode);323otherCurrenciesDefaultFractionDigits[otherCurrenciesCount] = getDefaultFractionDigits(currencyCode);324otherCurrenciesNumericCode[otherCurrenciesCount] = getNumericCode(currencyCode);325otherCurrenciesCount++;326}327}328}329330private static void checkCurrencyCode(String currencyCode) {331if (currencyCode.length() != 3) {332throw new RuntimeException("illegal length for currency code: " + currencyCode);333}334for (int i = 0; i < 3; i++) {335char aChar = currencyCode.charAt(i);336if ((aChar < 'A' || aChar > 'Z') && !currencyCode.equals("XB5")) {337throw new RuntimeException("currency code contains illegal character: " + currencyCode);338}339}340if (validCurrencyCodes.indexOf(currencyCode) == -1) {341throw new RuntimeException("currency code not listed as valid: " + currencyCode);342}343}344345private static void writeOutput() throws IOException {346out.writeInt(MAGIC_NUMBER);347out.writeInt(Integer.parseInt(formatVersion));348out.writeInt(Integer.parseInt(dataVersion));349writeIntArray(mainTable, mainTable.length);350out.writeInt(specialCaseCount);351writeLongArray(specialCaseCutOverTimes, specialCaseCount);352writeStringArray(specialCaseOldCurrencies, specialCaseCount);353writeStringArray(specialCaseNewCurrencies, specialCaseCount);354writeIntArray(specialCaseOldCurrenciesDefaultFractionDigits, specialCaseCount);355writeIntArray(specialCaseNewCurrenciesDefaultFractionDigits, specialCaseCount);356writeIntArray(specialCaseOldCurrenciesNumericCode, specialCaseCount);357writeIntArray(specialCaseNewCurrenciesNumericCode, specialCaseCount);358out.writeInt(otherCurrenciesCount);359out.writeUTF(otherCurrencies.toString());360writeIntArray(otherCurrenciesDefaultFractionDigits, otherCurrenciesCount);361writeIntArray(otherCurrenciesNumericCode, otherCurrenciesCount);362}363364private static void writeIntArray(int[] ia, int count) throws IOException {365for (int i = 0; i < count; i ++) {366out.writeInt(ia[i]);367}368}369370private static void writeLongArray(long[] la, int count) throws IOException {371for (int i = 0; i < count; i ++) {372out.writeLong(la[i]);373}374}375376private static void writeStringArray(String[] sa, int count) throws IOException {377for (int i = 0; i < count; i ++) {378String str = (sa[i] != null) ? sa[i] : "";379out.writeUTF(str);380}381}382}383384385