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.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                 return getDisplayString(locale.toLanguageTag(),
 514                             DN_LOCALE_REGION, nativeDisplayLanguage+"-"+countryCode);
 515             }
 516 
 517             @Override
 518             public String getDisplayScript(String scriptCode, Locale locale) {
 519                 return null;
 520             }
 521 
 522             @Override
 523             public String getDisplayVariant(String variantCode, Locale locale) {
 524                 return null;
 525             }
 526         };
 527     }
 528 
 529     public static JavaTimeDateTimePatternProvider getJavaTimeDateTimePatternProvider() {
 530         return new JavaTimeDateTimePatternProvider() {
 531             @Override
 532             public Locale[] getAvailableLocales() {
 533                 return getSupportedCalendarLocales();
 534             }
 535 
 536             @Override
 537             public boolean isSupportedLocale(Locale locale) {
 538                 return isSupportedCalendarLocale(locale);
 539             }
 540 
 541             @Override
 542             public String getJavaTimeDateTimePattern(int timeStyle, int dateStyle, String calType, Locale locale) {
 543                 AtomicReferenceArray<String> patterns = getDateTimePatterns(locale);
 544                 String pattern = new StringBuilder(patterns.get(dateStyle / 2))
 545                         .append(" ")
 546                         .append(patterns.get(timeStyle / 2 + 2))
 547                         .toString();
 548                 return toJavaTimeDateTimePattern(calType, pattern);
 549 
 550             }
 551 
 552             private AtomicReferenceArray<String> getDateTimePatterns(Locale locale) {
 553                 AtomicReferenceArray<String> patterns;
 554                 SoftReference<AtomicReferenceArray<String>> ref = dateFormatCache.get(locale);
 555 
 556                 if (ref == null || (patterns = ref.get()) == null) {
 557                     String langtag = removeExtensions(locale).toLanguageTag();
 558                     patterns = new AtomicReferenceArray<>(4);
 559                     patterns.compareAndSet(0, null, convertDateTimePattern(
 560                             getDateTimePattern(DateFormat.LONG, -1, langtag)));
 561                     patterns.compareAndSet(1, null, convertDateTimePattern(
 562                             getDateTimePattern(DateFormat.SHORT, -1, langtag)));
 563                     patterns.compareAndSet(2, null, convertDateTimePattern(
 564                             getDateTimePattern(-1, DateFormat.LONG, langtag)));
 565                     patterns.compareAndSet(3, null, convertDateTimePattern(
 566                             getDateTimePattern(-1, DateFormat.SHORT, langtag)));
 567                     ref = new SoftReference<>(patterns);
 568                     dateFormatCache.put(locale, ref);
 569                 }
 570                 return patterns;
 571             }
 572             /**
 573              * This method will convert JRE Date/time Pattern String to JSR310
 574              * type Date/Time Pattern
 575              */
 576             private String toJavaTimeDateTimePattern(String calendarType, String jrePattern) {
 577                 int length = jrePattern.length();
 578                 StringBuilder sb = new StringBuilder(length);
 579                 boolean inQuote = false;
 580                 int count = 0;
 581                 char lastLetter = 0;
 582                 for (int i = 0; i < length; i++) {
 583                     char c = jrePattern.charAt(i);
 584                     if (c == '\'') {
 585                         // '' is treated as a single quote regardless of being
 586                         // in a quoted section.
 587                         if ((i + 1) < length) {
 588                             char nextc = jrePattern.charAt(i + 1);
 589                             if (nextc == '\'') {
 590                                 i++;
 591                                 if (count != 0) {
 592                                     convert(calendarType, lastLetter, count, sb);
 593                                     lastLetter = 0;
 594                                     count = 0;
 595                                 }
 596                                 sb.append("''");
 597                                 continue;
 598                             }
 599                         }
 600                         if (!inQuote) {
 601                             if (count != 0) {
 602                                 convert(calendarType, lastLetter, count, sb);
 603                                 lastLetter = 0;
 604                                 count = 0;
 605                             }
 606                             inQuote = true;
 607                         } else {
 608                             inQuote = false;
 609                         }
 610                         sb.append(c);
 611                         continue;
 612                     }
 613                     if (inQuote) {
 614                         sb.append(c);
 615                         continue;
 616                     }
 617                     if (!(c >= 'a' && c <= 'z' || c >= 'A' && c <= 'Z')) {
 618                         if (count != 0) {
 619                             convert(calendarType, lastLetter, count, sb);
 620                             lastLetter = 0;
 621                             count = 0;
 622                         }
 623                         sb.append(c);
 624                         continue;
 625                     }
 626                     if (lastLetter == 0 || lastLetter == c) {
 627                         lastLetter = c;
 628                         count++;
 629                         continue;
 630                     }
 631                     convert(calendarType, lastLetter, count, sb);
 632                     lastLetter = c;
 633                     count = 1;
 634                 }
 635                 if (inQuote) {
 636                     // should not come here.
 637                     // returning null so that FALLBACK provider will kick in.
 638                     return null;
 639                 }
 640                 if (count != 0) {
 641                     convert(calendarType, lastLetter, count, sb);
 642                 }
 643                 return sb.toString();
 644             }
 645 
 646             private void convert(String calendarType, char letter, int count, StringBuilder sb) {
 647                 switch (letter) {
 648                     case 'G':
 649                         if (calendarType.equals("japanese")) {
 650                             if (count >= 4) {
 651                                 count = 1;
 652                             } else {
 653                                 count = 5;
 654                             }
 655                         } else if (!calendarType.equals("iso8601")) {
 656                             // Adjust the number of 'G's
 657                             // Gregorian calendar is iso8601 for java.time
 658                             if (count >= 4) {
 659                                 // JRE full -> JavaTime full
 660                                 count = 4;
 661                             } else {
 662                                 // JRE short -> JavaTime short
 663                                 count = 1;
 664                             }
 665                         }
 666                         break;
 667                     case 'y':
 668                         if (calendarType.equals("japanese") && count >= 4) {
 669                             // JRE specific "gan-nen" support
 670                             count = 1;
 671                         }
 672                         break;
 673                     default:
 674                         // JSR 310 and CLDR define 5-letter patterns for narrow text.
 675                         if (count > 4) {
 676                             count = 4;
 677                         }
 678                         break;
 679                 }
 680                 appendN(letter, count, sb);
 681             }
 682 
 683             private void appendN(char c, int n, StringBuilder sb) {
 684                 for (int i = 0; i < n; i++) {
 685                     sb.append(c);
 686                 }
 687             }
 688         };
 689     }
 690 
 691     private static String convertDateTimePattern(String winPattern) {
 692         String ret = winPattern.replaceAll("dddd", "EEEE");
 693         ret = ret.replaceAll("ddd", "EEE");
 694         ret = ret.replaceAll("tt", "aa");
 695         ret = ret.replaceAll("g", "GG");
 696         return ret;
 697     }
 698 
 699     private static Locale[] getSupportedCalendarLocales() {
 700         if (supportedLocale.length != 0 &&
 701             supportedLocaleSet.contains(Locale.JAPAN) &&
 702             isJapaneseCalendar()) {
 703             Locale[] sup = new Locale[supportedLocale.length+1];
 704             sup[0] = JRELocaleConstants.JA_JP_JP;
 705             System.arraycopy(supportedLocale, 0, sup, 1, supportedLocale.length);
 706             return sup;
 707         }
 708         return supportedLocale;
 709     }
 710 
 711     private static boolean isSupportedCalendarLocale(Locale locale) {
 712         Locale base = stripVariantAndExtensions(locale);
 713 
 714         if (!supportedLocaleSet.contains(base)) {
 715             return false;
 716         }
 717 
 718         int calid = getCalendarID(base.toLanguageTag());
 719         if (calid <= 0 || calid >= calIDToLDML.length) {
 720             return false;
 721         }
 722 
 723         String requestedCalType = locale.getUnicodeLocaleType("ca");
 724         String nativeCalType = calIDToLDML[calid]
 725                 .replaceFirst("_.*", ""); // remove locale part.
 726 
 727         if (requestedCalType == null) {
 728             return Calendar.getAvailableCalendarTypes().contains(nativeCalType);
 729         } else {
 730             return requestedCalType.equals(nativeCalType);
 731         }
 732     }
 733 
 734     private static Locale[] getSupportedNativeDigitLocales() {
 735         if (supportedLocale.length != 0 &&
 736             supportedLocaleSet.contains(JRELocaleConstants.TH_TH) &&
 737             isNativeDigit("th-TH")) {
 738             Locale[] sup = new Locale[supportedLocale.length+1];
 739             sup[0] = JRELocaleConstants.TH_TH_TH;
 740             System.arraycopy(supportedLocale, 0, sup, 1, supportedLocale.length);
 741             return sup;
 742         }
 743         return supportedLocale;
 744     }
 745 
 746     private static boolean isSupportedNativeDigitLocale(Locale locale) {
 747         // special case for th_TH_TH
 748         if (JRELocaleConstants.TH_TH_TH.equals(locale)) {
 749             return isNativeDigit("th-TH");
 750         }
 751 
 752         String numtype = null;
 753         Locale base = locale;
 754         if (locale.hasExtensions()) {
 755             numtype = locale.getUnicodeLocaleType("nu");
 756             base = locale.stripExtensions();
 757         }
 758 
 759         if (supportedLocaleSet.contains(base)) {
 760             // Only supports Latin or Thai (in thai locales) digits.
 761             if (numtype == null || numtype.equals("latn")) {
 762                 return true;
 763             } else if (locale.getLanguage().equals("th")) {
 764                 return "thai".equals(numtype) &&
 765                        isNativeDigit(locale.toLanguageTag());
 766             }
 767         }
 768 
 769         return false;
 770     }
 771 
 772     private static Locale removeExtensions(Locale src) {
 773         return new Locale.Builder().setLocale(src).clearExtensions().build();
 774     }
 775 
 776     private static boolean isJapaneseCalendar() {
 777         return getCalendarID("ja-JP") == CAL_JAPAN;
 778     }
 779 
 780     private static Locale stripVariantAndExtensions(Locale locale) {
 781         if (locale.hasExtensions() || locale.getVariant() != "") {
 782             // strip off extensions and variant.
 783             locale = new Locale.Builder()
 784                             .setLocale(locale)
 785                             .clearExtensions()
 786                             .build();
 787         }
 788 
 789         return locale;
 790     }
 791 
 792     private static Locale getCalendarLocale(Locale locale) {
 793         int calid = getCalendarID(stripVariantAndExtensions(locale).toLanguageTag());
 794         if (calid > 0 && calid < calIDToLDML.length) {
 795             Locale.Builder lb = new Locale.Builder();
 796             String[] caltype = calIDToLDML[calid].split("_");
 797             if (caltype.length > 1) {
 798                 lb.setLocale(Locale.forLanguageTag(caltype[1]));
 799             } else {
 800                 lb.setLocale(locale);
 801             }
 802             lb.setUnicodeLocaleKeyword("ca", caltype[0]);
 803             return lb.build();
 804         }
 805 
 806         return locale;
 807     }
 808 
 809     private static int getCalendarIDFromLDMLType(String ldmlType) {
 810         for (int i = 0; i < calIDToLDML.length; i++) {
 811             if (calIDToLDML[i].startsWith(ldmlType)) {
 812                 return i;
 813             }
 814         }
 815         return -1;
 816     }
 817 
 818     private static Locale getNumberLocale(Locale src) {
 819         if (JRELocaleConstants.TH_TH.equals(src)) {
 820             if (isNativeDigit("th-TH")) {
 821                 Locale.Builder lb = new Locale.Builder().setLocale(src);
 822                 lb.setUnicodeLocaleKeyword("nu", "thai");
 823                 return lb.build();
 824             }
 825         }
 826 
 827         return src;
 828     }
 829 
 830     // native methods
 831 
 832     // initialize
 833     private static native boolean initialize();
 834     private static native String getDefaultLocale(int cat);
 835 
 836     // For DateFormatProvider
 837     private static native String getDateTimePattern(int dateStyle, int timeStyle, String langTag);
 838     private static native int getCalendarID(String langTag);
 839 
 840     // For DateFormatSymbolsProvider
 841     private static native String[] getAmPmStrings(String langTag, String[] ampm);
 842     private static native String[] getEras(String langTag, String[] eras);
 843     private static native String[] getMonths(String langTag, String[] months);
 844     private static native String[] getShortMonths(String langTag, String[] smonths);
 845     private static native String[] getWeekdays(String langTag, String[] wdays);
 846     private static native String[] getShortWeekdays(String langTag, String[] swdays);
 847 
 848     // For NumberFormatProvider
 849     private static native String getNumberPattern(int numberStyle, String langTag);
 850     private static native boolean isNativeDigit(String langTag);
 851 
 852     // For DecimalFormatSymbolsProvider
 853     private static native String getCurrencySymbol(String langTag, String currencySymbol);
 854     private static native char getDecimalSeparator(String langTag, char decimalSeparator);
 855     private static native char getGroupingSeparator(String langTag, char groupingSeparator);
 856     private static native String getInfinity(String langTag, String infinity);
 857     private static native String getInternationalCurrencySymbol(String langTag, String internationalCurrencySymbol);
 858     private static native char getMinusSign(String langTag, char minusSign);
 859     private static native char getMonetaryDecimalSeparator(String langTag, char monetaryDecimalSeparator);
 860     private static native String getNaN(String langTag, String nan);
 861     private static native char getPercent(String langTag, char percent);
 862     private static native char getPerMill(String langTag, char perMill);
 863     private static native char getZeroDigit(String langTag, char zeroDigit);
 864 
 865     // For CalendarDataProvider
 866     private static native int getCalendarDataValue(String langTag, int type);
 867 
 868     // For CalendarNameProvider
 869     private static native String[] getCalendarDisplayStrings(String langTag, int calid, int field, int style);
 870 
 871     // For Locale/CurrencyNameProvider
 872     private static native String getDisplayString(String langTag, int key, String value);
 873 }