ValidateISO4217.java revision 3646:84e339f1033b
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
52import java.io.*;
53import java.text.*;
54import java.util.*;
55
56public 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        try (FileReader fr = new FileReader(new File(System.getProperty("test.src", "."), datafile));
115             BufferedReader in = new BufferedReader(fr))
116        {
117            String line;
118            SimpleDateFormat format = null;
119
120            while ((line = in.readLine()) != null) {
121                if (line.length() == 0 || line.charAt(0) == '#') {
122                    continue;
123                }
124
125                StringTokenizer tokens = new StringTokenizer(line, "\t");
126                String country = tokens.nextToken();
127                if (country.length() != 2) {
128                    continue;
129                }
130
131                String currency;
132                String numeric;
133                String minorUnit;
134                int tokensCount = tokens.countTokens();
135                if (tokensCount < 3) {
136                    currency = "";
137                    numeric = "0";
138                    minorUnit = "0";
139                } else {
140                    currency = tokens.nextToken();
141                    numeric = tokens.nextToken();
142                    minorUnit = tokens.nextToken();
143                    testCurrencies.add(Currency.getInstance(currency));
144
145                    // check for the cutover
146                    if (tokensCount > 3) {
147                        if (format == null) {
148                            format = new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss", Locale.US);
149                            format.setTimeZone(TimeZone.getTimeZone("GMT"));
150                            format.setLenient(false);
151                        }
152                        if (format.parse(tokens.nextToken()).getTime() <
153                            System.currentTimeMillis()) {
154                            currency = tokens.nextToken();
155                            numeric = tokens.nextToken();
156                            minorUnit = tokens.nextToken();
157                            testCurrencies.add(Currency.getInstance(currency));
158                        }
159                    }
160                }
161                int index = toIndex(country);
162                testCountryCurrency(country, currency, Integer.parseInt(numeric),
163                    Integer.parseInt(minorUnit), index);
164            }
165        }
166
167        for (int i = 0; i < additionalCodes.length; i++) {
168            int index = toIndex(additionalCodes[i][0]);
169            if (additionalCodes[i][1].length() != 0) {
170                testCountryCurrency(additionalCodes[i][0], additionalCodes[i][1],
171                    Integer.parseInt(additionalCodes[i][2]),
172                    Integer.parseInt(additionalCodes[i][3]), index);
173                testCurrencies.add(Currency.getInstance(additionalCodes[i][1]));
174            } else {
175                codes[index] = SKIPPED;
176            }
177        }
178    }
179
180    static int toIndex(String s) {
181        return ((s.charAt(0) - 'A') * ALPHA_NUM + s.charAt(1) - 'A');
182    }
183
184    static void testCountryCurrency(String country, String currencyCode,
185                                int numericCode, int digits, int index) {
186        if (currencyCode.length() == 0) {
187            return;
188        }
189        testCurrencyDefined(currencyCode, numericCode, digits);
190
191        Locale loc = new Locale("", country);
192        try {
193            Currency currency = Currency.getInstance(loc);
194            if (!currency.getCurrencyCode().equals(currencyCode)) {
195                System.err.println("Error: [" + country + ":" +
196                    loc.getDisplayCountry() + "] expected: " + currencyCode +
197                    ", got: " + currency.getCurrencyCode());
198                err = true;
199            }
200
201            if (codes[index] != UNDEFINED) {
202                System.out.println("Warning: [" + country + ":" +
203                    loc.getDisplayCountry() +
204                    "] multiple definitions. currency code=" + currencyCode);
205            }
206            codes[index] = DEFINED;
207        }
208        catch (Exception e) {
209            System.err.println("Error: " + e + ": Country=" + country);
210            err = true;
211        }
212    }
213
214    static void testCurrencyDefined(String currencyCode, int numericCode, int digits) {
215        try {
216            Currency currency = currency = Currency.getInstance(currencyCode);
217
218            if (currency.getNumericCode() != numericCode) {
219                System.err.println("Error: [" + currencyCode + "] expected: " +
220                    numericCode + "; got: " + currency.getNumericCode());
221                err = true;
222            }
223
224            if (currency.getDefaultFractionDigits() != digits) {
225                System.err.println("Error: [" + currencyCode + "] expected: " +
226                    digits + "; got: " + currency.getDefaultFractionDigits());
227                err = true;
228            }
229        }
230        catch (Exception e) {
231            System.err.println("Error: " + e + ": Currency code=" +
232                currencyCode);
233            err = true;
234        }
235    }
236
237    static void test2() {
238        for (int i = 0; i < ALPHA_NUM; i++) {
239            for (int j = 0; j < ALPHA_NUM; j++) {
240                char[] code = new char[2];
241                code[0] = (char)('A'+ i);
242                code[1] = (char)('A'+ j);
243                String country = new String(code);
244                boolean ex;
245
246                if (codes[toIndex(country)] == UNDEFINED) {
247                    ex = false;
248                    try {
249                        Currency.getInstance(new Locale("", country));
250                    }
251                    catch (IllegalArgumentException e) {
252                        ex = true;
253                    }
254                    if (!ex) {
255                        System.err.println("Error: This should be an undefined code and throw IllegalArgumentException: " +
256                            country);
257                        err = true;
258                    }
259                } else if (codes[toIndex(country)] == SKIPPED) {
260                    Currency cur = null;
261                    try {
262                        cur = Currency.getInstance(new Locale("", country));
263                    }
264                    catch (Exception e) {
265                        System.err.println("Error: " + e + ": Country=" +
266                            country);
267                        err = true;
268                    }
269                    if (cur != null) {
270                        System.err.println("Error: Currency.getInstance() for an this locale should return null: " +
271                            country);
272                        err = true;
273                    }
274                }
275            }
276        }
277    }
278
279    /**
280     * This test depends on test1(), where 'testCurrencies' set is constructed
281     */
282    static void getAvailableCurrenciesTest() {
283        Set<Currency> jreCurrencies = Currency.getAvailableCurrencies();
284
285        // add otherCodes
286        StringTokenizer st = new StringTokenizer(otherCodes, "-");
287        while (st.hasMoreTokens()) {
288            testCurrencies.add(Currency.getInstance(st.nextToken()));
289        }
290
291        if (!testCurrencies.containsAll(jreCurrencies)) {
292            System.err.print("Error: getAvailableCurrencies() returned extra currencies than expected: ");
293            jreCurrencies.removeAll(testCurrencies);
294            for (Currency c : jreCurrencies) {
295                System.err.print(" "+c);
296            }
297            System.err.println();
298            err = true;
299        }
300    }
301}
302