1 /* 2 * Licensed to the Apache Software Foundation (ASF) under one or more 3 * contributor license agreements. See the NOTICE file distributed with 4 * this work for additional information regarding copyright ownership. 5 * The ASF licenses this file to You under the Apache License, Version 2.0 6 * (the "License"); you may not use this file except in compliance with 7 * the License. You may obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 */ 17 18 package java.util; 19 20 import java.io.IOException; 21 import java.io.ObjectInputStream; 22 import java.io.ObjectOutputStream; 23 import java.io.ObjectStreamField; 24 import java.io.Serializable; 25 import libcore.icu.ICU; 26 27 /** 28 * {@code Locale} represents a language/country/variant combination. Locales are used to 29 * alter the presentation of information such as numbers or dates to suit the conventions 30 * in the region they describe. 31 * 32 * <p>The language codes are two-letter lowercase ISO language codes (such as "en") as defined by 33 * <a href="http://en.wikipedia.org/wiki/ISO_639-1">ISO 639-1</a>. 34 * The country codes are two-letter uppercase ISO country codes (such as "US") as defined by 35 * <a href="http://en.wikipedia.org/wiki/ISO_3166-1_alpha-3">ISO 3166-1</a>. 36 * The variant codes are unspecified. 37 * 38 * <p>Note that Java uses several deprecated two-letter codes. The Hebrew ("he") language 39 * code is rewritten as "iw", Indonesian ("id") as "in", and Yiddish ("yi") as "ji". This 40 * rewriting happens even if you construct your own {@code Locale} object, not just for 41 * instances returned by the various lookup methods. 42 * 43 * <a name="available_locales"><h3>Available locales</h3></a> 44 * <p>This class' constructors do no error checking. You can create a {@code Locale} for languages 45 * and countries that don't exist, and you can create instances for combinations that don't 46 * exist (such as "de_US" for "German as spoken in the US"). 47 * 48 * <p>Note that locale data is not necessarily available for any of the locales pre-defined as 49 * constants in this class except for en_US, which is the only locale Java guarantees is always 50 * available. 51 * 52 * <p>It is also a mistake to assume that all devices have the same locales available. 53 * A device sold in the US will almost certainly support en_US and es_US, but not necessarily 54 * any locales with the same language but different countries (such as en_GB or es_ES), 55 * nor any locales for other languages (such as de_DE). The opposite may well be true for a device 56 * sold in Europe. 57 * 58 * <p>You can use {@link Locale#getDefault} to get an appropriate locale for the <i>user</i> of the 59 * device you're running on, or {@link Locale#getAvailableLocales} to get a list of all the locales 60 * available on the device you're running on. 61 * 62 * <a name="locale_data"><h3>Locale data</h3></a> 63 * <p>Note that locale data comes solely from ICU. User-supplied locale service providers (using 64 * the {@code java.text.spi} or {@code java.util.spi} mechanisms) are not supported. 65 * 66 * <p>Here are the versions of ICU (and the corresponding CLDR and Unicode versions) used in 67 * various Android releases: 68 * <table BORDER="1" WIDTH="100%" CELLPADDING="3" CELLSPACING="0" SUMMARY=""> 69 * <tr><td>Cupcake/Donut/Eclair</td> <td>ICU 3.8</td> <td><a href="http://cldr.unicode.org/index/downloads/cldr-1-5">CLDR 1.5</a></td> <td><a href="http://www.unicode.org/versions/Unicode5.0.0/">Unicode 5.0</a></td></tr> 70 * <tr><td>Froyo</td> <td>ICU 4.2</td> <td><a href="http://cldr.unicode.org/index/downloads/cldr-1-7">CLDR 1.7</a></td> <td><a href="http://www.unicode.org/versions/Unicode5.1.0/">Unicode 5.1</a></td></tr> 71 * <tr><td>Gingerbread/Honeycomb</td><td>ICU 4.4</td> <td><a href="http://cldr.unicode.org/index/downloads/cldr-1-8">CLDR 1.8</a></td> <td><a href="http://www.unicode.org/versions/Unicode5.2.0/">Unicode 5.2</a></td></tr> 72 * <tr><td>Ice Cream Sandwich</td> <td>ICU 4.6</td> <td><a href="http://cldr.unicode.org/index/downloads/cldr-1-9">CLDR 1.9</a></td> <td><a href="http://www.unicode.org/versions/Unicode6.0.0/">Unicode 6.0</a></td></tr> 73 * <tr><td>Jelly Bean</td> <td>ICU 4.8</td> <td><a href="http://cldr.unicode.org/index/downloads/cldr-2-0">CLDR 2.0</a></td> <td><a href="http://www.unicode.org/versions/Unicode6.0.0/">Unicode 6.0</a></td></tr> 74 * <tr><td>Jelly Bean MR2</td> <td>ICU 50</td> <td><a href="http://cldr.unicode.org/index/downloads/cldr-21-1">CLDR 22.1</a></td> <td><a href="http://www.unicode.org/versions/Unicode6.2.0/">Unicode 6.2</a></td></tr> 75 * </table> 76 * 77 * <a name="default_locale"><h3>Be wary of the default locale</h3></a> 78 * <p>Note that there are many convenience methods that automatically use the default locale, but 79 * using them may lead to subtle bugs. 80 * 81 * <p>The default locale is appropriate for tasks that involve presenting data to the user. In 82 * this case, you want to use the user's date/time formats, number 83 * formats, rules for conversion to lowercase, and so on. In this case, it's safe to use the 84 * convenience methods. 85 * 86 * <p>The default locale is <i>not</i> appropriate for machine-readable output. The best choice 87 * there is usually {@code Locale.US} – this locale is guaranteed to be available on all 88 * devices, and the fact that it has no surprising special cases and is frequently used (especially 89 * for computer-computer communication) means that it tends to be the most efficient choice too. 90 * 91 * <p>A common mistake is to implicitly use the default locale when producing output meant to be 92 * machine-readable. This tends to work on the developer's test devices (especially because so many 93 * developers use en_US), but fails when run on a device whose user is in a more complex locale. 94 * 95 * <p>For example, if you're formatting integers some locales will use non-ASCII decimal 96 * digits. As another example, if you're formatting floating-point numbers some locales will use 97 * {@code ','} as the decimal point and {@code '.'} for digit grouping. That's correct for 98 * human-readable output, but likely to cause problems if presented to another 99 * computer ({@link Double#parseDouble} can't parse such a number, for example). 100 * You should also be wary of the {@link String#toLowerCase} and 101 * {@link String#toUpperCase} overloads that don't take a {@code Locale}: in Turkey, for example, 102 * the characters {@code 'i'} and {@code 'I'} won't be converted to {@code 'I'} and {@code 'i'}. 103 * This is the correct behavior for Turkish text (such as user input), but inappropriate for, say, 104 * HTTP headers. 105 */ 106 public final class Locale implements Cloneable, Serializable { 107 108 private static final long serialVersionUID = 9149081749638150636L; 109 110 /** 111 * Locale constant for en_CA. 112 */ 113 public static final Locale CANADA = new Locale(true, "en", "CA"); 114 115 /** 116 * Locale constant for fr_CA. 117 */ 118 public static final Locale CANADA_FRENCH = new Locale(true, "fr", "CA"); 119 120 /** 121 * Locale constant for zh_CN. 122 */ 123 public static final Locale CHINA = new Locale(true, "zh", "CN"); 124 125 /** 126 * Locale constant for zh. 127 */ 128 public static final Locale CHINESE = new Locale(true, "zh", ""); 129 130 /** 131 * Locale constant for en. 132 */ 133 public static final Locale ENGLISH = new Locale(true, "en", ""); 134 135 /** 136 * Locale constant for fr_FR. 137 */ 138 public static final Locale FRANCE = new Locale(true, "fr", "FR"); 139 140 /** 141 * Locale constant for fr. 142 */ 143 public static final Locale FRENCH = new Locale(true, "fr", ""); 144 145 /** 146 * Locale constant for de. 147 */ 148 public static final Locale GERMAN = new Locale(true, "de", ""); 149 150 /** 151 * Locale constant for de_DE. 152 */ 153 public static final Locale GERMANY = new Locale(true, "de", "DE"); 154 155 /** 156 * Locale constant for it. 157 */ 158 public static final Locale ITALIAN = new Locale(true, "it", ""); 159 160 /** 161 * Locale constant for it_IT. 162 */ 163 public static final Locale ITALY = new Locale(true, "it", "IT"); 164 165 /** 166 * Locale constant for ja_JP. 167 */ 168 public static final Locale JAPAN = new Locale(true, "ja", "JP"); 169 170 /** 171 * Locale constant for ja. 172 */ 173 public static final Locale JAPANESE = new Locale(true, "ja", ""); 174 175 /** 176 * Locale constant for ko_KR. 177 */ 178 public static final Locale KOREA = new Locale(true, "ko", "KR"); 179 180 /** 181 * Locale constant for ko. 182 */ 183 public static final Locale KOREAN = new Locale(true, "ko", ""); 184 185 /** 186 * Locale constant for zh_CN. 187 */ 188 public static final Locale PRC = new Locale(true, "zh", "CN"); 189 190 /** 191 * Locale constant for the root locale. The root locale has an empty language, 192 * country, and variant. 193 * 194 * @since 1.6 195 */ 196 public static final Locale ROOT = new Locale(true, "", ""); 197 198 /** 199 * Locale constant for zh_CN. 200 */ 201 public static final Locale SIMPLIFIED_CHINESE = new Locale(true, "zh", "CN"); 202 203 /** 204 * Locale constant for zh_TW. 205 */ 206 public static final Locale TAIWAN = new Locale(true, "zh", "TW"); 207 208 /** 209 * Locale constant for zh_TW. 210 */ 211 public static final Locale TRADITIONAL_CHINESE = new Locale(true, "zh", "TW"); 212 213 /** 214 * Locale constant for en_GB. 215 */ 216 public static final Locale UK = new Locale(true, "en", "GB"); 217 218 /** 219 * Locale constant for en_US. 220 */ 221 public static final Locale US = new Locale(true, "en", "US"); 222 223 /** 224 * The current default locale. It is temporarily assigned to US because we 225 * need a default locale to lookup the real default locale. 226 */ 227 private static Locale defaultLocale = US; 228 229 static { 230 String language = System.getProperty("user.language", "en"); 231 String region = System.getProperty("user.region", "US"); 232 String variant = System.getProperty("user.variant", ""); 233 defaultLocale = new Locale(language, region, variant); 234 } 235 236 private transient String countryCode; 237 private transient String languageCode; 238 private transient String variantCode; 239 private transient String cachedToStringResult; 240 241 /** 242 * There's a circular dependency between toLowerCase/toUpperCase and 243 * Locale.US. Work around this by avoiding these methods when constructing 244 * the built-in locales. 245 * 246 * @param unused required for this constructor to have a unique signature 247 */ Locale(boolean unused, String lowerCaseLanguageCode, String upperCaseCountryCode)248 private Locale(boolean unused, String lowerCaseLanguageCode, String upperCaseCountryCode) { 249 this.languageCode = lowerCaseLanguageCode; 250 this.countryCode = upperCaseCountryCode; 251 this.variantCode = ""; 252 } 253 254 /** 255 * Constructs a new {@code Locale} using the specified language. 256 */ Locale(String language)257 public Locale(String language) { 258 this(language, "", ""); 259 } 260 261 /** 262 * Constructs a new {@code Locale} using the specified language and country codes. 263 */ Locale(String language, String country)264 public Locale(String language, String country) { 265 this(language, country, ""); 266 } 267 268 /** 269 * Constructs a new {@code Locale} using the specified language, country, 270 * and variant codes. 271 */ Locale(String language, String country, String variant)272 public Locale(String language, String country, String variant) { 273 if (language == null || country == null || variant == null) { 274 throw new NullPointerException("language=" + language + 275 ",country=" + country + 276 ",variant=" + variant); 277 } 278 if (language.isEmpty() && country.isEmpty()) { 279 languageCode = ""; 280 countryCode = ""; 281 variantCode = variant; 282 return; 283 } 284 285 languageCode = language.toLowerCase(Locale.US); 286 // Map new language codes to the obsolete language 287 // codes so the correct resource bundles will be used. 288 if (languageCode.equals("he")) { 289 languageCode = "iw"; 290 } else if (languageCode.equals("id")) { 291 languageCode = "in"; 292 } else if (languageCode.equals("yi")) { 293 languageCode = "ji"; 294 } 295 296 countryCode = country.toUpperCase(Locale.US); 297 298 // Work around for be compatible with RI 299 variantCode = variant; 300 } 301 clone()302 @Override public Object clone() { 303 try { 304 return super.clone(); 305 } catch (CloneNotSupportedException e) { 306 throw new AssertionError(e); 307 } 308 } 309 310 /** 311 * Returns true if {@code object} is a locale with the same language, 312 * country and variant. 313 */ equals(Object object)314 @Override public boolean equals(Object object) { 315 if (object == this) { 316 return true; 317 } 318 if (object instanceof Locale) { 319 Locale o = (Locale) object; 320 return languageCode.equals(o.languageCode) 321 && countryCode.equals(o.countryCode) 322 && variantCode.equals(o.variantCode); 323 } 324 return false; 325 } 326 327 /** 328 * Returns the system's installed locales. This array always includes {@code 329 * Locale.US}, and usually several others. Most locale-sensitive classes 330 * offer their own {@code getAvailableLocales} method, which should be 331 * preferred over this general purpose method. 332 * 333 * @see java.text.BreakIterator#getAvailableLocales() 334 * @see java.text.Collator#getAvailableLocales() 335 * @see java.text.DateFormat#getAvailableLocales() 336 * @see java.text.DateFormatSymbols#getAvailableLocales() 337 * @see java.text.DecimalFormatSymbols#getAvailableLocales() 338 * @see java.text.NumberFormat#getAvailableLocales() 339 * @see java.util.Calendar#getAvailableLocales() 340 */ getAvailableLocales()341 public static Locale[] getAvailableLocales() { 342 return ICU.getAvailableLocales(); 343 } 344 345 /** 346 * Returns the country code for this locale, or {@code ""} if this locale 347 * doesn't correspond to a specific country. 348 */ getCountry()349 public String getCountry() { 350 return countryCode; 351 } 352 353 /** 354 * Returns the user's preferred locale. This may have been overridden for 355 * this process with {@link #setDefault}. 356 * 357 * <p>Since the user's locale changes dynamically, avoid caching this value. 358 * Instead, use this method to look it up for each use. 359 */ getDefault()360 public static Locale getDefault() { 361 return defaultLocale; 362 } 363 364 /** 365 * Equivalent to {@code getDisplayCountry(Locale.getDefault())}. 366 */ getDisplayCountry()367 public final String getDisplayCountry() { 368 return getDisplayCountry(getDefault()); 369 } 370 371 /** 372 * Returns the name of this locale's country, localized to {@code locale}. 373 * Returns the empty string if this locale does not correspond to a specific 374 * country. 375 */ getDisplayCountry(Locale locale)376 public String getDisplayCountry(Locale locale) { 377 if (countryCode.isEmpty()) { 378 return ""; 379 } 380 String result = ICU.getDisplayCountryNative(toString(), locale.toString()); 381 if (result == null) { // TODO: do we need to do this, or does ICU do it for us? 382 result = ICU.getDisplayCountryNative(toString(), Locale.getDefault().toString()); 383 } 384 return result; 385 } 386 387 /** 388 * Equivalent to {@code getDisplayLanguage(Locale.getDefault())}. 389 */ getDisplayLanguage()390 public final String getDisplayLanguage() { 391 return getDisplayLanguage(getDefault()); 392 } 393 394 /** 395 * Returns the name of this locale's language, localized to {@code locale}. 396 * If the language name is unknown, the language code is returned. 397 */ getDisplayLanguage(Locale locale)398 public String getDisplayLanguage(Locale locale) { 399 if (languageCode.isEmpty()) { 400 return ""; 401 } 402 403 // http://b/8049507 --- frameworks/base should use fil_PH instead of tl_PH. 404 // Until then, we're stuck covering their tracks, making it look like they're 405 // using "fil" when they're not. 406 String localeString = toString(); 407 if (languageCode.equals("tl")) { 408 localeString = toNewString("fil", countryCode, variantCode); 409 } 410 411 String result = ICU.getDisplayLanguageNative(localeString, locale.toString()); 412 if (result == null) { // TODO: do we need to do this, or does ICU do it for us? 413 result = ICU.getDisplayLanguageNative(localeString, Locale.getDefault().toString()); 414 } 415 return result; 416 } 417 418 /** 419 * Equivalent to {@code getDisplayName(Locale.getDefault())}. 420 */ getDisplayName()421 public final String getDisplayName() { 422 return getDisplayName(getDefault()); 423 } 424 425 /** 426 * Returns this locale's language name, country name, and variant, localized 427 * to {@code locale}. The exact output form depends on whether this locale 428 * corresponds to a specific language, country and variant. 429 * 430 * <p>For example: 431 * <ul> 432 * <li>{@code new Locale("en").getDisplayName(Locale.US)} -> {@code English} 433 * <li>{@code new Locale("en", "US").getDisplayName(Locale.US)} -> {@code English (United States)} 434 * <li>{@code new Locale("en", "US", "POSIX").getDisplayName(Locale.US)} -> {@code English (United States,Computer)} 435 * <li>{@code new Locale("en").getDisplayName(Locale.FRANCE)} -> {@code anglais} 436 * <li>{@code new Locale("en", "US").getDisplayName(Locale.FRANCE)} -> {@code anglais (États-Unis)} 437 * <li>{@code new Locale("en", "US", "POSIX").getDisplayName(Locale.FRANCE)} -> {@code anglais (États-Unis,informatique)}. 438 * </ul> 439 */ getDisplayName(Locale locale)440 public String getDisplayName(Locale locale) { 441 int count = 0; 442 StringBuilder buffer = new StringBuilder(); 443 if (!languageCode.isEmpty()) { 444 String displayLanguage = getDisplayLanguage(locale); 445 buffer.append(displayLanguage.isEmpty() ? languageCode : displayLanguage); 446 ++count; 447 } 448 if (!countryCode.isEmpty()) { 449 if (count == 1) { 450 buffer.append(" ("); 451 } 452 String displayCountry = getDisplayCountry(locale); 453 buffer.append(displayCountry.isEmpty() ? countryCode : displayCountry); 454 ++count; 455 } 456 if (!variantCode.isEmpty()) { 457 if (count == 1) { 458 buffer.append(" ("); 459 } else if (count == 2) { 460 buffer.append(","); 461 } 462 String displayVariant = getDisplayVariant(locale); 463 buffer.append(displayVariant.isEmpty() ? variantCode : displayVariant); 464 ++count; 465 } 466 if (count > 1) { 467 buffer.append(")"); 468 } 469 return buffer.toString(); 470 } 471 472 /** 473 * Returns the full variant name in the default {@code Locale} for the variant code of 474 * this {@code Locale}. If there is no matching variant name, the variant code is 475 * returned. 476 */ getDisplayVariant()477 public final String getDisplayVariant() { 478 return getDisplayVariant(getDefault()); 479 } 480 481 /** 482 * Returns the full variant name in the specified {@code Locale} for the variant code 483 * of this {@code Locale}. If there is no matching variant name, the variant code is 484 * returned. 485 */ getDisplayVariant(Locale locale)486 public String getDisplayVariant(Locale locale) { 487 if (variantCode.length() == 0) { 488 return variantCode; 489 } 490 String result = ICU.getDisplayVariantNative(toString(), locale.toString()); 491 if (result == null) { // TODO: do we need to do this, or does ICU do it for us? 492 result = ICU.getDisplayVariantNative(toString(), Locale.getDefault().toString()); 493 } 494 return result; 495 } 496 497 /** 498 * Returns the three-letter ISO 3166 country code which corresponds to the country 499 * code for this {@code Locale}. 500 * @throws MissingResourceException if there's no 3-letter country code for this locale. 501 */ getISO3Country()502 public String getISO3Country() { 503 String code = ICU.getISO3CountryNative(toString()); 504 if (!countryCode.isEmpty() && code.isEmpty()) { 505 throw new MissingResourceException("No 3-letter country code for locale: " + this, "FormatData_" + this, "ShortCountry"); 506 } 507 return code; 508 } 509 510 /** 511 * Returns the three-letter ISO 639-2/T language code which corresponds to the language 512 * code for this {@code Locale}. 513 * @throws MissingResourceException if there's no 3-letter language code for this locale. 514 */ getISO3Language()515 public String getISO3Language() { 516 String code = ICU.getISO3LanguageNative(toString()); 517 if (!languageCode.isEmpty() && code.isEmpty()) { 518 throw new MissingResourceException("No 3-letter language code for locale: " + this, "FormatData_" + this, "ShortLanguage"); 519 } 520 return code; 521 } 522 523 /** 524 * Returns an array of strings containing all the two-letter ISO 3166 country codes that can be 525 * used as the country code when constructing a {@code Locale}. 526 */ getISOCountries()527 public static String[] getISOCountries() { 528 return ICU.getISOCountries(); 529 } 530 531 /** 532 * Returns an array of strings containing all the two-letter ISO 639-1 language codes that can be 533 * used as the language code when constructing a {@code Locale}. 534 */ getISOLanguages()535 public static String[] getISOLanguages() { 536 return ICU.getISOLanguages(); 537 } 538 539 /** 540 * Returns the language code for this {@code Locale} or the empty string if no language 541 * was set. 542 */ getLanguage()543 public String getLanguage() { 544 return languageCode; 545 } 546 547 /** 548 * Returns the variant code for this {@code Locale} or an empty {@code String} if no variant 549 * was set. 550 */ getVariant()551 public String getVariant() { 552 return variantCode; 553 } 554 555 @Override hashCode()556 public synchronized int hashCode() { 557 return countryCode.hashCode() + languageCode.hashCode() 558 + variantCode.hashCode(); 559 } 560 561 /** 562 * Overrides the default locale. This does not affect system configuration, 563 * and attempts to override the system-provided default locale may 564 * themselves be overridden by actual changes to the system configuration. 565 * Code that calls this method is usually incorrect, and should be fixed by 566 * passing the appropriate locale to each locale-sensitive method that's 567 * called. 568 */ setDefault(Locale locale)569 public synchronized static void setDefault(Locale locale) { 570 if (locale == null) { 571 throw new NullPointerException("locale == null"); 572 } 573 defaultLocale = locale; 574 } 575 576 /** 577 * Returns the string representation of this {@code Locale}. It consists of the 578 * language code, country code and variant separated by underscores. 579 * If the language is missing the string begins 580 * with an underscore. If the country is missing there are 2 underscores 581 * between the language and the variant. The variant cannot stand alone 582 * without a language and/or country code: in this case this method would 583 * return the empty string. 584 * 585 * <p>Examples: "en", "en_US", "_US", "en__POSIX", "en_US_POSIX" 586 */ 587 @Override toString()588 public final String toString() { 589 String result = cachedToStringResult; 590 if (result == null) { 591 result = cachedToStringResult = toNewString(languageCode, countryCode, variantCode); 592 } 593 return result; 594 } 595 toNewString(String languageCode, String countryCode, String variantCode)596 private static String toNewString(String languageCode, String countryCode, String variantCode) { 597 // The string form of a locale that only has a variant is the empty string. 598 if (languageCode.length() == 0 && countryCode.length() == 0) { 599 return ""; 600 } 601 // Otherwise, the output format is "ll_cc_variant", where language and country are always 602 // two letters, but the variant is an arbitrary length. A size of 11 characters has room 603 // for "en_US_POSIX", the largest "common" value. (In practice, the string form is almost 604 // always 5 characters: "ll_cc".) 605 StringBuilder result = new StringBuilder(11); 606 result.append(languageCode); 607 if (countryCode.length() > 0 || variantCode.length() > 0) { 608 result.append('_'); 609 } 610 result.append(countryCode); 611 if (variantCode.length() > 0) { 612 result.append('_'); 613 } 614 result.append(variantCode); 615 return result.toString(); 616 } 617 618 private static final ObjectStreamField[] serialPersistentFields = { 619 new ObjectStreamField("country", String.class), 620 new ObjectStreamField("hashcode", int.class), 621 new ObjectStreamField("language", String.class), 622 new ObjectStreamField("variant", String.class), 623 }; 624 writeObject(ObjectOutputStream stream)625 private void writeObject(ObjectOutputStream stream) throws IOException { 626 ObjectOutputStream.PutField fields = stream.putFields(); 627 fields.put("country", countryCode); 628 fields.put("hashcode", -1); 629 fields.put("language", languageCode); 630 fields.put("variant", variantCode); 631 stream.writeFields(); 632 } 633 readObject(ObjectInputStream stream)634 private void readObject(ObjectInputStream stream) throws IOException, ClassNotFoundException { 635 ObjectInputStream.GetField fields = stream.readFields(); 636 countryCode = (String) fields.get("country", ""); 637 languageCode = (String) fields.get("language", ""); 638 variantCode = (String) fields.get("variant", ""); 639 } 640 } 641