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