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