1 /*
   2  * Copyright (c) 2007, 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.
   8  *
   9  * This code is distributed in the hope that it will be useful, but WITHOUT
  10  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  11  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  12  * version 2 for more details (a copy is included in the LICENSE file that
  13  * accompanied this code).
  14  *
  15  * You should have received a copy of the GNU General Public License version
  16  * 2 along with this work; if not, write to the Free Software Foundation,
  17  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  18  *
  19  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  20  * or visit www.oracle.com if you need additional information or have any
  21  * questions.
  22  */
  23 /*
  24  * @test
  25  * @bug 4691089 4819436 4942982 5104960 6544471 6627549
  26  * @summary Validate ISO 4217 data for Currency class.
  27  */
  28 
  29 /*
  30  * ############################################################################
  31  *
  32  *  ValidateISO4217 is a tool to detect differences between the latest ISO 4217
  33  *  data and and Java's currency data which is based on ISO 4217.
  34  *  If there is a difference, the following file which includes currency data
  35  *  may need to be updated.
  36  *      src/share/classes/java/util/CurrencyData.properties
  37  *
  38  * ############################################################################
  39  *
  40  * 1) Make a golden-data file.
  41  *      From BSi's ISO4217 data (TABLE A1.doc), extract four (or eight, if currency is changing)
  42  *      fields and save as ./tablea1.txt.
  43  *        <Country code>\t<Currency code>\t<Numeric code>\t<Minor unit>[\t<Cutover Date>\t<new Currency code>\t<new Numeric code>\t<new Minor unit>]
  44  *      The Cutover Date is given in SimpleDateFormat's 'yyyy-MM-dd-HH-mm-ss' format in the GMT time zone.
  45  *
  46  * 2) Compile ValidateISO4217.java
  47  *
  48  * 3) Execute ValidateISO4217 as follows:
  49  *      java ValidateISO4217
  50  */
  51 
  52 import java.io.*;
  53 import java.text.*;
  54 import java.util.*;
  55 
  56 public class ValidateISO4217 {
  57 
  58     static final int ALPHA_NUM = 26;
  59 
  60     static final byte UNDEFINED = 0;
  61     static final byte DEFINED = 1;
  62     static final byte SKIPPED = 2;
  63 
  64     /* input files */
  65     static final String datafile = "tablea1.txt";
  66 
  67     /* alpha2-code table */
  68     static byte[] codes = new byte[ALPHA_NUM * ALPHA_NUM];
  69 
  70     static final String[][] additionalCodes = {
  71         /* Defined in ISO 4217 list, but don't have code and minor unit info. */
  72         {"AQ", "", "", "0"},    // Antarctica
  73 
  74         /*
  75          * Defined in ISO 4217 list, but don't have code and minor unit info in
  76          * it. On the othe hand, both code and minor unit are defined in
  77          * .properties file. I don't know why, though.
  78          */
  79         {"GS", "GBP", "826", "2"},      // South Georgia And The South Sandwich Islands
  80 
  81         /* Not defined in ISO 4217 list, but defined in .properties file. */
  82         {"AX", "EUR", "978", "2"},      // \u00c5LAND ISLANDS
  83         {"PS", "ILS", "376", "2"},      // Palestinian Territory, Occupied
  84 
  85         /* Not defined in ISO 4217 list, but added in ISO 3166 country code list */
  86         {"JE", "GBP", "826", "2"},      // Jersey
  87         {"GG", "GBP", "826", "2"},      // Guernsey
  88         {"IM", "GBP", "826", "2"},      // Isle of Man
  89         {"BL", "EUR", "978", "2"},      // Saint Barthelemy
  90         {"MF", "EUR", "978", "2"},      // Saint Martin
  91     };
  92 
  93     /* Codes that are obsolete, do not have related country */
  94     static final String otherCodes =
  95         "ADP-AFA-ATS-AYM-BEF-BGL-BOV-BYB-CLF-CYP-DEM-ESP-FIM-FRF-GRD-GWP-IEP-ITL-LUF-MGF-MTL-MXV-NLG-PTE-RUR-SDD-SIT-SRG-TPE-TRL-VEF-USN-USS-XAG-XAU-XBA-XBB-XBC-XBD-XDR-XFO-XFU-XPD-XPT-XTS-XXX-YUM-ZWN";
  96 
  97     static boolean err = false;
  98 
  99     static Set<Currency> testCurrencies = new HashSet<Currency>();
 100 
 101     public static void main(String[] args) throws Exception {
 102         CheckDataVersion.check();
 103         test1();
 104         test2();
 105         getAvailableCurrenciesTest();
 106 
 107         if (err) {
 108             throw new RuntimeException("Failed: Validation ISO 4217 data");
 109         }
 110     }
 111 
 112     static void test1() throws Exception {
 113 
 114         FileReader fr = new FileReader(new File(System.getProperty("test.src", "."), datafile));
 115         BufferedReader in = new BufferedReader(fr);
 116         String line;
 117         SimpleDateFormat format = null;
 118 
 119         while ((line = in.readLine()) != null) {
 120             if (line.length() == 0 || line.charAt(0) == '#') {
 121                 continue;
 122             }
 123 
 124             StringTokenizer tokens = new StringTokenizer(line, "\t");
 125             String country = tokens.nextToken();
 126             if (country.length() != 2) {
 127                 continue;
 128             }
 129 
 130             String currency;
 131             String numeric;
 132             String minorUnit;
 133             int tokensCount = tokens.countTokens();
 134             if (tokensCount < 3) {
 135                 currency = "";
 136                 numeric = "0";
 137                 minorUnit = "0";
 138             } else {
 139                 currency = tokens.nextToken();
 140                 numeric = tokens.nextToken();
 141                 minorUnit = tokens.nextToken();
 142                 testCurrencies.add(Currency.getInstance(currency));
 143 
 144                 // check for the cutover
 145                 if (tokensCount > 3) {
 146                     if (format == null) {
 147                         format = new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss", Locale.US);
 148                         format.setTimeZone(TimeZone.getTimeZone("GMT"));
 149                         format.setLenient(false);
 150                     }
 151                     if (format.parse(tokens.nextToken()).getTime() <
 152                         System.currentTimeMillis()) {
 153                         currency = tokens.nextToken();
 154                         numeric = tokens.nextToken();
 155                         minorUnit = tokens.nextToken();
 156                         testCurrencies.add(Currency.getInstance(currency));
 157                     }
 158                 }
 159             }
 160             int index = toIndex(country);
 161             testCountryCurrency(country, currency, Integer.parseInt(numeric),
 162                 Integer.parseInt(minorUnit), index);
 163         }
 164         in.close();
 165 
 166         for (int i = 0; i < additionalCodes.length; i++) {
 167             int index = toIndex(additionalCodes[i][0]);
 168             if (additionalCodes[i][1].length() != 0) {
 169                 testCountryCurrency(additionalCodes[i][0], additionalCodes[i][1],
 170                     Integer.parseInt(additionalCodes[i][2]),
 171                     Integer.parseInt(additionalCodes[i][3]), index);
 172                 testCurrencies.add(Currency.getInstance(additionalCodes[i][1]));
 173             } else {
 174                 codes[index] = SKIPPED;
 175             }
 176         }
 177     }
 178 
 179     static int toIndex(String s) {
 180         return ((s.charAt(0) - 'A') * ALPHA_NUM + s.charAt(1) - 'A');
 181     }
 182 
 183     static void testCountryCurrency(String country, String currencyCode,
 184                                 int numericCode, int digits, int index) {
 185         if (currencyCode.length() == 0) {
 186             return;
 187         }
 188         testCurrencyDefined(currencyCode, numericCode, digits);
 189 
 190         Locale loc = new Locale("", country);
 191         try {
 192             Currency currency = Currency.getInstance(loc);
 193             if (!currency.getCurrencyCode().equals(currencyCode)) {
 194                 System.err.println("Error: [" + country + ":" +
 195                     loc.getDisplayCountry() + "] expected: " + currencyCode +
 196                     ", got: " + currency.getCurrencyCode());
 197                 err = true;
 198             }
 199 
 200             if (codes[index] != UNDEFINED) {
 201                 System.out.println("Warning: [" + country + ":" +
 202                     loc.getDisplayCountry() +
 203                     "] multiple definitions. currency code=" + currencyCode);
 204             }
 205             codes[index] = DEFINED;
 206         }
 207         catch (Exception e) {
 208             System.err.println("Error: " + e + ": Country=" + country);
 209             err = true;
 210         }
 211     }
 212 
 213     static void testCurrencyDefined(String currencyCode, int numericCode, int digits) {
 214         try {
 215             Currency currency = currency = Currency.getInstance(currencyCode);
 216 
 217             if (currency.getNumericCode() != numericCode) {
 218                 System.err.println("Error: [" + currencyCode + "] expected: " +
 219                     numericCode + "; got: " + currency.getNumericCode());
 220                 err = true;
 221             }
 222 
 223             if (currency.getDefaultFractionDigits() != digits) {
 224                 System.err.println("Error: [" + currencyCode + "] expected: " +
 225                     digits + "; got: " + currency.getDefaultFractionDigits());
 226                 err = true;
 227             }
 228         }
 229         catch (Exception e) {
 230             System.err.println("Error: " + e + ": Currency code=" +
 231                 currencyCode);
 232             err = true;
 233         }
 234     }
 235 
 236     static void test2() {
 237         for (int i = 0; i < ALPHA_NUM; i++) {
 238             for (int j = 0; j < ALPHA_NUM; j++) {
 239                 char[] code = new char[2];
 240                 code[0] = (char)('A'+ i);
 241                 code[1] = (char)('A'+ j);
 242                 String country = new String(code);
 243                 boolean ex;
 244 
 245                 if (codes[toIndex(country)] == UNDEFINED) {
 246                     ex = false;
 247                     try {
 248                         Currency.getInstance(new Locale("", country));
 249                     }
 250                     catch (IllegalArgumentException e) {
 251                         ex = true;
 252                     }
 253                     if (!ex) {
 254                         System.err.println("Error: This should be an undefined code and throw IllegalArgumentException: " +
 255                             country);
 256                         err = true;
 257                     }
 258                 } else if (codes[toIndex(country)] == SKIPPED) {
 259                     Currency cur = null;
 260                     try {
 261                         cur = Currency.getInstance(new Locale("", country));
 262                     }
 263                     catch (Exception e) {
 264                         System.err.println("Error: " + e + ": Country=" +
 265                             country);
 266                         err = true;
 267                     }
 268                     if (cur != null) {
 269                         System.err.println("Error: Currency.getInstance() for an this locale should return null: " +
 270                             country);
 271                         err = true;
 272                     }
 273                 }
 274             }
 275         }
 276     }
 277 
 278     /**
 279      * This test depends on test1(), where 'testCurrencies' set is constructed
 280      */
 281     static void getAvailableCurrenciesTest() {
 282         Set<Currency> jreCurrencies = Currency.getAvailableCurrencies();
 283 
 284         // add otherCodes
 285         StringTokenizer st = new StringTokenizer(otherCodes, "-");
 286         while (st.hasMoreTokens()) {
 287             testCurrencies.add(Currency.getInstance(st.nextToken()));
 288         }
 289 
 290         if (!testCurrencies.containsAll(jreCurrencies)) {
 291             System.err.print("Error: getAvailableCurrencies() returned extra currencies than expected: ");
 292             jreCurrencies.removeAll(testCurrencies);
 293             for (Currency c : jreCurrencies) {
 294                 System.err.print(" "+c);
 295             }
 296             System.err.println();
 297             err = true;
 298         }
 299     }
 300 }