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