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 }