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