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