1 /*
   2  * Copyright (c) 2007, 2023, 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 4052404 4052440 4084688 4092475 4101316 4105828 4107014 4107953 4110613
  26  * 4118587 4118595 4122371 4126371 4126880 4135316 4135752 4139504 4139940 4143951
  27  * 4147315 4147317 4147552 4335196 4778440 4940539 5010672 6475525 6544471 6627549
  28  * 6786276 7066203 7085757 8008577 8030696 8170840 8255086 8263202 8287868
  29  * @summary test Locales
  30  * @modules jdk.localedata
  31  * @run junit/othervm -Djava.locale.providers=COMPAT,SPI LocaleTest
  32  * @run junit/othervm -Djava.locale.providers=COMPAT,SPI -Djava.locale.useOldISOCodes=true LocaleTest
  33  */
  34 /*
  35  * This file is available under and governed by the GNU General Public
  36  * License version 2 only, as published by the Free Software Foundation.
  37  * However, the following notice accompanied the original version of this
  38  * file and, per its terms, should not be removed:
  39  *
  40  * (C) Copyright Taligent, Inc. 1996, 1997 - All Rights Reserved
  41  * (C) Copyright IBM Corp. 1996 - 1998 - All Rights Reserved
  42  *
  43  * Portions copyright (c) 2007 Sun Microsystems, Inc.
  44  * All Rights Reserved.
  45  *
  46  * The original version of this source code and documentation
  47  * is copyrighted and owned by Taligent, Inc., a wholly-owned
  48  * subsidiary of IBM. These materials are provided under terms
  49  * of a License Agreement between Taligent and Sun. This technology
  50  * is protected by multiple US and International patents.
  51  *
  52  * This notice and attribution to Taligent may not be removed.
  53  * Taligent is a registered trademark of Taligent, Inc.
  54  *
  55  * Permission to use, copy, modify, and distribute this software
  56  * and its documentation for NON-COMMERCIAL purposes and without
  57  * fee is hereby granted provided that this copyright notice
  58  * appears in all copies. Please refer to the file "copyright.html"
  59  * for further important copyright and licensing information.
  60  *
  61  * SUN MAKES NO REPRESENTATIONS OR WARRANTIES ABOUT THE SUITABILITY OF
  62  * THE SOFTWARE, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
  63  * TO THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
  64  * PARTICULAR PURPOSE, OR NON-INFRINGEMENT. SUN SHALL NOT BE LIABLE FOR
  65  * ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF USING, MODIFYING OR
  66  * DISTRIBUTING THIS SOFTWARE OR ITS DERIVATIVES.
  67  *
  68  */
  69 
  70 import java.io.ByteArrayInputStream;
  71 import java.io.ByteArrayOutputStream;
  72 import java.io.IOException;
  73 import java.io.ObjectInputStream;
  74 import java.io.ObjectOutputStream;
  75 import java.io.OptionalDataException;
  76 import java.io.StreamCorruptedException;
  77 import java.text.DateFormat;
  78 import java.text.DecimalFormat;
  79 import java.text.NumberFormat;
  80 import java.text.SimpleDateFormat;
  81 import java.util.Arrays;
  82 import java.util.Calendar;
  83 import java.util.Date;
  84 import java.util.List;
  85 import java.util.Locale;
  86 import java.util.MissingResourceException;
  87 
  88 import org.junit.jupiter.api.Test;
  89 
  90 import static org.junit.jupiter.api.Assertions.fail;
  91 
  92 public class LocaleTest {
  93     public LocaleTest() {
  94     }
  95 
  96     private int ENGLISH = 0;
  97     private int FRENCH = 1;
  98     private int CROATIAN = 2;
  99     private int GREEK = 3;
 100     private int NORWEGIAN = 4;
 101     private int ITALIAN = 5;
 102     private int DUMMY = 6;
 103     private int MAX_LOCALES = 6;
 104 
 105     private int LANG = 0;
 106     private int CTRY = 1;
 107     private int VAR = 2;
 108     private int NAME = 3;
 109     private int LANG3 = 4;
 110     private int CTRY3 = 5;
 111     private int LCID = 6;
 112     private int DLANG_EN = 7;
 113     private int DCTRY_EN = 8;
 114     private int DVAR_EN = 9;
 115     private int DNAME_EN = 10;
 116     private int DLANG_FR = 11;
 117     private int DCTRY_FR = 12;
 118     private int DVAR_FR = 13;
 119     private int DNAME_FR = 14;
 120     private int DLANG_HR = 15;
 121     private int DCTRY_HR = 16;
 122     private int DVAR_HR = 17;
 123     private int DNAME_HR = 18;
 124     private int DLANG_EL = 19;
 125     private int DCTRY_EL = 20;
 126     private int DVAR_EL = 21;
 127     private int DNAME_EL = 22;
 128     private int DLANG_ROOT = 23;
 129     private int DCTRY_ROOT = 24;
 130     private int DVAR_ROOT = 25;
 131     private int DNAME_ROOT = 26;
 132 
 133     private String[][] dataTable = {
 134         // language code
 135         {   "en",   "fr",   "hr",   "el",   "no",   "it",   "xx"    },
 136         // country code
 137         {   "US",   "FR",   "HR",   "GR",   "NO",   "",   "YY"    },
 138         // variant code
 139         {   "",     "",     "",     "",     "NY",   "",   ""    },
 140         // full name
 141         {   "en_US",    "fr_FR",    "hr_HR",    "el_GR",    "no_NO_NY", "it",   "xx_YY"  },
 142         // ISO-3 language
 143         {   "eng",  "fra",  "hrv",  "ell",  "nor",  "ita",  ""   },
 144         // ISO-3 country
 145         {   "USA",  "FRA",  "HRV",  "GRC",  "NOR",  "",     ""   },
 146         // LCID (not currently public)
 147         {   "0409", "040c", "041a", "0408", "0814", "",     ""  },
 148 
 149         // display language (English)
 150         {   "English",  "French",   "Croatian", "Greek",    "Norwegian",    "Italian",  "xx" },
 151         // display country (English)
 152         {   "United States",    "France",   "Croatia",  "Greece",   "Norway",   "",     "YY" },
 153         // display variant (English)
 154         {   "",     "",     "",     "",     "Nynorsk",   "",     ""},
 155         // display name (English)
 156         // Updated no_NO_NY English display name for new pattern-based algorithm
 157         // (part of Euro support).
 158         {   "English (United States)", "French (France)", "Croatian (Croatia)", "Greek (Greece)", "Norwegian (Norway,Nynorsk)", "Italian", "xx (YY)" },
 159 
 160         // display langage (French)
 161         {   "anglais",  "fran\u00e7ais",   "croate", "grec",    "norv\u00e9gien",    "italien", "xx" },
 162         // display country (French)
 163         {   "\u00c9tats-Unis",    "France",   "Croatie",  "Gr\u00e8ce",   "Norv\u00e8ge", "",     "YY" },
 164         // display variant (French)
 165         {   "",     "",     "",     "",     "",     "",    "" },
 166         // display name (French)
 167         {   "anglais (\u00c9tats-Unis)", "fran\u00e7ais (France)", "croate (Croatie)", "grec (Gr\u00e8ce)", "norv\u00e9gien (Norv\u00e8ge,Nynorsk)", "italien", "xx (YY)" },
 168 
 169         // display langage (Croatian)
 170         {   "",  "", "hrvatski", "",    "", "", "xx" },
 171         // display country (Croatian)
 172         {   "",    "",   "Hrvatska",  "",   "", "", "YY" },
 173         // display variant (Croatian)
 174         {   "",     "",     "",     "",     "", "", ""},
 175         // display name (Croatian)
 176         {   "", "", "hrvatski (Hrvatska)", "", "", "", "xx (YY)" },
 177 
 178         // display langage (Greek)
 179         {   "\u0391\u03b3\u03b3\u03bb\u03b9\u03ba\u03ac",  "\u0393\u03b1\u03bb\u03bb\u03b9\u03ba\u03ac", "\u039a\u03c1\u03bf\u03b1\u03c4\u03b9\u03ba\u03ac", "\u0395\u03bb\u03bb\u03b7\u03bd\u03b9\u03ba\u03ac",    "\u039d\u03bf\u03c1\u03b2\u03b7\u03b3\u03b9\u03ba\u03ac", "\u0399\u03c4\u03b1\u03bb\u03b9\u03ba\u03ac", "xx" },
 180         // display country (Greek)
 181         {   "\u0397\u03bd\u03c9\u03bc\u03ad\u03bd\u03b5\u03c2 \u03a0\u03bf\u03bb\u03b9\u03c4\u03b5\u03af\u03b5\u03c2",    "\u0393\u03b1\u03bb\u03bb\u03af\u03b1",   "\u039a\u03c1\u03bf\u03b1\u03c4\u03af\u03b1",  "\u0395\u03bb\u03bb\u03ac\u03b4\u03b1",   "\u039d\u03bf\u03c1\u03b2\u03b7\u03b3\u03af\u03b1", "", "YY" },
 182         // display variant (Greek)
 183         {   "",     "",     "",     "",     "", "", "" },
 184         // display name (Greek)
 185         {   "\u0391\u03b3\u03b3\u03bb\u03b9\u03ba\u03ac (\u0397\u03bd\u03c9\u03bc\u03ad\u03bd\u03b5\u03c2 \u03a0\u03bf\u03bb\u03b9\u03c4\u03b5\u03af\u03b5\u03c2)", "\u0393\u03b1\u03bb\u03bb\u03b9\u03ba\u03ac (\u0393\u03b1\u03bb\u03bb\u03af\u03b1)", "\u039a\u03c1\u03bf\u03b1\u03c4\u03b9\u03ba\u03ac (\u039a\u03c1\u03bf\u03b1\u03c4\u03af\u03b1)", "\u0395\u03bb\u03bb\u03b7\u03bd\u03b9\u03ba\u03ac (\u0395\u03bb\u03bb\u03ac\u03b4\u03b1)", "\u039d\u03bf\u03c1\u03b2\u03b7\u03b3\u03b9\u03ba\u03ac (\u039d\u03bf\u03c1\u03b2\u03b7\u03b3\u03af\u03b1,Nynorsk)", "\u0399\u03c4\u03b1\u03bb\u03b9\u03ba\u03ac", "xx (YY)" },
 186 
 187         // display langage (<root>)
 188         {   "English",  "French",   "Croatian", "Greek",    "Norwegian",  "Italian",  "xx" },
 189         // display country (<root>)
 190         {   "United States",    "France",   "Croatia",  "Greece",   "Norway",  "",     "YY" },
 191         // display variant (<root>)
 192         {   "",     "",     "",     "",     "Nynorsk",   "",     ""},
 193         // display name (<root>)
 194         {   "English (United States)", "French (France)", "Croatian (Croatia)", "Greek (Greece)", "Norwegian (Norway,Nynorsk)", "Italian", "xx (YY)" },
 195     };
 196 
 197     @Test
 198     public void TestBasicGetters() {
 199         for (int i = 0; i <= MAX_LOCALES; i++) {
 200             Locale testLocale = Locale.of(dataTable[LANG][i], dataTable[CTRY][i], dataTable[VAR][i]);
 201             System.out.println("Testing " + testLocale + "...");
 202 
 203             if (!testLocale.getLanguage().equals(dataTable[LANG][i])) {
 204                 fail("  Language code mismatch: " + testLocale.getLanguage() + " versus "
 205                         + dataTable[LANG][i]);
 206             }
 207             if (!testLocale.getCountry().equals(dataTable[CTRY][i])) {
 208                 fail("  Country code mismatch: " + testLocale.getCountry() + " versus "
 209                         + dataTable[CTRY][i]);
 210             }
 211             if (!testLocale.getVariant().equals(dataTable[VAR][i])) {
 212                 fail("  Variant code mismatch: " + testLocale.getVariant() + " versus "
 213                         + dataTable[VAR][i]);
 214             }
 215             if (!testLocale.toString().equals(dataTable[NAME][i])) {
 216                 fail("  Locale name mismatch: " + testLocale.toString() + " versus "
 217                         + dataTable[NAME][i]);
 218             }
 219         }
 220 
 221         System.out.println("Same thing without variant codes...");
 222         for (int i = 0; i <= MAX_LOCALES; i++) {
 223             Locale testLocale = Locale.of(dataTable[LANG][i], dataTable[CTRY][i]);
 224             System.out.println("Testing " + testLocale + "...");
 225 
 226             if (!testLocale.getLanguage().equals(dataTable[LANG][i])) {
 227                 fail("  Language code mismatch: " + testLocale.getLanguage() + " versus "
 228                         + dataTable[LANG][i]);
 229             }
 230             if (!testLocale.getCountry().equals(dataTable[CTRY][i])) {
 231                 fail("  Country code mismatch: " + testLocale.getCountry() + " versus "
 232                         + dataTable[CTRY][i]);
 233             }
 234             if (!testLocale.getVariant().equals("")) {
 235                 fail("  Variant code mismatch: " + testLocale.getVariant() + " versus \"\"");
 236             }
 237         }
 238     }
 239 
 240     @Test
 241     public void TestSimpleResourceInfo() {
 242         for (int i = 0; i <= MAX_LOCALES; i++) {
 243             if (dataTable[LANG][i].equals("xx")) {
 244                 continue;
 245             }
 246 
 247             Locale testLocale = Locale.of(dataTable[LANG][i], dataTable[CTRY][i], dataTable[VAR][i]);
 248             System.out.println("Testing " + testLocale + "...");
 249 
 250             if (!testLocale.getISO3Language().equals(dataTable[LANG3][i])) {
 251                 fail("  ISO-3 language code mismatch: " + testLocale.getISO3Language()
 252                         + " versus " + dataTable[LANG3][i]);
 253             }
 254             if (!testLocale.getISO3Country().equals(dataTable[CTRY3][i])) {
 255                 fail("  ISO-3 country code mismatch: " + testLocale.getISO3Country()
 256                         + " versus " + dataTable[CTRY3][i]);
 257             }
 258 /*
 259             // getLCID() is currently private
 260             if (!String.valueOf(testLocale.getLCID()).equals(dataTable[LCID][i]))
 261                 fail("  LCID mismatch: " + testLocale.getLCID() + " versus "
 262                             + dataTable[LCID][i]);
 263 */
 264         }
 265     }
 266 
 267     /*
 268      * @bug 4101316
 269      * @bug 4084688 (This bug appears to be a duplicate of something, because it was fixed
 270      *              between 1.1.5 and 1.1.6, but I included a new test for it anyway)
 271      * @bug 4052440 Stop falling back to the default locale.
 272      */
 273     @Test
 274     public void TestDisplayNames() {
 275         Locale saveDefault = Locale.getDefault();
 276         Locale english = Locale.US;
 277         Locale french = Locale.FRANCE;
 278         Locale croatian = Locale.of("hr", "HR");
 279         Locale greek = Locale.of("el", "GR");
 280 
 281         Locale.setDefault(english);
 282         System.out.println("With default = en_US...");
 283         System.out.println("  In default locale...");
 284         doTestDisplayNames(null, DLANG_EN, false);
 285         System.out.println("  In locale = en_US...");
 286         doTestDisplayNames(english, DLANG_EN, false);
 287         System.out.println("  In locale = fr_FR...");
 288         doTestDisplayNames(french, DLANG_FR, false);
 289         System.out.println("  In locale = hr_HR...");
 290         doTestDisplayNames(croatian, DLANG_HR, false);
 291         System.out.println("  In locale = el_GR...");
 292         doTestDisplayNames(greek, DLANG_EL, false);
 293 
 294         Locale.setDefault(french);
 295         System.out.println("With default = fr_FR...");
 296         System.out.println("  In default locale...");
 297         doTestDisplayNames(null, DLANG_FR, true);
 298         System.out.println("  In locale = en_US...");
 299         doTestDisplayNames(english, DLANG_EN, true);
 300         System.out.println("  In locale = fr_FR...");
 301         doTestDisplayNames(french, DLANG_FR, true);
 302         System.out.println("  In locale = hr_HR...");
 303         doTestDisplayNames(croatian, DLANG_HR, true);
 304         System.out.println("  In locale = el_GR...");
 305         doTestDisplayNames(greek, DLANG_EL, true);
 306 
 307         Locale.setDefault(saveDefault);
 308     }
 309 
 310     private void doTestDisplayNames(Locale inLocale, int compareIndex, boolean defaultIsFrench) {
 311         String language = Locale.getDefault().getLanguage();
 312 
 313         if (defaultIsFrench && !language.equals("fr")) {
 314             fail("Default locale should be French, but it's really " + language);
 315         } else if (!defaultIsFrench && !language.equals("en")) {
 316             fail("Default locale should be English, but it's really " + language);
 317         }
 318 
 319         for (int i = 0; i <= MAX_LOCALES; i++) {
 320             Locale testLocale = Locale.of(dataTable[LANG][i], dataTable[CTRY][i], dataTable[VAR][i]);
 321             System.out.println("  Testing " + testLocale + "...");
 322 
 323             String testLang;
 324             String testCtry;
 325             String testVar;
 326             String testName;
 327 
 328             if (inLocale == null) {
 329                 testLang = testLocale.getDisplayLanguage();
 330                 testCtry = testLocale.getDisplayCountry();
 331                 testVar = testLocale.getDisplayVariant();
 332                 testName = testLocale.getDisplayName();
 333             } else {
 334                 testLang = testLocale.getDisplayLanguage(inLocale);
 335                 testCtry = testLocale.getDisplayCountry(inLocale);
 336                 testVar = testLocale.getDisplayVariant(inLocale);
 337                 testName = testLocale.getDisplayName(inLocale);
 338             }
 339 
 340             String expectedLang;
 341             String expectedCtry;
 342             String expectedVar;
 343             String expectedName;
 344 
 345             expectedLang = dataTable[compareIndex][i];
 346             if (expectedLang.equals("") && defaultIsFrench) {
 347                 expectedLang = dataTable[DLANG_EN][i];
 348             }
 349             if (expectedLang.equals("")) {
 350                 expectedLang = dataTable[DLANG_ROOT][i];
 351             }
 352 
 353             expectedCtry = dataTable[compareIndex + 1][i];
 354             if (expectedCtry.equals("") && defaultIsFrench) {
 355                 expectedCtry = dataTable[DCTRY_EN][i];
 356             }
 357             if (expectedCtry.equals("")) {
 358                 expectedCtry = dataTable[DCTRY_ROOT][i];
 359             }
 360 
 361             expectedVar = dataTable[compareIndex + 2][i];
 362             if (expectedVar.equals("") && defaultIsFrench) {
 363                 expectedVar = dataTable[DVAR_EN][i];
 364             }
 365             if (expectedVar.equals("")) {
 366                 expectedVar = dataTable[DVAR_ROOT][i];
 367             }
 368 
 369             expectedName = dataTable[compareIndex + 3][i];
 370             if (expectedName.equals("") && defaultIsFrench) {
 371                 expectedName = dataTable[DNAME_EN][i];
 372             }
 373             if (expectedName.equals("")) {
 374                 expectedName = dataTable[DNAME_ROOT][i];
 375             }
 376 
 377             if (!testLang.equals(expectedLang)) {
 378                 fail("Display language mismatch: " + testLang + " versus " + expectedLang);
 379             }
 380             if (!testCtry.equals(expectedCtry)) {
 381                 fail("Display country mismatch: " + testCtry + " versus " + expectedCtry);
 382             }
 383             if (!testVar.equals(expectedVar)) {
 384                 fail("Display variant mismatch: " + testVar + " versus " + expectedVar);
 385             }
 386             if (!testName.equals(expectedName)) {
 387                 fail("Display name mismatch: " + testName + " versus " + expectedName);
 388             }
 389         }
 390     }
 391 
 392     @SuppressWarnings("deprecation")
 393     @Test
 394     public void TestSimpleObjectStuff() {
 395         Locale test1 = new Locale("aa", "AA");
 396         Locale test2 = new Locale("aa", "AA");
 397         Locale test3 = (Locale) test1.clone();
 398         Locale test4 = Locale.of("zz", "ZZ");
 399 
 400         if (test1 == test2 || test1 == test3 || test1 == test4 || test2 == test3) {
 401             fail("Some of the test variables point to the same locale!");
 402         }
 403 
 404         if (test3 == null) {
 405             fail("clone() failed to produce a valid object!");
 406         }
 407 
 408         if (!test1.equals(test2) || !test1.equals(test3) || !test2.equals(test3)) {
 409             fail("clone() or equals() failed: objects that should compare equal don't");
 410         }
 411 
 412         if (test1.equals(test4) || test2.equals(test4) || test3.equals(test4)) {
 413             fail("equals() failed: objects that shouldn't compare equal do");
 414         }
 415 
 416         int hash1 = test1.hashCode();
 417         int hash2 = test2.hashCode();
 418         int hash3 = test3.hashCode();
 419 
 420         if (hash1 != hash2 || hash1 != hash3 || hash2 != hash3) {
 421             fail("hashCode() failed: objects that should have the same hash code don't");
 422         }
 423     }
 424 
 425     /**
 426      * @bug 4011756 4011380
 427      */
 428     @Test
 429     public void TestISO3Fallback() {
 430         Locale test = Locale.of("xx", "YY");
 431         boolean gotException = false;
 432         String result = "";
 433 
 434         try {
 435             result = test.getISO3Language();
 436         } catch (MissingResourceException e) {
 437             gotException = true;
 438         }
 439         if (!gotException) {
 440             fail("getISO3Language() on xx_YY returned " + result + " instead of throwing an exception");
 441         }
 442 
 443         gotException = false;
 444         try {
 445             result = test.getISO3Country();
 446         } catch (MissingResourceException e) {
 447             gotException = true;
 448         }
 449         if (!gotException) {
 450             fail("getISO3Country() on xx_YY returned " + result + " instead of throwing an exception");
 451         }
 452     }
 453 
 454     /**
 455      * @bug 4106155 4118587 7066203 7085757
 456      */
 457     @Test
 458     public void TestGetLangsAndCountries() {
 459         // It didn't seem right to just do an exhaustive test of everything here, so I check
 460         // for the following things:
 461         // 1) Does each list have the right total number of entries?
 462         // 2) Does each list contain certain language and country codes we think are important
 463         //     (the G7 countries, plus a couple others)?
 464         // 3) Does each list have every entry formatted correctly? (i.e., two characters,
 465         //     all lower case for the language codes, all upper case for the country codes)
 466         // 4) Is each list in sorted order?
 467         String[] test = Locale.getISOLanguages();
 468         String[] spotCheck1 = {"en", "es", "fr", "de", "it", "ja", "ko", "zh", "th",
 469             "he", "id", "iu", "ug", "yi", "za"};
 470 
 471         if (test.length != 188) {
 472             fail("Expected getISOLanguages() to return 188 languages; it returned " + test.length);
 473         } else {
 474             for (int i = 0; i < spotCheck1.length; i++) {
 475                 int j;
 476                 for (j = 0; j < test.length; j++) {
 477                     if (test[j].equals(spotCheck1[i])) {
 478                         break;
 479                     }
 480                 }
 481                 if (j == test.length || !test[j].equals(spotCheck1[i])) {
 482                     fail("Couldn't find " + spotCheck1[i] + " in language list.");
 483                 }
 484             }
 485         }
 486         for (int i = 0; i < test.length; i++) {
 487             if (!test[i].equals(test[i].toLowerCase())) {
 488                 fail(test[i] + " is not all lower case.");
 489             }
 490             if (test[i].length() != 2) {
 491                 fail(test[i] + " is not two characters long.");
 492             }
 493             if (i > 0 && test[i].compareTo(test[i - 1]) <= 0) {
 494                 fail(test[i] + " appears in an out-of-order position in the list.");
 495             }
 496         }
 497 
 498         test = Locale.getISOCountries();
 499         String[] spotCheck2 = {"US", "CA", "GB", "FR", "DE", "IT", "JP", "KR", "CN", "TW", "TH"};
 500 
 501 
 502         if (test.length != 249) {
 503             fail("Expected getISOCountries to return 249 countries; it returned " + test.length);
 504         } else {
 505             for (int i = 0; i < spotCheck2.length; i++) {
 506                 int j;
 507                 for (j = 0; j < test.length; j++) {
 508                     if (test[j].equals(spotCheck2[i])) {
 509                         break;
 510                     }
 511                 }
 512                 if (j == test.length || !test[j].equals(spotCheck2[i])) {
 513                     fail("Couldn't find " + spotCheck2[i] + " in country list.");
 514                 }
 515             }
 516         }
 517         for (int i = 0; i < test.length; i++) {
 518             if (!test[i].equals(test[i].toUpperCase())) {
 519                 fail(test[i] + " is not all upper case.");
 520             }
 521             if (test[i].length() != 2) {
 522                 fail(test[i] + " is not two characters long.");
 523             }
 524             if (i > 0 && test[i].compareTo(test[i - 1]) <= 0) {
 525                 fail(test[i] + " appears in an out-of-order position in the list.");
 526             }
 527         }
 528     }
 529 
 530     /**
 531      * @bug 4126880
 532      */
 533     void Test4126880() {
 534         String[] test;
 535 
 536         test = Locale.getISOCountries();
 537         test[0] = "SUCKER!!!";
 538         test = Locale.getISOCountries();
 539         if (test[0].equals("SUCKER!!!")) {
 540             fail("Changed internal country code list!");
 541         }
 542 
 543         test = Locale.getISOLanguages();
 544         test[0] = "HAHAHAHA!!!";
 545         test = Locale.getISOLanguages();
 546         if (test[0].equals("HAHAHAHA!!!")) { // Fixed typo
 547             fail("Changes internal language code list!");
 548         }
 549     }
 550 
 551     /**
 552      * @bug 4107014
 553      */
 554     @Test
 555     public void TestGetAvailableLocales() {
 556         Locale[] locales = Locale.getAvailableLocales();
 557         if (locales == null || locales.length == 0) {
 558             fail("Locale.getAvailableLocales() returned no installed locales!");
 559         } else {
 560             System.out.println("Locale.getAvailableLocales() returned a list of " + locales.length + " locales.");
 561             for (int i = 0; i < locales.length; i++) {
 562                 System.out.println(locales[i].toString());
 563             }
 564         }
 565     }
 566 
 567     /**
 568      * @bug 4135316
 569      */
 570     @Test
 571     public void TestBug4135316() {
 572         Locale[] locales1 = Locale.getAvailableLocales();
 573         Locale[] locales2 = Locale.getAvailableLocales();
 574         if (locales1 == locales2) {
 575             fail("Locale.getAvailableLocales() doesn't clone its internal storage!");
 576         }
 577     }
 578 
 579     /**
 580      * @bug 4107953
 581      */
 582 /*
 583 test commented out pending API-change approval
 584     @Test
 585     public void TestGetLanguagesForCountry() {
 586         String[] languages = Locale.getLanguagesForCountry("US");
 587 
 588         if (!searchStringArrayFor("en", languages))
 589             fail("Didn't get en as a language for US");
 590 
 591         languages = Locale.getLanguagesForCountry("FR");
 592         if (!searchStringArrayFor("fr", languages))
 593             fail("Didn't get fr as a language for FR");
 594 
 595         languages = Locale.getLanguagesForCountry("CH");
 596         if (!searchStringArrayFor("fr", languages))
 597             fail("Didn't get fr as a language for CH");
 598         if (!searchStringArrayFor("it", languages))
 599             fail("Didn't get it as a language for CH");
 600         if (!searchStringArrayFor("de", languages))
 601             fail("Didn't get de as a language for CH");
 602 
 603         languages = Locale.getLanguagesForCountry("JP");
 604         if (!searchStringArrayFor("ja", languages))
 605             fail("Didn't get ja as a language for JP");
 606     }
 607 */
 608 
 609     private boolean searchStringArrayFor(String s, String[] array) {
 610         for (int i = 0; i < array.length; i++)
 611             if (s.equals(array[i]))
 612                 return true;
 613         return false;
 614     }
 615     /**
 616      * @bug 4110613
 617      */
 618     @Test
 619     public void TestSerialization() throws ClassNotFoundException, OptionalDataException,
 620             IOException, StreamCorruptedException {
 621         ObjectOutputStream ostream;
 622         ByteArrayOutputStream obstream;
 623         byte[] bytes = null;
 624 
 625         obstream = new ByteArrayOutputStream();
 626         ostream = new ObjectOutputStream(obstream);
 627 
 628         Locale test1 = Locale.of("zh", "TW");
 629         int dummy = test1.hashCode();   // fill in the cached hash-code value
 630         ostream.writeObject(test1);
 631 
 632         bytes = obstream.toByteArray();
 633 
 634         ObjectInputStream istream = new ObjectInputStream(new ByteArrayInputStream(bytes));
 635 
 636         Locale test2 = (Locale) (istream.readObject());
 637 
 638         if (!test1.equals(test2) || test1.hashCode() != test2.hashCode()) {
 639             fail("Locale failed to deserialize correctly.");
 640         }
 641     }
 642 
 643     /**
 644      * @bug 4118587
 645      */
 646     @Test
 647     public void TestSimpleDisplayNames() {
 648         // This test is different from TestDisplayNames because TestDisplayNames checks
 649         // fallback behavior, combination of language and country names to form locale
 650         // names, and other stuff like that.  This test just checks specific language
 651         // and country codes to make sure we have the correct names for them.
 652         String[] languageCodes = {"he", "id", "iu", "ug", "yi", "za"};
 653         String[] languageNames = {"Hebrew", "Indonesian", "Inuktitut", "Uyghur", "Yiddish",
 654             "Zhuang"};
 655 
 656         for (int i = 0; i < languageCodes.length; i++) {
 657             String test = (Locale.of(languageCodes[i])).getDisplayLanguage(Locale.US);
 658             if (!test.equals(languageNames[i])) {
 659                 fail("Got wrong display name for " + languageCodes[i] + ": Expected \""
 660                         + languageNames[i] + "\", got \"" + test + "\".");
 661             }
 662         }
 663     }
 664 
 665     /**
 666      * @bug 4118595
 667      */
 668     @Test
 669     public void TestUninstalledISO3Names() {
 670         // This test checks to make sure getISO3Language and getISO3Country work right
 671         // even for locales that are not installed.
 672         String[] iso2Languages = {"am", "ba", "fy", "mr", "rn", "ss", "tw", "zu"};
 673         String[] iso3Languages = {"amh", "bak", "fry", "mar", "run", "ssw", "twi", "zul"};
 674 
 675         for (int i = 0; i < iso2Languages.length; i++) {
 676             String test = (Locale.of(iso2Languages[i])).getISO3Language();
 677             if (!test.equals(iso3Languages[i])) {
 678                 fail("Got wrong ISO3 code for " + iso2Languages[i] + ": Expected \""
 679                         + iso3Languages[i] + "\", got \"" + test + "\".");
 680             }
 681         }
 682 
 683         String[] iso2Countries = {"AF", "BW", "KZ", "MO", "MN", "SB", "TC", "ZW"};
 684         String[] iso3Countries = {"AFG", "BWA", "KAZ", "MAC", "MNG", "SLB", "TCA", "ZWE"};
 685 
 686         for (int i = 0; i < iso2Countries.length; i++) {
 687             String test = (Locale.of("", iso2Countries[i])).getISO3Country();
 688             if (!test.equals(iso3Countries[i])) {
 689                 fail("Got wrong ISO3 code for " + iso2Countries[i] + ": Expected \""
 690                         + iso3Countries[i] + "\", got \"" + test + "\".");
 691             }
 692         }
 693     }
 694 
 695     /**
 696      * @bug 4052404 4778440 8263202
 697      */
 698     @Test
 699     public void TestChangedISO639Codes() {
 700         Locale hebrewOld = Locale.of("iw", "IL");
 701         Locale hebrewNew = Locale.of("he", "IL");
 702         Locale yiddishOld = Locale.of("ji", "IL");
 703         Locale yiddishNew = Locale.of("yi", "IL");
 704         Locale indonesianOld = Locale.of("in");
 705         Locale indonesianNew = Locale.of("id");
 706 
 707         if ("true".equalsIgnoreCase(System.getProperty("java.locale.useOldISOCodes"))) {
 708             if (!hebrewNew.getLanguage().equals("iw")) {
 709                 fail("Got back wrong language code for new Hebrew: expected \"iw\", got \""
 710                         + hebrewNew.getLanguage() + "\"");
 711             }
 712             if (!yiddishNew.getLanguage().equals("ji")) {
 713                 fail("Got back wrong language code for new Yiddish: expected \"ji\", got \""
 714                         + yiddishNew.getLanguage() + "\"");
 715             }
 716             if (!indonesianNew.getLanguage().equals("in")) {
 717                 fail("Got back wrong language code for new Indonesian: expected \"in\", got \""
 718                         + indonesianNew.getLanguage() + "\"");
 719             }
 720         } else {
 721             if (!hebrewOld.getLanguage().equals("he")) {
 722                 fail("Got back wrong language code for old Hebrew: expected \"he\", got \""
 723                         + hebrewNew.getLanguage() + "\"");
 724             }
 725             if (!yiddishOld.getLanguage().equals("yi")) {
 726                 fail("Got back wrong language code for old Yiddish: expected \"yi\", got \""
 727                         + yiddishNew.getLanguage() + "\"");
 728             }
 729             if (!indonesianOld.getLanguage().equals("id")) {
 730                 fail("Got back wrong language code for old Indonesian: expected \"id\", got \""
 731                         + indonesianNew.getLanguage() + "\"");
 732             }
 733         }
 734 
 735     }
 736 
 737     /**
 738      * @bug 4092475
 739      * I could not reproduce this bug.  I'm pretty convinced it was fixed with the
 740      * big locale-data reorg of 10/28/97.  The lookup logic for language and country
 741      * display names was also changed at that time in that check-in.    --rtg 3/20/98
 742 
 743      * This test is not designed to work in any other locale but en_US.
 744      * Most of the LocaleElements do not contain display names for other languages,
 745      * so this test fails (bug 4289223) when run under different locales. For example,
 746      * LocaleElements_es as of kestrel does not have a localized name for Japanese, so
 747      * the getDisplayName method asks the default locale for a display name. The Japanese
 748      * localized name for "Japanese" does not equal "Japanese" so this test fails for es
 749      * display names if run under a ja locale. Eventually, he LocaleElements should probably
 750      * be updated to contain more localized language and region display names.
 751      * 1999-11-19 joconner
 752      *
 753      */
 754     @Test
 755     public void TestAtypicalLocales() {
 756         Locale[] localesToTest = { Locale.of("de", "CA"),
 757                                    Locale.of("ja", "ZA"),
 758                                    Locale.of("ru", "MX"),
 759                                    Locale.of("en", "FR"),
 760                                    Locale.of("es", "DE"),
 761                                    Locale.of("", "HR"),
 762                                    Locale.of("", "SE"),
 763                                    Locale.of("", "DO"),
 764                                    Locale.of("", "BE") };
 765         String[] englishDisplayNames = { "German (Canada)",
 766                                          "Japanese (South Africa)",
 767                                          "Russian (Mexico)",
 768                                          "English (France)",
 769                                          "Spanish (Germany)",
 770                                          "Croatia",
 771                                          "Sweden",
 772                                          "Dominican Republic",
 773                                          "Belgium" };
 774         String[] frenchDisplayNames = { "allemand (Canada)",
 775                                         "japonais (Afrique du Sud)",
 776                                         "russe (Mexique)",
 777                                          "anglais (France)",
 778                                          "espagnol (Allemagne)",
 779                                         "Croatie",
 780                                         "Su\u00e8de",
 781                                         "R\u00e9publique dominicaine",
 782                                         "Belgique" };
 783         String[] spanishDisplayNames = { "alem\u00E1n (Canad\u00E1)",
 784                                          "japon\u00E9s (Sud\u00E1frica)",
 785                                          "ruso (M\u00e9xico)",
 786                                          "ingl\u00E9s (Francia)",
 787                                          "espa\u00f1ol (Alemania)",
 788                                          "Croacia",
 789                                          "Suecia",
 790                                          "Rep\u00fablica Dominicana",
 791                                          "B\u00E9lgica" };
 792 
 793 
 794         // save the default locale and set to the new default to en_US
 795         Locale defaultLocale = Locale.getDefault();
 796         Locale.setDefault(Locale.US);
 797 
 798         for (int i = 0; i < localesToTest.length; i++) {
 799             String name = localesToTest[i].getDisplayName(Locale.US);
 800             System.out.println(name);
 801             if (!name.equals(englishDisplayNames[i])) {
 802                 fail("Lookup in English failed: expected \"" + englishDisplayNames[i]
 803                         + "\", got \"" + name + "\"");
 804             }
 805         }
 806 
 807         for (int i = 0; i < localesToTest.length; i++) {
 808             String name = localesToTest[i].getDisplayName(Locale.of("es", "ES"));
 809             System.out.println(name);
 810             if (!name.equals(spanishDisplayNames[i])) {
 811                 fail("Lookup in Spanish failed: expected \"" + spanishDisplayNames[i]
 812                         + "\", got \"" + name + "\"");
 813             }
 814         }
 815 
 816         for (int i = 0; i < localesToTest.length; i++) {
 817             String name = localesToTest[i].getDisplayName(Locale.FRANCE);
 818             System.out.println(name);
 819             if (!name.equals(frenchDisplayNames[i])) {
 820                 fail("Lookup in French failed: expected \"" + frenchDisplayNames[i]
 821                         + "\", got \"" + name + "\"");
 822             }
 823         }
 824 
 825         // restore the default locale for other tests
 826         Locale.setDefault(defaultLocale);
 827     }
 828 
 829     /**
 830      * @bug 4126371
 831      */
 832     @Test
 833     public void TestNullDefault() {
 834         // why on earth anyone would ever try to do this is beyond me, but we should
 835         // definitely make sure we don't let them
 836         boolean gotException = false;
 837         try {
 838             Locale.setDefault(null);
 839         } catch (NullPointerException e) {
 840             // all other exception types propagate through here back to the test harness
 841             gotException = true;
 842         }
 843         if (Locale.getDefault() == null) {
 844             fail("Locale.getDefault() allowed us to set default to NULL!");
 845         }
 846         if (!gotException) {
 847             fail("Trying to set default locale to NULL didn't throw exception!");
 848         }
 849     }
 850 
 851     /**
 852      * @bug 4135752
 853      * This would be better tested by the LocaleDataTest.  Will move it when I
 854      * get the LocaleDataTest working again.
 855      */
 856     @Test
 857     public void TestThaiCurrencyFormat() {
 858         DecimalFormat thaiCurrency = (DecimalFormat) NumberFormat.getCurrencyInstance(
 859                 Locale.of("th", "TH"));
 860         if (!thaiCurrency.getPositivePrefix().equals("\u0e3f")) {
 861             fail("Thai currency prefix wrong: expected \"\u0e3f\", got \""
 862                     + thaiCurrency.getPositivePrefix() + "\"");
 863         }
 864         if (!thaiCurrency.getPositiveSuffix().equals("")) {
 865             fail("Thai currency suffix wrong: expected \"\", got \""
 866                     + thaiCurrency.getPositiveSuffix() + "\"");
 867         }
 868     }
 869 
 870     /**
 871      * @bug 4122371
 872      * Confirm that Euro support works.  This test is pretty rudimentary; all it does
 873      * is check that any locales with the EURO variant format a number using the
 874      * Euro currency symbol.
 875      *
 876      * ASSUME: All locales encode the Euro character "\u20AC".
 877      * If this is changed to use the single-character Euro symbol, this
 878      * test must be updated.
 879      *
 880      * DON'T ASSUME: Any specific countries support the Euro.  Instead,
 881      * iterate through all locales.
 882      */
 883     @Test
 884     public void TestEuroSupport() {
 885         final String EURO_VARIANT = "EURO";
 886         final String EURO_CURRENCY = "\u20AC"; // Look for this string in formatted Euro currency
 887 
 888         Locale[] locales = NumberFormat.getAvailableLocales();
 889         for (int i = 0; i < locales.length; ++i) {
 890             Locale loc = locales[i];
 891             if (loc.getVariant().indexOf(EURO_VARIANT) >= 0) {
 892                 NumberFormat nf = NumberFormat.getCurrencyInstance(loc);
 893                 String pos = nf.format(271828.182845);
 894                 String neg = nf.format(-271828.182845);
 895                 if (pos.indexOf(EURO_CURRENCY) >= 0
 896                         && neg.indexOf(EURO_CURRENCY) >= 0) {
 897                     System.out.println("Ok: " + loc.toString()
 898                             + ": " + pos + " / " + neg);
 899                 } else {
 900                     fail("Fail: " + loc.toString()
 901                             + " formats without " + EURO_CURRENCY
 902                             + ": " + pos + " / " + neg
 903                             + "\n*** THIS FAILURE MAY ONLY MEAN THAT LOCALE DATA HAS CHANGED ***");
 904                 }
 905             }
 906         }
 907     }
 908 
 909     /**
 910      * @bug 4139504
 911      * toString() doesn't work with language_VARIANT.
 912      */
 913     @Test
 914     public void TestToString() {
 915         Object[] DATA = {
 916             Locale.of("xx", "", ""), "xx",
 917             Locale.of("", "YY", ""), "_YY",
 918             Locale.of("", "", "ZZ"), "",
 919             Locale.of("xx", "YY", ""), "xx_YY",
 920             Locale.of("xx", "", "ZZ"), "xx__ZZ",
 921             Locale.of("", "YY", "ZZ"), "_YY_ZZ",
 922             Locale.of("xx", "YY", "ZZ"), "xx_YY_ZZ",
 923         };
 924         for (int i = 0; i < DATA.length; i += 2) {
 925             Locale loc = (Locale) DATA[i];
 926             String fmt = (String) DATA[i + 1];
 927             if (!loc.toString().equals(fmt)) {
 928                 fail("Fail: Locale.toString(" + fmt + ")=>" + loc);
 929             }
 930         }
 931     }
 932 
 933     /**
 934      * @bug 4105828
 935      * Currency symbol in zh is wrong.  We will test this at the NumberFormat
 936      * end to test the whole pipe.
 937      */
 938     @Test
 939     public void Test4105828() {
 940         Locale[] LOC = {Locale.CHINESE, Locale.of("zh", "CN"),
 941             Locale.of("zh", "TW"), Locale.of("zh", "HK")};
 942         for (int i = 0; i < LOC.length; ++i) {
 943             NumberFormat fmt = NumberFormat.getPercentInstance(LOC[i]);
 944             String result = fmt.format(1);
 945             if (!result.equals("100%")) {
 946                 fail("Percent for " + LOC[i] + " should be 100%, got " + result);
 947             }
 948         }
 949     }
 950 
 951     /**
 952      * @bug 4139940
 953      * Couldn't reproduce this bug -- probably was fixed earlier.
 954      *
 955      * ORIGINAL BUG REPORT:
 956      * -- basically, hungarian for monday shouldn't have an \u00f4
 957      * (o circumflex)in it instead it should be an o with 2 inclined
 958      * (right) lines over it..
 959      *
 960      * You may wonder -- why do all this -- why not just add a line to
 961      * LocaleData?  Well, I could see by inspection that the locale file had the
 962      * right character in it, so I wanted to check the rest of the pipeline -- a
 963      * very remote possibility, but I wanted to be sure.  The other possibility
 964      * is that something is wrong with the font mapping subsystem, but we can't
 965      * test that here.
 966      */
 967     @Test
 968     public void Test4139940() {
 969         Locale mylocale = Locale.of("hu");
 970         @SuppressWarnings("deprecation")
 971         Date mydate = new Date(98, 3, 13); // A Monday
 972         DateFormat df_full = new SimpleDateFormat("EEEE", mylocale);
 973         String str = df_full.format(mydate);
 974         // Make sure that o circumflex (\u00F4) is NOT there, and
 975         // o double acute (\u0151) IS.
 976         if (str.indexOf('\u0151') < 0 || str.indexOf('\u00F4') >= 0) {
 977             fail("Fail: Monday in Hungarian is wrong");
 978         }
 979     }
 980 
 981     /**
 982      * @bug 4143951
 983      * Russian first day of week should be Monday. Confirmed.
 984      */
 985     @Test
 986     public void Test4143951() {
 987         Calendar cal = Calendar.getInstance(Locale.of("ru"));
 988         if (cal.getFirstDayOfWeek() != Calendar.MONDAY) {
 989             fail("Fail: First day of week in Russia should be Monday");
 990         }
 991     }
 992 
 993     /**
 994      * @bug 4147315
 995      * java.util.Locale.getISO3Country() works wrong for non ISO-3166 codes.
 996      * Should throw an exception for unknown locales
 997      */
 998     @Test
 999     public void Test4147315() {
1000         // Try with codes that are the wrong length but happen to match text
1001         // at a valid offset in the mapping table
1002         Locale locale = Locale.of("aaa", "CCC");
1003 
1004         try {
1005             String result = locale.getISO3Country();
1006 
1007             fail("ERROR: getISO3Country() returns: " + result
1008                     + " for locale '" + locale + "' rather than exception");
1009         } catch (MissingResourceException e) {
1010         }
1011     }
1012 
1013     /**
1014      * @bug 4147317 4940539
1015      * java.util.Locale.getISO3Language() works wrong for non ISO-639 codes.
1016      * Should throw an exception for unknown locales, except they have three
1017      * letter language codes.
1018      */
1019     @Test
1020     public void Test4147317() {
1021         // Try a three letter language code, and check whether it is
1022         // returned as is.
1023         Locale locale = Locale.of("aaa", "CCC");
1024 
1025         String result = locale.getISO3Language();
1026         if (!result.equals("aaa")) {
1027             fail("ERROR: getISO3Language() returns: " + result
1028                     + " for locale '" + locale + "' rather than returning it as is");
1029         }
1030 
1031         // Try an invalid two letter language code, and check whether it
1032         // throws a MissingResourceException.
1033         locale = Locale.of("zz", "CCC");
1034 
1035         try {
1036             result = locale.getISO3Language();
1037 
1038             fail("ERROR: getISO3Language() returns: " + result
1039                     + " for locale '" + locale + "' rather than exception");
1040         } catch (MissingResourceException e) {
1041         }
1042     }
1043 
1044     /*
1045      * @bug 4147552 4778440 8030696
1046      */
1047     @Test
1048     public void Test4147552() {
1049         Locale[] locales = {Locale.of("no", "NO"), Locale.of("no", "NO", "B"),
1050             Locale.of("no", "NO", "NY"), Locale.of("nb", "NO"),
1051             Locale.of("nn", "NO")};
1052         String[] englishDisplayNames = {"Norwegian (Norway)",
1053             "Norwegian (Norway,Bokm\u00e5l)",
1054             "Norwegian (Norway,Nynorsk)",
1055             "Norwegian Bokm\u00e5l (Norway)",
1056             "Norwegian Nynorsk (Norway)"};
1057         String[] norwegianDisplayNames = {"norsk (Norge)",
1058             "norsk (Norge,bokm\u00e5l)", "norsk (Noreg,nynorsk)",
1059             "norsk bokm\u00e5l (Norge)", "norsk nynorsk (Noreg)"};
1060 
1061         for (int i = 0; i < locales.length; i++) {
1062             Locale loc = locales[i];
1063             if (!loc.getDisplayName(Locale.US).equals(englishDisplayNames[i])) {
1064                 fail("English display-name mismatch: expected "
1065                         + englishDisplayNames[i] + ", got " + loc.getDisplayName());
1066             }
1067             if (!loc.getDisplayName(loc).equals(norwegianDisplayNames[i])) {
1068                 fail("Norwegian display-name mismatch: expected "
1069                         + norwegianDisplayNames[i] + ", got "
1070                         + loc.getDisplayName(loc));
1071             }
1072         }
1073     }
1074 
1075     /*
1076      * @bug 8030696
1077      */
1078     @Test
1079     public void Test8030696() {
1080         List<Locale> av = Arrays.asList(Locale.getAvailableLocales());
1081         if (!av.contains(Locale.of("nb", "NO"))
1082                 || !av.contains(Locale.of("nn", "NO"))) {
1083             fail("\"nb-NO\" and/or \"nn-NO\" locale(s) not returned from getAvailableLocales().");
1084         }
1085     }
1086 
1087     static String escapeUnicode(String s) {
1088         StringBuffer buf = new StringBuffer();
1089         for (int i = 0; i < s.length(); ++i) {
1090             char c = s.charAt(i);
1091             if (c >= 0x20 && c <= 0x7F) {
1092                 buf.append(c);
1093             } else {
1094                 buf.append("\\u");
1095                 String h = "000" + Integer.toHexString(c);
1096                 if (h.length() > 4) {
1097                     h = h.substring(h.length() - 4);
1098                 }
1099                 buf.append(h);
1100             }
1101         }
1102         return buf.toString();
1103     }
1104 }