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