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