1 /*
   2  * Copyright (c) 2012, 2013, 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.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 package sun.util.locale.provider;
  26 
  27 import java.lang.ref.SoftReference;
  28 import java.text.DateFormat;
  29 import java.text.DateFormatSymbols;
  30 import java.text.DecimalFormat;
  31 import java.text.DecimalFormatSymbols;
  32 import java.text.NumberFormat;
  33 import java.text.SimpleDateFormat;
  34 import java.text.spi.DateFormatProvider;
  35 import java.text.spi.DateFormatSymbolsProvider;
  36 import java.text.spi.DecimalFormatSymbolsProvider;
  37 import java.text.spi.NumberFormatProvider;
  38 import java.util.Calendar;
  39 import java.util.Collections;
  40 import java.util.Currency;
  41 import java.util.HashSet;
  42 import java.util.Locale;
  43 import java.util.Map;
  44 import java.util.ResourceBundle.Control;
  45 import java.util.Set;
  46 import java.util.TimeZone;
  47 import java.util.concurrent.ConcurrentHashMap;
  48 import java.util.concurrent.ConcurrentMap;
  49 import java.util.concurrent.atomic.AtomicReferenceArray;
  50 import java.util.spi.CalendarDataProvider;
  51 import java.util.spi.CalendarNameProvider;
  52 import java.util.spi.CurrencyNameProvider;
  53 import java.util.spi.LocaleNameProvider;
  54 import sun.util.spi.CalendarProvider;
  55 
  56 /**
  57  * LocaleProviderdapter implementation for the Windows locale data.
  58  *
  59  * @author Naoto Sato
  60  */
  61 public class HostLocaleProviderAdapterImpl {
  62 
  63     // locale categories
  64     private static final int CAT_DISPLAY = 0;
  65     private static final int CAT_FORMAT  = 1;
  66 
  67     // NumberFormat styles
  68     private static final int NF_NUMBER   = 0;
  69     private static final int NF_CURRENCY = 1;
  70     private static final int NF_PERCENT  = 2;
  71     private static final int NF_INTEGER  = 3;
  72     private static final int NF_MAX = NF_INTEGER;
  73 
  74     // CalendarData value types
  75     private static final int CD_FIRSTDAYOFWEEK = 0;
  76     private static final int CD_MINIMALDAYSINFIRSTWEEK = 1;
  77 
  78     // Currency/Locale display name types
  79     private static final int DN_CURRENCY_NAME   = 0;
  80     private static final int DN_CURRENCY_SYMBOL = 1;
  81     private static final int DN_LOCALE_LANGUAGE = 2;
  82     private static final int DN_LOCALE_SCRIPT   = 3;
  83     private static final int DN_LOCALE_REGION   = 4;
  84     private static final int DN_LOCALE_VARIANT  = 5;
  85 
  86     // Native Calendar ID to LDML calendar type map
  87     private static final String[] calIDToLDML = {
  88         "",
  89         "gregory",
  90         "gregory_en-US",
  91         "japanese",
  92         "roc",
  93         "",          // No appropriate type for CAL_KOREA
  94         "islamic",
  95         "buddhist",
  96         "hebrew",
  97         "gregory_fr",
  98         "gregory_ar",
  99         "gregory_en",
 100         "gregory_fr",
 101     };
 102 
 103     // Caches
 104     private static ConcurrentMap<Locale, SoftReference<AtomicReferenceArray<String>>> dateFormatCache = new ConcurrentHashMap<>();
 105     private static ConcurrentMap<Locale, SoftReference<DateFormatSymbols>> dateFormatSymbolsCache = new ConcurrentHashMap<>();
 106     private static ConcurrentMap<Locale, SoftReference<AtomicReferenceArray<String>>> numberFormatCache = new ConcurrentHashMap<>();
 107     private static ConcurrentMap<Locale, SoftReference<DecimalFormatSymbols>> decimalFormatSymbolsCache = new ConcurrentHashMap<>();
 108 
 109     private static final Set<Locale> supportedLocaleSet;
 110     private static final String nativeDisplayLanguage;
 111     static {
 112         Set<Locale> tmpSet = new HashSet<>();
 113         if (initialize()) {
 114             // Assuming the default locales do not include any extensions, so
 115             // no stripping is needed here.
 116             Control c = Control.getNoFallbackControl(Control.FORMAT_DEFAULT);
 117             String displayLocale = getDefaultLocale(CAT_DISPLAY);
 118             Locale l = Locale.forLanguageTag(displayLocale.replace('_', '-'));
 119             tmpSet.addAll(c.getCandidateLocales("", l));
 120             nativeDisplayLanguage = l.getLanguage();
 121 
 122             String formatLocale = getDefaultLocale(CAT_FORMAT);
 123             if (!formatLocale.equals(displayLocale)) {
 124                 l = Locale.forLanguageTag(formatLocale.replace('_', '-'));
 125                 tmpSet.addAll(c.getCandidateLocales("", l));
 126             }
 127         } else {
 128             nativeDisplayLanguage = "";
 129         }
 130         supportedLocaleSet = Collections.unmodifiableSet(tmpSet);
 131     }
 132     private final static Locale[] supportedLocale = supportedLocaleSet.toArray(new Locale[0]);
 133 
 134     public static DateFormatProvider getDateFormatProvider() {
 135         return new DateFormatProvider() {
 136             @Override
 137             public Locale[] getAvailableLocales() {
 138                 return getSupportedCalendarLocales();
 139             }
 140 
 141             @Override
 142             public boolean isSupportedLocale(Locale locale) {
 143                 return isSupportedCalendarLocale(locale);
 144             }
 145 
 146             @Override
 147             public DateFormat getDateInstance(int style, Locale locale) {
 148                 AtomicReferenceArray<String> patterns = getDateTimePatterns(locale);
 149                 return new SimpleDateFormat(patterns.get(style/2),
 150                                             getCalendarLocale(locale));
 151             }
 152 
 153             @Override
 154             public DateFormat getTimeInstance(int style, Locale locale) {
 155                 AtomicReferenceArray<String> patterns = getDateTimePatterns(locale);
 156                 return new SimpleDateFormat(patterns.get(style/2+2),
 157                                             getCalendarLocale(locale));
 158             }
 159 
 160             @Override
 161             public DateFormat getDateTimeInstance(int dateStyle,
 162                     int timeStyle, Locale locale) {
 163                 AtomicReferenceArray<String> patterns = getDateTimePatterns(locale);
 164                 String pattern = new StringBuilder(patterns.get(dateStyle/2))
 165                                        .append(" ")
 166                                        .append(patterns.get(timeStyle/2+2))
 167                                        .toString();
 168                 return new SimpleDateFormat(pattern, getCalendarLocale(locale));
 169             }
 170 
 171             private AtomicReferenceArray<String> getDateTimePatterns(Locale locale) {
 172                 AtomicReferenceArray<String> patterns;
 173                 SoftReference<AtomicReferenceArray<String>> ref = dateFormatCache.get(locale);
 174 
 175                 if (ref == null || (patterns = ref.get()) == null) {
 176                     String langtag = removeExtensions(locale).toLanguageTag();
 177                     patterns = new AtomicReferenceArray<>(4);
 178                     patterns.compareAndSet(0, null, convertDateTimePattern(
 179                         getDateTimePattern(DateFormat.LONG, -1, langtag)));
 180                     patterns.compareAndSet(1, null, convertDateTimePattern(
 181                         getDateTimePattern(DateFormat.SHORT, -1, langtag)));
 182                     patterns.compareAndSet(2, null, convertDateTimePattern(
 183                         getDateTimePattern(-1, DateFormat.LONG, langtag)));
 184                     patterns.compareAndSet(3, null, convertDateTimePattern(
 185                         getDateTimePattern(-1, DateFormat.SHORT, langtag)));
 186                     ref = new SoftReference<>(patterns);
 187                     dateFormatCache.put(locale, ref);
 188                 }
 189 
 190                 return patterns;
 191             }
 192         };
 193     }
 194 
 195     public static DateFormatSymbolsProvider getDateFormatSymbolsProvider() {
 196         return new DateFormatSymbolsProvider() {
 197 
 198             @Override
 199             public Locale[] getAvailableLocales() {
 200                 return getSupportedCalendarLocales();
 201             }
 202 
 203             @Override
 204             public boolean isSupportedLocale(Locale locale) {
 205                 return isSupportedCalendarLocale(locale);
 206             }
 207 
 208             @Override
 209             public DateFormatSymbols getInstance(Locale locale) {
 210                 DateFormatSymbols dfs;
 211                 SoftReference<DateFormatSymbols> ref =
 212                     dateFormatSymbolsCache.get(locale);
 213 
 214                 if (ref == null || (dfs = ref.get()) == null) {
 215                     dfs = new DateFormatSymbols(locale);
 216                     String langTag = removeExtensions(locale).toLanguageTag();
 217 
 218                     dfs.setAmPmStrings(getAmPmStrings(langTag, dfs.getAmPmStrings()));
 219                     dfs.setEras(getEras(langTag, dfs.getEras()));
 220                     dfs.setMonths(getMonths(langTag, dfs.getMonths()));
 221                     dfs.setShortMonths(getShortMonths(langTag, dfs.getShortMonths()));
 222                     dfs.setWeekdays(getWeekdays(langTag, dfs.getWeekdays()));
 223                     dfs.setShortWeekdays(getShortWeekdays(langTag, dfs.getShortWeekdays()));
 224                     ref = new SoftReference<>(dfs);
 225                     dateFormatSymbolsCache.put(locale, ref);
 226                 }
 227                 return (DateFormatSymbols)dfs.clone();
 228             }
 229         };
 230     }
 231 
 232     public static NumberFormatProvider getNumberFormatProvider() {
 233         return new NumberFormatProvider() {
 234 
 235             @Override
 236             public Locale[] getAvailableLocales() {
 237                 return getSupportedNativeDigitLocales();
 238             }
 239 
 240             @Override
 241             public boolean isSupportedLocale(Locale locale) {
 242                 return isSupportedNativeDigitLocale(locale);
 243             }
 244 
 245             @Override
 246             public NumberFormat getCurrencyInstance(Locale locale) {
 247                 AtomicReferenceArray<String> patterns = getNumberPatterns(locale);
 248                 return new DecimalFormat(patterns.get(NF_CURRENCY),
 249                     DecimalFormatSymbols.getInstance(locale));
 250             }
 251 
 252             @Override
 253             public NumberFormat getIntegerInstance(Locale locale) {
 254                 AtomicReferenceArray<String> patterns = getNumberPatterns(locale);
 255                 return new DecimalFormat(patterns.get(NF_INTEGER),
 256                     DecimalFormatSymbols.getInstance(locale));
 257             }
 258 
 259             @Override
 260             public NumberFormat getNumberInstance(Locale locale) {
 261                 AtomicReferenceArray<String> patterns = getNumberPatterns(locale);
 262                 return new DecimalFormat(patterns.get(NF_NUMBER),
 263                     DecimalFormatSymbols.getInstance(locale));
 264             }
 265 
 266             @Override
 267             public NumberFormat getPercentInstance(Locale locale) {
 268                 AtomicReferenceArray<String> patterns = getNumberPatterns(locale);
 269                 return new DecimalFormat(patterns.get(NF_PERCENT),
 270                     DecimalFormatSymbols.getInstance(locale));
 271             }
 272 
 273             private AtomicReferenceArray<String> getNumberPatterns(Locale locale) {
 274                 AtomicReferenceArray<String> patterns;
 275                 SoftReference<AtomicReferenceArray<String>> ref = numberFormatCache.get(locale);
 276 
 277                 if (ref == null || (patterns = ref.get()) == null) {
 278                     String langtag = locale.toLanguageTag();
 279                     patterns = new AtomicReferenceArray<>(NF_MAX+1);
 280                     for (int i = 0; i <= NF_MAX; i++) {
 281                         patterns.compareAndSet(i, null, getNumberPattern(i, langtag));
 282                     }
 283                     ref = new SoftReference<>(patterns);
 284                     numberFormatCache.put(locale, ref);
 285                 }
 286                 return patterns;
 287             }
 288         };
 289     }
 290 
 291     public static DecimalFormatSymbolsProvider getDecimalFormatSymbolsProvider() {
 292         return new DecimalFormatSymbolsProvider() {
 293 
 294             @Override
 295             public Locale[] getAvailableLocales() {
 296                 return getSupportedNativeDigitLocales();
 297             }
 298 
 299             @Override
 300             public boolean isSupportedLocale(Locale locale) {
 301                 return isSupportedNativeDigitLocale(locale);
 302             }
 303 
 304             @Override
 305             public DecimalFormatSymbols getInstance(Locale locale) {
 306                 DecimalFormatSymbols dfs;
 307                 SoftReference<DecimalFormatSymbols> ref =
 308                     decimalFormatSymbolsCache.get(locale);
 309 
 310                 if (ref == null || (dfs = ref.get()) == null) {
 311                     dfs = new DecimalFormatSymbols(getNumberLocale(locale));
 312                     String langTag = removeExtensions(locale).toLanguageTag();
 313 
 314                     // DecimalFormatSymbols.setInternationalCurrencySymbol() has
 315                     // a side effect of setting the currency symbol as well. So
 316                     // the calling order is relevant here.
 317                     dfs.setInternationalCurrencySymbol(getInternationalCurrencySymbol(langTag, dfs.getInternationalCurrencySymbol()));
 318                     dfs.setCurrencySymbol(getCurrencySymbol(langTag, dfs.getCurrencySymbol()));
 319                     dfs.setDecimalSeparator(getDecimalSeparator(langTag, dfs.getDecimalSeparator()));
 320                     dfs.setGroupingSeparator(getGroupingSeparator(langTag, dfs.getGroupingSeparator()));
 321                     dfs.setInfinity(getInfinity(langTag, dfs.getInfinity()));
 322                     dfs.setMinusSign(getMinusSign(langTag, dfs.getMinusSign()));
 323                     dfs.setMonetaryDecimalSeparator(getMonetaryDecimalSeparator(langTag, dfs.getMonetaryDecimalSeparator()));
 324                     dfs.setNaN(getNaN(langTag, dfs.getNaN()));
 325                     dfs.setPercent(getPercent(langTag, dfs.getPercent()));
 326                     dfs.setPerMill(getPerMill(langTag, dfs.getPerMill()));
 327                     dfs.setZeroDigit(getZeroDigit(langTag, dfs.getZeroDigit()));
 328                     ref = new SoftReference<>(dfs);
 329                     decimalFormatSymbolsCache.put(locale, ref);
 330                 }
 331                 return (DecimalFormatSymbols)dfs.clone();
 332             }
 333         };
 334     }
 335 
 336     public static CalendarDataProvider getCalendarDataProvider() {
 337         return new CalendarDataProvider() {
 338             @Override
 339             public Locale[] getAvailableLocales() {
 340                 return getSupportedCalendarLocales();
 341             }
 342 
 343             @Override
 344             public boolean isSupportedLocale(Locale locale) {
 345                 return isSupportedCalendarLocale(locale);
 346             }
 347 
 348             @Override
 349             public int getFirstDayOfWeek(Locale locale) {
 350                 int first = getCalendarDataValue(
 351                                  removeExtensions(locale).toLanguageTag(),
 352                                  CD_FIRSTDAYOFWEEK);
 353                 if (first != -1) {
 354                     return (first + 1) % 7 + 1;
 355                 } else {
 356                     return 0;
 357                 }
 358             }
 359 
 360             @Override
 361             public int getMinimalDaysInFirstWeek(Locale locale) {
 362                 return 0;
 363             }
 364         };
 365     }
 366 
 367     public static CalendarNameProvider getCalendarNameProvider() {
 368         return new CalendarNameProvider() {
 369             @Override
 370             public Locale[] getAvailableLocales() {
 371                 return getSupportedCalendarLocales();
 372             }
 373 
 374             @Override
 375             public boolean isSupportedLocale(Locale locale) {
 376                 return isSupportedCalendarLocale(locale);
 377             }
 378 
 379             @Override
 380             public String getDisplayName(String calType, int field, int value,
 381                                          int style, Locale locale) {
 382                 return null;
 383             }
 384 
 385             @Override
 386             public Map<String, Integer> getDisplayNames(String calType,
 387                                          int field, int style, Locale locale) {
 388                 return null;
 389             }
 390         };
 391     }
 392 
 393     public static CalendarProvider getCalendarProvider() {
 394         return new CalendarProvider() {
 395             @Override
 396             public Locale[] getAvailableLocales() {
 397                 return getSupportedCalendarLocales();
 398             }
 399 
 400             @Override
 401             public boolean isSupportedLocale(Locale locale) {
 402                 return isSupportedCalendarLocale(locale);
 403             }
 404 
 405             @Override
 406             public Calendar getInstance(TimeZone zone, Locale locale) {
 407                 return new Calendar.Builder()
 408                              .setLocale(getCalendarLocale(locale))
 409                              .setTimeZone(zone)
 410                              .setInstant(System.currentTimeMillis())
 411                              .build();
 412             }
 413         };
 414     }
 415 
 416     public static CurrencyNameProvider getCurrencyNameProvider() {
 417         return new CurrencyNameProvider() {
 418             @Override
 419             public Locale[] getAvailableLocales() {
 420                 return supportedLocale;
 421             }
 422 
 423             @Override
 424             public boolean isSupportedLocale(Locale locale) {
 425                 // Ignore the extensions for now
 426                 return supportedLocaleSet.contains(locale.stripExtensions()) &&
 427                        locale.getLanguage().equals(nativeDisplayLanguage);
 428             }
 429 
 430             @Override
 431             public String getSymbol(String currencyCode, Locale locale) {
 432                 // Retrieves the currency symbol by calling
 433                 // GetLocaleInfoEx(LOCALE_SCURRENCY).
 434                 // It only works with the "locale"'s currency in its native
 435                 // language.
 436                 try {
 437                     if (Currency.getInstance(locale).getCurrencyCode()
 438                         .equals(currencyCode)) {
 439                         return getDisplayString(locale.toLanguageTag(),
 440                                 DN_CURRENCY_SYMBOL, currencyCode);
 441                     }
 442                 } catch (IllegalArgumentException iae) {}
 443                 return null;
 444             }
 445 
 446             @Override
 447             public String getDisplayName(String currencyCode, Locale locale) {
 448                 // Retrieves the display name by calling
 449                 // GetLocaleInfoEx(LOCALE_SNATIVECURRNAME).
 450                 // It only works with the "locale"'s currency in its native
 451                 // language.
 452                 try {
 453                     if (Currency.getInstance(locale).getCurrencyCode()
 454                         .equals(currencyCode)) {
 455                         return getDisplayString(locale.toLanguageTag(),
 456                                 DN_CURRENCY_NAME, currencyCode);
 457                     }
 458                 } catch (IllegalArgumentException iae) {}
 459                 return null;
 460             }
 461         };
 462     }
 463 
 464     public static LocaleNameProvider getLocaleNameProvider() {
 465         return new LocaleNameProvider() {
 466             @Override
 467             public Locale[] getAvailableLocales() {
 468                 return supportedLocale;
 469             }
 470 
 471             @Override
 472             public boolean isSupportedLocale(Locale locale) {
 473                 return supportedLocaleSet.contains(locale.stripExtensions()) &&
 474                        locale.getLanguage().equals(nativeDisplayLanguage);
 475             }
 476 
 477             @Override
 478             public String getDisplayLanguage(String languageCode, Locale locale) {
 479                 // Retrieves the display language name by calling
 480                 // GetLocaleInfoEx(LOCALE_SLOCALIZEDLANGUAGENAME).
 481                 return getDisplayString(locale.toLanguageTag(),
 482                             DN_LOCALE_LANGUAGE, languageCode);
 483             }
 484 
 485             @Override
 486             public String getDisplayCountry(String countryCode, Locale locale) {
 487                 // Retrieves the display country name by calling
 488                 // GetLocaleInfoEx(LOCALE_SLOCALIZEDCOUNTRYNAME).
 489                 return getDisplayString(locale.toLanguageTag(),
 490                             DN_LOCALE_REGION, nativeDisplayLanguage+"-"+countryCode);
 491             }
 492 
 493             @Override
 494             public String getDisplayScript(String scriptCode, Locale locale) {
 495                 return null;
 496             }
 497 
 498             @Override
 499             public String getDisplayVariant(String variantCode, Locale locale) {
 500                 return null;
 501             }
 502         };
 503     }
 504 
 505 
 506     private static String convertDateTimePattern(String winPattern) {
 507         String ret = winPattern.replaceAll("dddd", "EEEE");
 508         ret = ret.replaceAll("ddd", "EEE");
 509         ret = ret.replaceAll("tt", "aa");
 510         ret = ret.replaceAll("g", "GG");
 511         return ret;
 512     }
 513 
 514     private static Locale[] getSupportedCalendarLocales() {
 515         if (supportedLocale.length != 0 &&
 516             supportedLocaleSet.contains(Locale.JAPAN) &&
 517             isJapaneseCalendar()) {
 518             Locale[] sup = new Locale[supportedLocale.length+1];
 519             sup[0] = JRELocaleConstants.JA_JP_JP;
 520             System.arraycopy(supportedLocale, 0, sup, 1, supportedLocale.length);
 521             return sup;
 522         }
 523         return supportedLocale;
 524     }
 525 
 526     private static boolean isSupportedCalendarLocale(Locale locale) {
 527         Locale base = locale;
 528 
 529         if (base.hasExtensions() || base.getVariant() != "") {
 530             // strip off extensions and variant.
 531             base = new Locale.Builder()
 532                             .setLocale(locale)
 533                             .clearExtensions()
 534                             .build();
 535         }
 536 
 537         if (!supportedLocaleSet.contains(base)) {
 538             return false;
 539         }
 540 
 541         int calid = getCalendarID(base.toLanguageTag());
 542         if (calid <= 0 || calid >= calIDToLDML.length) {
 543             return false;
 544         }
 545 
 546         String requestedCalType = locale.getUnicodeLocaleType("ca");
 547         String nativeCalType = calIDToLDML[calid]
 548                 .replaceFirst("_.*", ""); // remove locale part.
 549 
 550         if (requestedCalType == null) {
 551             return Calendar.getAvailableCalendarTypes().contains(nativeCalType);
 552         } else {
 553             return requestedCalType.equals(nativeCalType);
 554         }
 555     }
 556 
 557     private static Locale[] getSupportedNativeDigitLocales() {
 558         if (supportedLocale.length != 0 &&
 559             supportedLocaleSet.contains(JRELocaleConstants.TH_TH) &&
 560             isNativeDigit("th-TH")) {
 561             Locale[] sup = new Locale[supportedLocale.length+1];
 562             sup[0] = JRELocaleConstants.TH_TH_TH;
 563             System.arraycopy(supportedLocale, 0, sup, 1, supportedLocale.length);
 564             return sup;
 565         }
 566         return supportedLocale;
 567     }
 568 
 569     private static boolean isSupportedNativeDigitLocale(Locale locale) {
 570         // special case for th_TH_TH
 571         if (JRELocaleConstants.TH_TH_TH.equals(locale)) {
 572             return isNativeDigit("th-TH");
 573         }
 574 
 575         String numtype = null;
 576         Locale base = locale;
 577         if (locale.hasExtensions()) {
 578             numtype = locale.getUnicodeLocaleType("nu");
 579             base = locale.stripExtensions();
 580         }
 581 
 582         if (supportedLocaleSet.contains(base)) {
 583             // Only supports Latin or Thai (in thai locales) digits.
 584             if (numtype == null || numtype.equals("latn")) {
 585                 return true;
 586             } else if (locale.getLanguage().equals("th")) {
 587                 return "thai".equals(numtype) &&
 588                        isNativeDigit(locale.toLanguageTag());
 589             }
 590         }
 591 
 592         return false;
 593     }
 594 
 595     private static Locale removeExtensions(Locale src) {
 596         return new Locale.Builder().setLocale(src).clearExtensions().build();
 597     }
 598 
 599     private static boolean isJapaneseCalendar() {
 600         return getCalendarID("ja-JP") == 3; // 3: CAL_JAPAN
 601     }
 602 
 603     private static Locale getCalendarLocale(Locale locale) {
 604         int calid = getCalendarID(locale.toLanguageTag());
 605         if (calid > 0 && calid < calIDToLDML.length) {
 606             Locale.Builder lb = new Locale.Builder();
 607             String[] caltype = calIDToLDML[calid].split("_");
 608             if (caltype.length > 1) {
 609                 lb.setLocale(Locale.forLanguageTag(caltype[1]));
 610             } else {
 611                 lb.setLocale(locale);
 612             }
 613             lb.setUnicodeLocaleKeyword("ca", caltype[0]);
 614             return lb.build();
 615         }
 616 
 617         return locale;
 618     }
 619 
 620     private static Locale getNumberLocale(Locale src) {
 621         if (JRELocaleConstants.TH_TH.equals(src)) {
 622             if (isNativeDigit("th-TH")) {
 623                 Locale.Builder lb = new Locale.Builder().setLocale(src);
 624                 lb.setUnicodeLocaleKeyword("nu", "thai");
 625                 return lb.build();
 626             }
 627         }
 628 
 629         return src;
 630     }
 631 
 632     // native methods
 633 
 634     // initialize
 635     private static native boolean initialize();
 636     private static native String getDefaultLocale(int cat);
 637 
 638     // For DateFormatProvider
 639     private static native String getDateTimePattern(int dateStyle, int timeStyle, String langTag);
 640     private static native int getCalendarID(String langTag);
 641 
 642     // For DateFormatSymbolsProvider
 643     private static native String[] getAmPmStrings(String langTag, String[] ampm);
 644     private static native String[] getEras(String langTag, String[] eras);
 645     private static native String[] getMonths(String langTag, String[] months);
 646     private static native String[] getShortMonths(String langTag, String[] smonths);
 647     private static native String[] getWeekdays(String langTag, String[] wdays);
 648     private static native String[] getShortWeekdays(String langTag, String[] swdays);
 649 
 650     // For NumberFormatProvider
 651     private static native String getNumberPattern(int numberStyle, String langTag);
 652     private static native boolean isNativeDigit(String langTag);
 653 
 654     // For DecimalFormatSymbolsProvider
 655     private static native String getCurrencySymbol(String langTag, String currencySymbol);
 656     private static native char getDecimalSeparator(String langTag, char decimalSeparator);
 657     private static native char getGroupingSeparator(String langTag, char groupingSeparator);
 658     private static native String getInfinity(String langTag, String infinity);
 659     private static native String getInternationalCurrencySymbol(String langTag, String internationalCurrencySymbol);
 660     private static native char getMinusSign(String langTag, char minusSign);
 661     private static native char getMonetaryDecimalSeparator(String langTag, char monetaryDecimalSeparator);
 662     private static native String getNaN(String langTag, String nan);
 663     private static native char getPercent(String langTag, char percent);
 664     private static native char getPerMill(String langTag, char perMill);
 665     private static native char getZeroDigit(String langTag, char zeroDigit);
 666 
 667     // For CalendarDataProvider
 668     private static native int getCalendarDataValue(String langTag, int type);
 669 
 670     // For Locale/CurrencyNameProvider
 671     private static native String getDisplayString(String langTag, int key, String value);
 672 }