aboutsummaryrefslogtreecommitdiff
path: root/test/sun/text/resources/LocaleDataTest.java
blob: 94e92b95db28839d3ed7c0ef8742f03feedd1dfd (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
/*
 * Copyright (c) 2007, 2015, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */
/*
 * @test
 * @bug 4052473 4052679 4055602 4066550 4067619 4068012 4068073 4070174 4070452
 *      4070178 4070450 4070695 4070725 4070795 4071003 4071183 4071782 4072013
 *      4072388 4072773 4075404 4084356 4087238 4092361 4094033 4094371 4098518
 *      4099810 4103218 4103220 4103861 4112136 4113638 4113654 4117054 4122468
 *      4122840 4139860 4156708 4175306 4215747 4209960 4290801 4900884 4942982
 *      4518811 4945388 4936845 4794068 4461740 4965260 4984277 4826794 5032580
 *      5102005 5074431 6182685 6208712 6277020 6245766 6351682 6386647 6379382
 *      6414459 6455680 6498742 6558863 6488119 6547501 6497154 6558856 6481177
 *      6379214 6485516 6486607 4225362 4494727 6533691 6531591 6531593 6570259
 *      6509039 6609737 6610748 6645271 6507067 6873931 6450945 6645268 6646611
 *      6645405 6650730 6910489 6573250 6870908 6585666 6716626 6914413 6916787
 *      6919624 6998391 7019267 7020960 7025837 7020583 7036905 7066203 7101495
 *      7003124 7085757 7028073 7171028 7189611 8000983 7195759 8004489 8006509
 *      7114053 7074882 7040556 8013836 8021121 6192407 6931564 8027695 7090826
 *      8017142 8037343 8055222 8042126 8074791 8075173 8080774 8129361
 *      8145952 8164784
 * @summary Verify locale data
 *
 */

/*
 *
 * (C) Copyright Taligent, Inc. 1996, 1997 - All Rights Reserved
 * (C) Copyright IBM Corp. 1996 - 1998 - All Rights Reserved
 *
 * Portions copyright (c) 2007 Sun Microsystems, Inc.
 * All Rights Reserved.
 *
 * The original version of this source code and documentation
 * is copyrighted and owned by Taligent, Inc., a wholly-owned
 * subsidiary of IBM. These materials are provided under terms
 * of a License Agreement between Taligent and Sun. This technology
 * is protected by multiple US and International patents.
 *
 * This notice and attribution to Taligent may not be removed.
 * Taligent is a registered trademark of Taligent, Inc.
 *
 * Permission to use, copy, modify, and distribute this software
 * and its documentation for NON-COMMERCIAL purposes and without
 * fee is hereby granted provided that this copyright notice
 * appears in all copies. Please refer to the file "copyright.html"
 * for further important copyright and licensing information.
 *
 * SUN MAKES NO REPRESENTATIONS OR WARRANTIES ABOUT THE SUITABILITY OF
 * THE SOFTWARE, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
 * TO THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
 * PARTICULAR PURPOSE, OR NON-INFRINGEMENT. SUN SHALL NOT BE LIABLE FOR
 * ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF USING, MODIFYING OR
 * DISTRIBUTING THIS SOFTWARE OR ITS DERIVATIVES.
 *
 */

/*    This test is a generalized test for verifying changes to the locale data.
 *    It is driven by an external file that specifies the particular pieces of locale
 *    data to check.  That file is in .properties file format: a series of key/value
 *    pairs delimited by newline characters, with the keys separated from the values
 *    by = signs.  The keys are similar in syntax to a Unix pathname, with keys at
 *    successive levels of containment in the resource-data hierarchy separated by
 *    slashes.  The file is in ISO 8859-1 encoding, with control characters and
 *    non-ASCII characters denoted with backslash-u escape sequences.  The program also allows
 *    blank lines and comment lines to be interspersed with the data.  Comment lines
 *    begin with '#'.
 *
 *    A data file for this test would look something like this:<pre>
 *        FormatData//MonthNames/0=January
 *        FormatData//MonthNames/1=February
 *        LocaleNames//US=United States
 *        LocaleNames//FR=France
 *        FormatData/fr_FR/MonthNames/0=janvier
 *        FormatData/fr_FR/MonthNames/1=f\u00e9vrier
 *        LocaleNames/fr_FR/US=\u00c9tats-Unis
 *        LocaleNames/fr_FR/FR=France</pre>
 *
 *    You can use language tag with '-' in locale field like this:<pre>
 *        LocaleNames/sr-Latn/SR=Surinam
 *        FormatData/sr-Latn-BA/DayNames/2=utorak</pre>
 *
 *    The command-line syntax of this test is
 *        <tt>java LocaleDataTest [-w] [{ -s | <filename> }]</tt>
 *
 *    This program always sends its results to standard output.   If -w is not specified,
 *    this program prints out only the differences between the data file and the actual
 *    resource data.  If -w is specified, the program prints out every entry, comment,
 *    and blank line from the data file.  Where there is a difference between the data
 *    file and the resource data, the data is the data from the resources.  This feature
 *    can be used to quickly generate a new data file.
 *
 *    The user can specify an optional filename or -s.  If the user specifies a filename,
 *    the program uses that file as the data file.  If the user specifies -s, the program
 *    reads its input from standard input rather than from a file.  If the user specifies
 *    neither, the program reads its input from a file called LocaleData in the same
 *    directory the program itself resides in.
 *
 *    The -nothrow option prevents the program from throwing an exception when it
 *    gets an error.  -w implies -nothrow.
 *
 *    Other command-line options can be specified, but are ignored.
 *
 *    It's important to note what this test will NOT test.  Certain changes to the locale
 *    data are meant to have certain effects on the internationalization frameworks.  For
 *    instance, we could ensure round-trip formatting/parsing integrity for the full
 *    date/time format of SimpleDateFormat by making sure that the full date and time
 *    patterns include sufficient data.  The test of this is not whether changes were
 *    made to the locale data; it's whether using this data gives round-trip integrity.
 *    Likewise, changing the currency patterns to use \u00a4 instead of local currency
 *    symbols isn't something that can be tested by this test; instead, you want to
 *    actually format currency values and make sure the proper currency symbol was used.
 *
 *    This test by itself doesn't do an exhaustive comparison of locale data.  It is
 *    possible to do this manually, however:  Use the GenerateKeyList tool to produce
 *    a complete list of keys for the two versions of the locales you want to compare,
 *    and then diff them.  This will flag additions and deletions.  Generate a data file
 *    for the base version of the data using the -w option and the output from
 *    GenerateKeyList, and then use the resultant file as the data file when you run
 *    this test against the new version of the data.
 */

import java.io.*;
import java.text.*;
import java.util.Locale;
import java.util.ResourceBundle;
import java.util.ResourceBundle.Control;
import java.util.MissingResourceException;

public class LocaleDataTest
{
    static final String TEXT_RESOURCES_PACKAGE ="sun.text.resources";
    static final String UTIL_RESOURCES_PACKAGE ="sun.util.resources";

    public static void main(String[] args) throws Exception {

        // set up our flags and our input and output streams based on the
        // command-line arguments (exceptions generated here will propagate out
        // to the environment)
        BufferedReader in = null;
        PrintWriter out = null;
        boolean writeNewFile = false;
        boolean doThrow = true;

        for (int i = 0; i < args.length; i++) {
            if (args[i].equals("-w")) {
                writeNewFile = true;
                doThrow = false;
            }

            else if (args[i].equals("-nothrow"))
                doThrow = false;

            else if (args[i].equals("-s") && in == null)
                in = new BufferedReader(new EscapeReader(new InputStreamReader(System.in,
                                "ISO8859_1")));
            else if (!args[i].startsWith("-") && in == null)
                in = new BufferedReader(new EscapeReader(new InputStreamReader(new
                                FileInputStream(args[i]), "ISO8859_1")));
        }
        if (in == null) {
            File localeData = new File(System.getProperty("test.src", "."), "LocaleData");
            in = new BufferedReader(new EscapeReader(new InputStreamReader(new
                            FileInputStream(localeData), "ISO8859_1")));
        }
        out = new PrintWriter(new EscapeWriter(new OutputStreamWriter(System.out,
                        "ISO8859_1")), true);

        // perform the actual test
        int errorCount = doTest(in, out, writeNewFile);

        // write out the error count, and throw an exception out into the environment
        // if there were any errors
        if (errorCount != 0) {
            if (!writeNewFile)
                out.println("Test failed.  " + errorCount + " errors.");
            if (doThrow)
                throw new Exception("Test failed.  " + errorCount + " errors.");
        }
        else if (!writeNewFile)
            out.println("Test passed.");

        in.close();
        out.close();
    }

    static int doTest(BufferedReader in, PrintWriter out, boolean writeNewFile)
                    throws Exception {
        int errorCount = 0;

        String key = null;
        String expectedValue = null;
        String line = in.readLine();
        while (line != null) {
            if (line.startsWith("#") || line.length() == 0) {
                if (writeNewFile)
                    out.println(line);
            }

            else {
                int index  = line.indexOf("=");
                if (index == -1) {
                    key = line;
                    expectedValue = "";
                }
                else {
                    key = line.substring(0, index);
                    if (index + 1 == line.length())
                        expectedValue = "";
                    else
                        expectedValue = line.substring(index + 1);
                }
                if (!processLine(key, expectedValue, out, writeNewFile))
                    ++errorCount;
            }
            line = in.readLine();
        }
        return errorCount;
    }

    static boolean processLine(String key, String expectedValue, PrintWriter out,
                    boolean writeNewFile) throws Exception {
        String rbName, localeName, resTag, qualifier;
        String language = "", country = "", variant = "";
        int index, oldIndex;

        index = key.indexOf("/");
        if (index == -1 || index + 1 == key.length())
            throw new Exception("Malformed input file: no slashes in \"" + key + "\"");
        rbName = key.substring(0, index);

        oldIndex = index + 1;
        index = key.indexOf("/", oldIndex);
        if (index == -1 || index + 1 == key.length())
            throw new Exception("Malformed input file: \"" + key + "\" is missing locale name");
        localeName = key.substring(oldIndex, index);
        boolean use_tag = localeName.indexOf("-") != -1;

        if (use_tag == false && localeName.length() > 0) {
            language = localeName.substring(0, 2);
            if (localeName.length() > 3) {
                country = localeName.substring(3, 5);
                if (localeName.length() > 5)
                    variant = localeName.substring(6);
            }
        }

        oldIndex = index + 1;
        index = key.indexOf("/", oldIndex);
        if (index == -1)
            index = key.length();
        resTag = key.substring(oldIndex, index);

        // TimeZone name may have "/" in it, for example "Asia/Taipei", so use "Asia\/Taipei in LocaleData.
        if(resTag.endsWith("\\")) {
            resTag = resTag.substring(0, resTag.length() - 1);
            oldIndex = index;
            index = key.indexOf("/", oldIndex + 1);
            if (index == -1) index = key.length();
            resTag += key.substring(oldIndex, index);
        }

        if (index < key.length() - 1)
            qualifier = key.substring(index + 1);
        else
            qualifier = "";

        String retrievedValue = null;
        Object resource = null;
        try {
            String fullName = null;
            if (rbName.equals("CalendarData")
                    || rbName.equals("CurrencyNames")
                    || rbName.equals("LocaleNames")
                    || rbName.equals("TimeZoneNames")) {
                fullName = UTIL_RESOURCES_PACKAGE + "." + rbName;
            } else {
                fullName = TEXT_RESOURCES_PACKAGE + "." + rbName;
            }
            Locale locale;
            if (use_tag) {
                locale = Locale.forLanguageTag(localeName);
            } else {
                locale = new Locale(language, country, variant);
            }
            ResourceBundle bundle = ResourceBundle.getBundle(fullName,
                                                             locale,
                                                             JRELocaleResourceBundleControl.INSTANCE);
            resource = bundle.getObject(resTag);
        }
        catch (MissingResourceException e) {
        }

        if (resource != null) {
            if (resource instanceof String) {
                retrievedValue = (String)resource;
            }
            else if (resource instanceof String[]) {
                int element = Integer.valueOf(qualifier).intValue();
                String[] stringList = (String[])resource;
                if (element >= 0 && element < stringList.length)
                    retrievedValue = stringList[element];
            }
            else if (resource instanceof String[][]) {
                String[][] stringArray = (String[][])resource;
                int slash = qualifier.indexOf("/");
                if (slash == -1) {
                    for (int i = 0; i < stringArray.length; i++) {
                        if (stringArray[i][0].equals(qualifier))
                            retrievedValue = stringArray[i][1];
                    }
                }
                else {
                    int row = Integer.valueOf(qualifier.substring(0, slash)).intValue();
                    int column = Integer.valueOf(qualifier.substring(slash + 1)).intValue();
                    if (row >= 0 && row < stringArray.length && column >= 0 && column <
                                    stringArray[row].length)
                        retrievedValue = stringArray[row][column];
                }
            }
        }

        if (retrievedValue == null || !retrievedValue.equals(expectedValue)) {
            if (retrievedValue == null)
                retrievedValue = "<MISSING!>";

            if (writeNewFile)
                out.println(key + "=" + retrievedValue);
            else {
                out.println("Mismatch in " + key + ":");
                out.println("  file = \"" + expectedValue + "\"");
                out.println("   jvm = \"" + retrievedValue + "\"");
            }
            return false;
        }
        else {
            if (writeNewFile)
                out.println(key + "=" + expectedValue);
        }
        return true;
    }

    private static class JRELocaleResourceBundleControl extends ResourceBundle.Control {
        static final JRELocaleResourceBundleControl INSTANCE = new JRELocaleResourceBundleControl();

        private JRELocaleResourceBundleControl() {
        }

        @Override
        public Locale getFallbackLocale(String baseName, Locale locale) {
            if (baseName == null || locale == null) {
                throw new NullPointerException();
            }
            return null;
        }

        private static final String CLDR      = ".cldr";

        /**
         * Changes baseName to its per-language package name and
         * calls the super class implementation. For example,
         * if the baseName is "sun.text.resources.FormatData" and locale is ja_JP,
         * the baseName is changed to "sun.text.resources.ja.FormatData". If
         * baseName contains "cldr", such as "sun.text.resources.cldr.FormatData",
         * the name is changed to "sun.text.resources.cldr.jp.FormatData".
         */
        @Override
        public String toBundleName(String baseName, Locale locale) {
            String newBaseName = baseName;
            String lang = locale.getLanguage();
            if (lang.length() > 0) {
                if (baseName.startsWith(UTIL_RESOURCES_PACKAGE)
                    || baseName.startsWith(TEXT_RESOURCES_PACKAGE)) {
                    // Assume the lengths are the same.
                    if (UTIL_RESOURCES_PACKAGE.length()
                        != TEXT_RESOURCES_PACKAGE.length()) {
                        throw new InternalError("The resources package names have different lengths.");
                    }
                    int index = TEXT_RESOURCES_PACKAGE.length();
                    if (baseName.indexOf(CLDR, index) > 0) {
                        index += CLDR.length();
                    }
                    newBaseName = baseName.substring(0, index + 1) + lang
                                      + baseName.substring(index);
                }
            }
            return super.toBundleName(newBaseName, locale);
        }
    }
}

class EscapeReader extends FilterReader {
    public EscapeReader(Reader in) {
        super(in);
    }

    public int read() throws IOException {
        if (buffer != null) {
            String b = buffer.toString();
            int result = b.charAt(0);
            if (b.length() > 1)
                buffer = new StringBuffer(b.substring(1));
            else
                buffer = null;
            return result;
        }
        else {
            int result = super.read();
            if (result != '\\')
                return result;
            else {
                buffer = new StringBuffer();
                result = super.read();
                buffer.append((char)result);
                if (result == 'u') {
                    for (int i = 0; i < 4; i++) {
                        result = super.read();
                        if (result == -1)
                            break;
                        buffer.append((char)result);
                    }
                    String number = buffer.toString().substring(1);
                    result = Integer.parseInt(number, 16);
                    buffer = null;
                    return result;
                }
                return '\\';
            }
        }
    }

    public int read(char[] cbuf, int start, int len) throws IOException {
        int p = start;
        int end = start + len;
        int c = 0;
        while (c != -1 && p < end) {
            c = read();
            if (c != -1)
                cbuf[p++] = (char)c;
        }
        if (c == -1 && p == start)
            return -1;
        else
            return p - start;
    }

    private StringBuffer buffer = null;
}

class EscapeWriter extends FilterWriter {
    public EscapeWriter(Writer out) {
        super(out);
    }

    public void write(int c) throws IOException {
        if ((c >= ' ' && c <= '\u007e') || c == '\r' || c == '\n')
            super.write(c);
        else {
            super.write('\\');
            super.write('u');
            String number = Integer.toHexString(c);
            if (number.length() < 4)
                number = zeros.substring(0, 4 - number.length()) + number;
            super.write(number.charAt(0));
            super.write(number.charAt(1));
            super.write(number.charAt(2));
            super.write(number.charAt(3));
        }
    }

    public void write(char[] cbuf, int off, int len) throws IOException {
        int end = off + len;
        while (off < end)
            write(cbuf[off++]);
    }

    public void write(String str, int off, int len) throws IOException {
        int end = off + len;
        while (off < end)
            write(str.charAt(off++));
    }

    private static String zeros = "0000";
}