make/jdk/src/classes/build/tools/generatecurrencydata/GenerateCurrencyData.java
changeset 47216 71c04702a3d5
parent 38440 9e77c5b81def
child 53110 50677f43ac3d
child 57074 12615de8335e
equal deleted inserted replaced
47215:4ebc2e2fb97c 47216:71c04702a3d5
       
     1 /*
       
     2  * Copyright (c) 2001, 2016, Oracle and/or its affiliates. All rights reserved.
       
     3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
       
     4  *
       
     5  * This code is free software; you can redistribute it and/or modify it
       
     6  * under the terms of the GNU General Public License version 2 only, as
       
     7  * published by the Free Software Foundation.  Oracle designates this
       
     8  * particular file as subject to the "Classpath" exception as provided
       
     9  * by Oracle in the LICENSE file that accompanied this code.
       
    10  *
       
    11  * This code is distributed in the hope that it will be useful, but WITHOUT
       
    12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
       
    13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
       
    14  * version 2 for more details (a copy is included in the LICENSE file that
       
    15  * accompanied this code).
       
    16  *
       
    17  * You should have received a copy of the GNU General Public License version
       
    18  * 2 along with this work; if not, write to the Free Software Foundation,
       
    19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
       
    20  *
       
    21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
       
    22  * or visit www.oracle.com if you need additional information or have any
       
    23  * questions.
       
    24  */
       
    25 
       
    26 package build.tools.generatecurrencydata;
       
    27 
       
    28 import java.io.IOException;
       
    29 import java.io.FileNotFoundException;
       
    30 import java.io.DataOutputStream;
       
    31 import java.io.FileOutputStream;
       
    32 import java.text.SimpleDateFormat;
       
    33 import java.util.Date;
       
    34 import java.util.HashMap;
       
    35 import java.util.Locale;
       
    36 import java.util.Objects;
       
    37 import java.util.Properties;
       
    38 import java.util.TimeZone;
       
    39 
       
    40 /**
       
    41  * Reads currency data in properties format from the file specified in the
       
    42  * command line and generates a binary data file as specified in the command line.
       
    43  *
       
    44  * Output of this tool is a binary file that contains the data in
       
    45  * the following order:
       
    46  *
       
    47  *     - magic number (int): always 0x43757244 ('CurD')
       
    48  *     - formatVersion (int)
       
    49  *     - dataVersion (int)
       
    50  *     - mainTable (int[26*26])
       
    51  *     - specialCaseCount (int)
       
    52  *     - specialCaseCutOverTimes (long[specialCaseCount])
       
    53  *     - specialCaseOldCurrencies (String[specialCaseCount])
       
    54  *     - specialCaseNewCurrencies (String[specialCaseCount])
       
    55  *     - specialCaseOldCurrenciesDefaultFractionDigits (int[specialCaseCount])
       
    56  *     - specialCaseNewCurrenciesDefaultFractionDigits (int[specialCaseCount])
       
    57  *     - specialCaseOldCurrenciesNumericCode (int[specialCaseCount])
       
    58  *     - specialCaseNewCurrenciesNumericCode (int[specialCaseCount])
       
    59  *     - otherCurrenciesCount (int)
       
    60  *     - otherCurrencies (String)
       
    61  *     - otherCurrenciesDefaultFractionDigits (int[otherCurrenciesCount])
       
    62  *     - otherCurrenciesNumericCode (int[otherCurrenciesCount])
       
    63  *
       
    64  * See CurrencyData.properties for the input format description and
       
    65  * Currency.java for the format descriptions of the generated tables.
       
    66  */
       
    67 public class GenerateCurrencyData {
       
    68 
       
    69     private static DataOutputStream out;
       
    70 
       
    71     // input data: currency data obtained from properties on input stream
       
    72     private static Properties currencyData;
       
    73     private static String formatVersion;
       
    74     private static String dataVersion;
       
    75     private static String validCurrencyCodes;
       
    76 
       
    77     // handy constants - must match definitions in java.util.Currency
       
    78     // magic number
       
    79     private static final int MAGIC_NUMBER = 0x43757244;
       
    80     // number of characters from A to Z
       
    81     private static final int A_TO_Z = ('Z' - 'A') + 1;
       
    82     // entry for invalid country codes
       
    83     private static final int INVALID_COUNTRY_ENTRY = 0x0000007F;
       
    84     // entry for countries without currency
       
    85     private static final int COUNTRY_WITHOUT_CURRENCY_ENTRY = 0x00000200;
       
    86     // mask for simple case country entries
       
    87     private static final int SIMPLE_CASE_COUNTRY_MASK = 0x00000000;
       
    88     // mask for simple case country entry final character
       
    89     private static final int SIMPLE_CASE_COUNTRY_FINAL_CHAR_MASK = 0x0000001F;
       
    90     // mask for simple case country entry default currency digits
       
    91     private static final int SIMPLE_CASE_COUNTRY_DEFAULT_DIGITS_MASK = 0x000001E0;
       
    92     // shift count for simple case country entry default currency digits
       
    93     private static final int SIMPLE_CASE_COUNTRY_DEFAULT_DIGITS_SHIFT = 5;
       
    94     // maximum number for simple case country entry default currency digits
       
    95     private static final int SIMPLE_CASE_COUNTRY_MAX_DEFAULT_DIGITS = 9;
       
    96     // mask for special case country entries
       
    97     private static final int SPECIAL_CASE_COUNTRY_MASK = 0x00000200;
       
    98     // mask for special case country index
       
    99     private static final int SPECIAL_CASE_COUNTRY_INDEX_MASK = 0x0000001F;
       
   100     // delta from entry index component in main table to index into special case tables
       
   101     private static final int SPECIAL_CASE_COUNTRY_INDEX_DELTA = 1;
       
   102     // mask for distinguishing simple and special case countries
       
   103     private static final int COUNTRY_TYPE_MASK = SIMPLE_CASE_COUNTRY_MASK | SPECIAL_CASE_COUNTRY_MASK;
       
   104     // mask for the numeric code of the currency
       
   105     private static final int NUMERIC_CODE_MASK = 0x000FFC00;
       
   106     // shift count for the numeric code of the currency
       
   107     private static final int NUMERIC_CODE_SHIFT = 10;
       
   108 
       
   109     // generated data
       
   110     private static int[] mainTable = new int[A_TO_Z * A_TO_Z];
       
   111 
       
   112     private static final int maxSpecialCases = 30;
       
   113     private static int specialCaseCount = 0;
       
   114     private static long[] specialCaseCutOverTimes = new long[maxSpecialCases];
       
   115     private static String[] specialCaseOldCurrencies = new String[maxSpecialCases];
       
   116     private static String[] specialCaseNewCurrencies = new String[maxSpecialCases];
       
   117     private static int[] specialCaseOldCurrenciesDefaultFractionDigits = new int[maxSpecialCases];
       
   118     private static int[] specialCaseNewCurrenciesDefaultFractionDigits = new int[maxSpecialCases];
       
   119     private static int[] specialCaseOldCurrenciesNumericCode = new int[maxSpecialCases];
       
   120     private static int[] specialCaseNewCurrenciesNumericCode = new int[maxSpecialCases];
       
   121 
       
   122     private static final int maxOtherCurrencies = 128;
       
   123     private static int otherCurrenciesCount = 0;
       
   124     private static String[] otherCurrencies = new String[maxOtherCurrencies];
       
   125     private static int[] otherCurrenciesDefaultFractionDigits = new int[maxOtherCurrencies];
       
   126     private static int[] otherCurrenciesNumericCode= new int[maxOtherCurrencies];
       
   127 
       
   128     // date format for parsing cut-over times
       
   129     private static SimpleDateFormat format;
       
   130 
       
   131     // Minor Units
       
   132     private static String[] currenciesWithDefinedMinorUnitDecimals =
       
   133         new String[SIMPLE_CASE_COUNTRY_MAX_DEFAULT_DIGITS + 1];
       
   134     private static String currenciesWithMinorUnitsUndefined;
       
   135 
       
   136     public static void main(String[] args) {
       
   137 
       
   138         // Look for "-o outputfilename" option
       
   139         if ( args.length == 2 && args[0].equals("-o") ) {
       
   140             try {
       
   141                 out = new DataOutputStream(new FileOutputStream(args[1]));
       
   142             } catch ( FileNotFoundException e ) {
       
   143                 System.err.println("Error: " + e.getMessage());
       
   144                 e.printStackTrace(System.err);
       
   145                 System.exit(1);
       
   146             }
       
   147         } else {
       
   148             System.err.println("Error: Illegal arg count");
       
   149             System.exit(1);
       
   150         }
       
   151 
       
   152         format = new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss", Locale.US);
       
   153         format.setTimeZone(TimeZone.getTimeZone("GMT"));
       
   154         format.setLenient(false);
       
   155 
       
   156         try {
       
   157             readInput();
       
   158             buildMainAndSpecialCaseTables();
       
   159             buildOtherTables();
       
   160             writeOutput();
       
   161             out.flush();
       
   162             out.close();
       
   163         } catch (Exception e) {
       
   164             System.err.println("Error: " + e.getMessage());
       
   165             e.printStackTrace(System.err);
       
   166             System.exit(1);
       
   167         }
       
   168     }
       
   169 
       
   170     private static void readInput() throws IOException {
       
   171         currencyData = new Properties();
       
   172         currencyData.load(System.in);
       
   173 
       
   174         // initialize other lookup strings
       
   175         formatVersion = (String) currencyData.get("formatVersion");
       
   176         dataVersion = (String) currencyData.get("dataVersion");
       
   177         validCurrencyCodes = (String) currencyData.get("all");
       
   178         for (int i = 0; i <= SIMPLE_CASE_COUNTRY_MAX_DEFAULT_DIGITS; i++) {
       
   179             currenciesWithDefinedMinorUnitDecimals[i]
       
   180                 = (String) currencyData.get("minor"+i);
       
   181         }
       
   182         currenciesWithMinorUnitsUndefined  = (String) currencyData.get("minorUndefined");
       
   183         if (formatVersion == null ||
       
   184                 dataVersion == null ||
       
   185                 validCurrencyCodes == null ||
       
   186                 currenciesWithMinorUnitsUndefined == null) {
       
   187             throw new NullPointerException("not all required data is defined in input");
       
   188         }
       
   189     }
       
   190 
       
   191     private static void buildMainAndSpecialCaseTables() throws Exception {
       
   192         for (int first = 0; first < A_TO_Z; first++) {
       
   193             for (int second = 0; second < A_TO_Z; second++) {
       
   194                 char firstChar = (char) ('A' + first);
       
   195                 char secondChar = (char) ('A' + second);
       
   196                 String countryCode = (new StringBuffer()).append(firstChar).append(secondChar).toString();
       
   197                 String currencyInfo = (String) currencyData.get(countryCode);
       
   198                 int tableEntry = 0;
       
   199                 if (currencyInfo == null) {
       
   200                     // no entry -> must be invalid ISO 3166 country code
       
   201                     tableEntry = INVALID_COUNTRY_ENTRY;
       
   202                 } else {
       
   203                     int length = currencyInfo.length();
       
   204                     if (length == 0) {
       
   205                         // special case: country without currency
       
   206                        tableEntry = COUNTRY_WITHOUT_CURRENCY_ENTRY;
       
   207                     } else if (length == 3) {
       
   208                         // valid currency
       
   209                         if (currencyInfo.charAt(0) == firstChar && currencyInfo.charAt(1) == secondChar) {
       
   210                             checkCurrencyCode(currencyInfo);
       
   211                             int digits = getDefaultFractionDigits(currencyInfo);
       
   212                             if (digits < 0 || digits > SIMPLE_CASE_COUNTRY_MAX_DEFAULT_DIGITS) {
       
   213                                 throw new RuntimeException("fraction digits out of range for " + currencyInfo);
       
   214                             }
       
   215                             int numericCode= getNumericCode(currencyInfo);
       
   216                             if (numericCode < 0 || numericCode >= 1000 ) {
       
   217                                 throw new RuntimeException("numeric code out of range for " + currencyInfo);
       
   218                             }
       
   219                             tableEntry = SIMPLE_CASE_COUNTRY_MASK
       
   220                                     | (currencyInfo.charAt(2) - 'A')
       
   221                                     | (digits << SIMPLE_CASE_COUNTRY_DEFAULT_DIGITS_SHIFT)
       
   222                                     | (numericCode << NUMERIC_CODE_SHIFT);
       
   223                         } else {
       
   224                             tableEntry = SPECIAL_CASE_COUNTRY_MASK | (makeSpecialCaseEntry(currencyInfo) + SPECIAL_CASE_COUNTRY_INDEX_DELTA);
       
   225                         }
       
   226                     } else {
       
   227                         tableEntry = SPECIAL_CASE_COUNTRY_MASK | (makeSpecialCaseEntry(currencyInfo) + SPECIAL_CASE_COUNTRY_INDEX_DELTA);
       
   228                     }
       
   229                 }
       
   230                 mainTable[first * A_TO_Z + second] = tableEntry;
       
   231             }
       
   232         }
       
   233     }
       
   234 
       
   235     private static int getDefaultFractionDigits(String currencyCode) {
       
   236         for (int i = 0; i <= SIMPLE_CASE_COUNTRY_MAX_DEFAULT_DIGITS; i++) {
       
   237             if (Objects.nonNull(currenciesWithDefinedMinorUnitDecimals[i]) &&
       
   238                 currenciesWithDefinedMinorUnitDecimals[i].indexOf(currencyCode) != -1) {
       
   239                 return i;
       
   240             }
       
   241         }
       
   242 
       
   243         if (currenciesWithMinorUnitsUndefined.indexOf(currencyCode) != -1) {
       
   244             return -1;
       
   245         } else {
       
   246             return 2;
       
   247         }
       
   248     }
       
   249 
       
   250     private static int getNumericCode(String currencyCode) {
       
   251         int index = validCurrencyCodes.indexOf(currencyCode);
       
   252         String numericCode = validCurrencyCodes.substring(index + 3, index + 6);
       
   253         return Integer.parseInt(numericCode);
       
   254     }
       
   255 
       
   256     static HashMap<String, Integer> specialCaseMap = new HashMap<>();
       
   257 
       
   258     private static int makeSpecialCaseEntry(String currencyInfo) throws Exception {
       
   259         Integer oldEntry = specialCaseMap.get(currencyInfo);
       
   260         if (oldEntry != null) {
       
   261             return oldEntry.intValue();
       
   262         }
       
   263         if (specialCaseCount == maxSpecialCases) {
       
   264             throw new RuntimeException("too many special cases");
       
   265         }
       
   266         if (currencyInfo.length() == 3) {
       
   267             checkCurrencyCode(currencyInfo);
       
   268             specialCaseCutOverTimes[specialCaseCount] = Long.MAX_VALUE;
       
   269             specialCaseOldCurrencies[specialCaseCount] = currencyInfo;
       
   270             specialCaseOldCurrenciesDefaultFractionDigits[specialCaseCount] = getDefaultFractionDigits(currencyInfo);
       
   271             specialCaseOldCurrenciesNumericCode[specialCaseCount] = getNumericCode(currencyInfo);
       
   272             specialCaseNewCurrencies[specialCaseCount] = null;
       
   273             specialCaseNewCurrenciesDefaultFractionDigits[specialCaseCount] = 0;
       
   274             specialCaseNewCurrenciesNumericCode[specialCaseCount] = 0;
       
   275         } else {
       
   276             int length = currencyInfo.length();
       
   277             if (currencyInfo.charAt(3) != ';' ||
       
   278                     currencyInfo.charAt(length - 4) != ';') {
       
   279                 throw new RuntimeException("invalid currency info: " + currencyInfo);
       
   280             }
       
   281             String oldCurrency = currencyInfo.substring(0, 3);
       
   282             String newCurrency = currencyInfo.substring(length - 3, length);
       
   283             checkCurrencyCode(oldCurrency);
       
   284             checkCurrencyCode(newCurrency);
       
   285             String timeString = currencyInfo.substring(4, length - 4);
       
   286             long time = format.parse(timeString).getTime();
       
   287             if (Math.abs(time - System.currentTimeMillis()) > ((long) 10) * 365 * 24 * 60 * 60 * 1000) {
       
   288                 throw new RuntimeException("time is more than 10 years from present: " + time);
       
   289             }
       
   290             specialCaseCutOverTimes[specialCaseCount] = time;
       
   291             specialCaseOldCurrencies[specialCaseCount] = oldCurrency;
       
   292             specialCaseOldCurrenciesDefaultFractionDigits[specialCaseCount] = getDefaultFractionDigits(oldCurrency);
       
   293             specialCaseOldCurrenciesNumericCode[specialCaseCount] = getNumericCode(oldCurrency);
       
   294             specialCaseNewCurrencies[specialCaseCount] = newCurrency;
       
   295             specialCaseNewCurrenciesDefaultFractionDigits[specialCaseCount] = getDefaultFractionDigits(newCurrency);
       
   296             specialCaseNewCurrenciesNumericCode[specialCaseCount] = getNumericCode(newCurrency);
       
   297         }
       
   298         specialCaseMap.put(currencyInfo, new Integer(specialCaseCount));
       
   299         return specialCaseCount++;
       
   300     }
       
   301 
       
   302     private static void buildOtherTables() {
       
   303         if (validCurrencyCodes.length() % 7 != 6) {
       
   304             throw new RuntimeException("\"all\" entry has incorrect size");
       
   305         }
       
   306         for (int i = 0; i < (validCurrencyCodes.length() + 1) / 7; i++) {
       
   307             if (i > 0 && validCurrencyCodes.charAt(i * 7 - 1) != '-') {
       
   308                 throw new RuntimeException("incorrect separator in \"all\" entry");
       
   309             }
       
   310             String currencyCode = validCurrencyCodes.substring(i * 7, i * 7 + 3);
       
   311             int numericCode = Integer.parseInt(
       
   312                 validCurrencyCodes.substring(i * 7 + 3, i * 7 + 6));
       
   313             checkCurrencyCode(currencyCode);
       
   314             int tableEntry = mainTable[(currencyCode.charAt(0) - 'A') * A_TO_Z + (currencyCode.charAt(1) - 'A')];
       
   315             if (tableEntry == INVALID_COUNTRY_ENTRY ||
       
   316                     (tableEntry & SPECIAL_CASE_COUNTRY_MASK) != 0 ||
       
   317                     (tableEntry & SIMPLE_CASE_COUNTRY_FINAL_CHAR_MASK) != (currencyCode.charAt(2) - 'A')) {
       
   318                 if (otherCurrenciesCount == maxOtherCurrencies) {
       
   319                     throw new RuntimeException("too many other currencies");
       
   320                 }
       
   321                 otherCurrencies[otherCurrenciesCount] = currencyCode;
       
   322                 otherCurrenciesDefaultFractionDigits[otherCurrenciesCount] = getDefaultFractionDigits(currencyCode);
       
   323                 otherCurrenciesNumericCode[otherCurrenciesCount] = getNumericCode(currencyCode);
       
   324                 otherCurrenciesCount++;
       
   325             }
       
   326         }
       
   327     }
       
   328 
       
   329     private static void checkCurrencyCode(String currencyCode) {
       
   330         if (currencyCode.length() != 3) {
       
   331             throw new RuntimeException("illegal length for currency code: " + currencyCode);
       
   332         }
       
   333         for (int i = 0; i < 3; i++) {
       
   334             char aChar = currencyCode.charAt(i);
       
   335             if ((aChar < 'A' || aChar > 'Z') && !currencyCode.equals("XB5")) {
       
   336                 throw new RuntimeException("currency code contains illegal character: " + currencyCode);
       
   337             }
       
   338         }
       
   339         if (validCurrencyCodes.indexOf(currencyCode) == -1) {
       
   340             throw new RuntimeException("currency code not listed as valid: " + currencyCode);
       
   341         }
       
   342     }
       
   343 
       
   344     private static void writeOutput() throws IOException {
       
   345         out.writeInt(MAGIC_NUMBER);
       
   346         out.writeInt(Integer.parseInt(formatVersion));
       
   347         out.writeInt(Integer.parseInt(dataVersion));
       
   348         writeIntArray(mainTable, mainTable.length);
       
   349         out.writeInt(specialCaseCount);
       
   350         writeSpecialCaseEntries();
       
   351         out.writeInt(otherCurrenciesCount);
       
   352         writeOtherCurrencies();
       
   353     }
       
   354 
       
   355     private static void writeIntArray(int[] ia, int count) throws IOException {
       
   356         for (int i = 0; i < count; i++) {
       
   357             out.writeInt(ia[i]);
       
   358         }
       
   359     }
       
   360 
       
   361     private static void writeSpecialCaseEntries() throws IOException {
       
   362         for (int index = 0; index < specialCaseCount; index++) {
       
   363             out.writeLong(specialCaseCutOverTimes[index]);
       
   364             String str = (specialCaseOldCurrencies[index] != null)
       
   365                     ? specialCaseOldCurrencies[index] : "";
       
   366             out.writeUTF(str);
       
   367             str = (specialCaseNewCurrencies[index] != null)
       
   368                     ? specialCaseNewCurrencies[index] : "";
       
   369             out.writeUTF(str);
       
   370             out.writeInt(specialCaseOldCurrenciesDefaultFractionDigits[index]);
       
   371             out.writeInt(specialCaseNewCurrenciesDefaultFractionDigits[index]);
       
   372             out.writeInt(specialCaseOldCurrenciesNumericCode[index]);
       
   373             out.writeInt(specialCaseNewCurrenciesNumericCode[index]);
       
   374         }
       
   375     }
       
   376 
       
   377     private static void writeOtherCurrencies() throws IOException {
       
   378         for (int index = 0; index < otherCurrenciesCount; index++) {
       
   379             String str = (otherCurrencies[index] != null)
       
   380                     ? otherCurrencies[index] : "";
       
   381             out.writeUTF(str);
       
   382             out.writeInt(otherCurrenciesDefaultFractionDigits[index]);
       
   383             out.writeInt(otherCurrenciesNumericCode[index]);
       
   384         }
       
   385     }
       
   386 
       
   387 }