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 }