1 /*
   2  * Copyright (c) 2012, 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.HashSet;
  36 import java.util.Locale;
  37 import java.util.Map;
  38 import java.util.ResourceBundle.Control;
  39 import java.util.Set;
  40 import java.util.concurrent.ConcurrentHashMap;
  41 import java.util.concurrent.ConcurrentMap;
  42 import java.util.concurrent.atomic.AtomicReferenceArray;
  43 import java.util.spi.CalendarDataProvider;
  44 import java.util.spi.CalendarNameProvider;
  45 import java.util.spi.CurrencyNameProvider;
  46 import java.util.spi.LocaleNameProvider;
  47 import java.util.spi.TimeZoneNameProvider;
  48 
  49 /**
  50  * LocaleProviderAdapter implementation for the Mac OS X locale data
  51  *
  52  * @author Naoto Sato
  53  */
  54 public class HostLocaleProviderAdapterImpl {
  55 
  56     // per supported locale instances
  57     private static ConcurrentMap<Locale, SoftReference<AtomicReferenceArray<String>>> dateFormatPatternsMap =
  58         new ConcurrentHashMap<>(2);
  59     private static ConcurrentMap<Locale, SoftReference<AtomicReferenceArray<String>>> numberFormatPatternsMap =
  60         new ConcurrentHashMap<>(2);
  61     private static ConcurrentMap<Locale, SoftReference<DateFormatSymbols>> dateFormatSymbolsMap =
  62         new ConcurrentHashMap<>(2);
  63     private static ConcurrentMap<Locale, SoftReference<DecimalFormatSymbols>> decimalFormatSymbolsMap =
  64         new ConcurrentHashMap<>(2);
  65 
  66     // locale categories
  67     private static final int CAT_DISPLAY = 0;
  68     private static final int CAT_FORMAT  = 1;
  69 
  70     // NumberFormat styles
  71     private static final int NF_NUMBER   = 0;
  72     private static final int NF_CURRENCY = 1;
  73     private static final int NF_PERCENT  = 2;
  74     private static final int NF_INTEGER  = 3;
  75     private static final int NF_MAX = NF_INTEGER;
  76 
  77     // CalendarData value types
  78     private static final int CD_FIRSTDAYOFWEEK = 0;
  79     private static final int CD_MINIMALDAYSINFIRSTWEEK = 1;
  80 
  81     // Locale/Currency display name types
  82     private static final int DN_LOCALE_LANGUAGE = 0;
  83     private static final int DN_LOCALE_SCRIPT   = 1;
  84     private static final int DN_LOCALE_REGION   = 2;
  85     private static final int DN_LOCALE_VARIANT  = 3;
  86     private static final int DN_CURRENCY_CODE   = 4;
  87     private static final int DN_CURRENCY_SYMBOL = 5;
  88 
  89     // TimeZone display name types
  90     private static final int DN_TZ_SHORT_STANDARD = 0;
  91     private static final int DN_TZ_SHORT_DST      = 1;
  92     private static final int DN_TZ_LONG_STANDARD  = 2;
  93     private static final int DN_TZ_LONG_DST       = 3;
  94 
  95     private static final Set<Locale> supportedLocaleSet;
  96     static {
  97         Set<Locale> tmpSet = new HashSet<Locale>();
  98         // Assuming the default locales do not include any extensions, so
  99         // no stripping is needed here.
 100         Locale l = Locale.forLanguageTag(getDefaultLocale(CAT_FORMAT).replaceAll("_","-"));
 101         tmpSet.addAll(Control.getNoFallbackControl(Control.FORMAT_DEFAULT).getCandidateLocales("", l));
 102         l = Locale.forLanguageTag(getDefaultLocale(CAT_DISPLAY).replaceAll("_","-"));
 103         tmpSet.addAll(Control.getNoFallbackControl(Control.FORMAT_DEFAULT).getCandidateLocales("", l));
 104         supportedLocaleSet = Collections.unmodifiableSet(tmpSet);
 105     }
 106     private final static Locale[] supportedLocale = supportedLocaleSet.toArray(new Locale[0]);
 107 
 108     public static DateFormatProvider getDateFormatProvider() {
 109         return new DateFormatProvider() {
 110 
 111             @Override
 112             public Locale[] getAvailableLocales() {
 113                 return getSupportedCalendarLocales();
 114             }
 115 
 116             @Override
 117             public boolean isSupportedLocale(Locale locale) {
 118                 return isSupportedCalendarLocale(locale);
 119             }
 120 
 121             @Override
 122             public DateFormat getDateInstance(int style, Locale locale) {
 123                 return new SimpleDateFormat(getDateTimePattern(style, -1, locale),
 124                                             getCalendarLocale(locale));
 125             }
 126 
 127             @Override
 128             public DateFormat getTimeInstance(int style, Locale locale) {
 129                 return new SimpleDateFormat(getDateTimePattern(-1, style, locale),
 130                                             getCalendarLocale(locale));
 131             }
 132 
 133             @Override
 134             public DateFormat getDateTimeInstance(int dateStyle,
 135                     int timeStyle, Locale locale) {
 136                 return new SimpleDateFormat(getDateTimePattern(dateStyle, timeStyle, locale),
 137                                             getCalendarLocale(locale));
 138             }
 139 
 140             private String getDateTimePattern(int dateStyle, int timeStyle, Locale locale) {
 141                 AtomicReferenceArray<String> dateFormatPatterns;
 142                 SoftReference<AtomicReferenceArray<String>> ref = dateFormatPatternsMap.get(locale);
 143 
 144                 if (ref == null || (dateFormatPatterns = ref.get()) == null) {
 145                     dateFormatPatterns = new AtomicReferenceArray<>(5 * 5);
 146                     ref = new SoftReference<>(dateFormatPatterns);
 147                     dateFormatPatternsMap.put(locale, ref);
 148                 }
 149 
 150                 int index = (dateStyle + 1) * 5 + timeStyle + 1;
 151                 String pattern = dateFormatPatterns.get(index);
 152                 if (pattern == null) {
 153                     String langTag = locale.toLanguageTag();
 154                     pattern = translateDateFormatLetters(getCalendarID(langTag),
 155                             getDateTimePatternNative(dateStyle, timeStyle, langTag));
 156                     if (!dateFormatPatterns.compareAndSet(index, null, pattern)) {
 157                         pattern = dateFormatPatterns.get(index);
 158                     }
 159                 }
 160 
 161                 return pattern;
 162             }
 163         };
 164     }
 165 
 166     public static DateFormatSymbolsProvider getDateFormatSymbolsProvider() {
 167         return new DateFormatSymbolsProvider() {
 168             @Override
 169             public Locale[] getAvailableLocales() {
 170                 if (isSupportedLocale(Locale.getDefault(Locale.Category.FORMAT))) {
 171                     return supportedLocale;
 172                 }
 173 
 174                         return new Locale[0];
 175                     }
 176 
 177             @Override
 178             public boolean isSupportedLocale(Locale locale) {
 179                 // Only supports the locale with Gregorian calendar
 180                 Locale base = locale.stripExtensions();
 181                 if (supportedLocaleSet.contains(base)) {
 182                     return getCalendarID(locale.toLanguageTag()).equals("gregorian");
 183                 }
 184                 return false;
 185             }
 186 
 187             @Override
 188             public DateFormatSymbols getInstance(Locale locale) {
 189                 DateFormatSymbols dateFormatSymbols;
 190                 SoftReference<DateFormatSymbols> ref = dateFormatSymbolsMap.get(locale);
 191 
 192                 if (ref == null || (dateFormatSymbols = ref.get()) == null) {
 193                     dateFormatSymbols = new DateFormatSymbols(locale);
 194                     String langTag = locale.toLanguageTag();
 195                     dateFormatSymbols.setAmPmStrings(getAmPmStrings(langTag, dateFormatSymbols.getAmPmStrings()));
 196                     dateFormatSymbols.setEras(getEras(langTag, dateFormatSymbols.getEras()));
 197                     dateFormatSymbols.setMonths(getMonths(langTag, dateFormatSymbols.getMonths()));
 198                     dateFormatSymbols.setShortMonths(getShortMonths(langTag, dateFormatSymbols.getShortMonths()));
 199                     dateFormatSymbols.setWeekdays(getWeekdays(langTag, dateFormatSymbols.getWeekdays()));
 200                     dateFormatSymbols.setShortWeekdays(getShortWeekdays(langTag, dateFormatSymbols.getShortWeekdays()));
 201                     ref = new SoftReference<>(dateFormatSymbols);
 202                     dateFormatSymbolsMap.put(locale, ref);
 203                 }
 204                 return (DateFormatSymbols)dateFormatSymbols.clone();
 205             }
 206         };
 207     }
 208 
 209     public static NumberFormatProvider getNumberFormatProvider() {
 210         return new NumberFormatProvider() {
 211             @Override
 212             public Locale[] getAvailableLocales() {
 213                 return supportedLocale;
 214             }
 215 
 216             @Override
 217             public boolean isSupportedLocale(Locale locale) {
 218                 // Ignore the extensions for now
 219                 return supportedLocaleSet.contains(locale.stripExtensions());
 220             }
 221 
 222             @Override
 223             public NumberFormat getCurrencyInstance(Locale locale) {
 224                 return new DecimalFormat(getNumberPattern(NF_CURRENCY, locale),
 225                     DecimalFormatSymbols.getInstance(locale));
 226             }
 227 
 228             @Override
 229             public NumberFormat getIntegerInstance(Locale locale) {
 230                 return new DecimalFormat(getNumberPattern(NF_INTEGER, locale),
 231                     DecimalFormatSymbols.getInstance(locale));
 232             }
 233 
 234             @Override
 235             public NumberFormat getNumberInstance(Locale locale) {
 236                 return new DecimalFormat(getNumberPattern(NF_NUMBER, locale),
 237                     DecimalFormatSymbols.getInstance(locale));
 238             }
 239 
 240             @Override
 241             public NumberFormat getPercentInstance(Locale locale) {
 242                 return new DecimalFormat(getNumberPattern(NF_PERCENT, locale),
 243                     DecimalFormatSymbols.getInstance(locale));
 244             }
 245 
 246             private String getNumberPattern(int style, Locale locale) {
 247                 AtomicReferenceArray<String> numberFormatPatterns;
 248                 SoftReference<AtomicReferenceArray<String>> ref = numberFormatPatternsMap.get(locale);
 249 
 250                 if (ref == null || (numberFormatPatterns = ref.get()) == null) {
 251                     numberFormatPatterns = new AtomicReferenceArray<>(4);
 252                     ref = new SoftReference<>(numberFormatPatterns);
 253                     numberFormatPatternsMap.put(locale, ref);
 254                 }
 255 
 256                 String pattern = numberFormatPatterns.get(style);
 257                 if (pattern == null) {
 258                     pattern = getNumberPatternNative(style, locale.toLanguageTag());
 259                     if (!numberFormatPatterns.compareAndSet(style, null, pattern)) {
 260                         pattern = numberFormatPatterns.get(style);
 261                     }
 262                 }
 263 
 264                 return pattern;
 265             }
 266         };
 267     }
 268 
 269     public static DecimalFormatSymbolsProvider getDecimalFormatSymbolsProvider() {
 270         return new DecimalFormatSymbolsProvider() {
 271 
 272             @Override
 273             public Locale[] getAvailableLocales() {
 274                 return supportedLocale;
 275             }
 276 
 277             @Override
 278             public boolean isSupportedLocale(Locale locale) {
 279                 // Ignore the extensions for now
 280                 return supportedLocaleSet.contains(locale.stripExtensions());
 281             }
 282 
 283             @Override
 284             public DecimalFormatSymbols getInstance(Locale locale) {
 285                 DecimalFormatSymbols decimalFormatSymbols;
 286                 SoftReference<DecimalFormatSymbols> ref = decimalFormatSymbolsMap.get(locale);
 287 
 288                 if (ref == null || (decimalFormatSymbols = ref.get()) == null) {
 289                     decimalFormatSymbols = new DecimalFormatSymbols(locale);
 290                     String langTag = locale.toLanguageTag();
 291 
 292                     // DecimalFormatSymbols.setInternationalCurrencySymbol() has
 293                     // a side effect of setting the currency symbol as well. So
 294                     // the calling order is relevant here.
 295                     decimalFormatSymbols.setInternationalCurrencySymbol(getInternationalCurrencySymbol(langTag, decimalFormatSymbols.getInternationalCurrencySymbol()));
 296                     decimalFormatSymbols.setCurrencySymbol(getCurrencySymbol(langTag, decimalFormatSymbols.getCurrencySymbol()));
 297                     decimalFormatSymbols.setDecimalSeparator(getDecimalSeparator(langTag, decimalFormatSymbols.getDecimalSeparator()));
 298                     decimalFormatSymbols.setGroupingSeparator(getGroupingSeparator(langTag, decimalFormatSymbols.getGroupingSeparator()));
 299                     decimalFormatSymbols.setInfinity(getInfinity(langTag, decimalFormatSymbols.getInfinity()));
 300                     decimalFormatSymbols.setMinusSign(getMinusSign(langTag, decimalFormatSymbols.getMinusSign()));
 301                     decimalFormatSymbols.setMonetaryDecimalSeparator(getMonetaryDecimalSeparator(langTag, decimalFormatSymbols.getMonetaryDecimalSeparator()));
 302                     decimalFormatSymbols.setNaN(getNaN(langTag, decimalFormatSymbols.getNaN()));
 303                     decimalFormatSymbols.setPercent(getPercent(langTag, decimalFormatSymbols.getPercent()));
 304                     decimalFormatSymbols.setPerMill(getPerMill(langTag, decimalFormatSymbols.getPerMill()));
 305                     decimalFormatSymbols.setZeroDigit(getZeroDigit(langTag, decimalFormatSymbols.getZeroDigit()));
 306                     decimalFormatSymbols.setExponentSeparator(getExponentSeparator(langTag, decimalFormatSymbols.getExponentSeparator()));
 307                     ref = new SoftReference<>(decimalFormatSymbols);
 308                     decimalFormatSymbolsMap.put(locale, ref);
 309                 }
 310                 return (DecimalFormatSymbols)decimalFormatSymbols.clone();
 311             }
 312         };
 313     }
 314 
 315     public static CalendarDataProvider getCalendarDataProvider() {
 316         return new CalendarDataProvider() {
 317             @Override
 318             public Locale[] getAvailableLocales() {
 319                 return getSupportedCalendarLocales();
 320             }
 321 
 322             @Override
 323             public boolean isSupportedLocale(Locale locale) {
 324                 return isSupportedCalendarLocale(locale);
 325             }
 326 
 327             @Override
 328             public int getFirstDayOfWeek(Locale locale) {
 329                 return getCalendarInt(locale.toLanguageTag(), CD_FIRSTDAYOFWEEK);
 330             }
 331 
 332             @Override
 333             public int getMinimalDaysInFirstWeek(Locale locale) {
 334                 return getCalendarInt(locale.toLanguageTag(), CD_MINIMALDAYSINFIRSTWEEK);
 335             }
 336         };
 337     }
 338 
 339     public static CalendarNameProvider getCalendarNameProvider() {
 340         return new CalendarNameProvider() {
 341             @Override
 342             public Locale[] getAvailableLocales() {
 343                 return getSupportedCalendarLocales();
 344             }
 345 
 346             @Override
 347             public boolean isSupportedLocale(Locale locale) {
 348                 return isSupportedCalendarLocale(locale);
 349             }
 350 
 351             @Override
 352             public String getDisplayName(String calType, int field, int value,
 353                                          int style, Locale locale) {
 354                 return null;
 355             }
 356 
 357             @Override
 358             public Map<String, Integer> getDisplayNames(String calType,
 359                                          int field, int style, Locale locale) {
 360                 return null;
 361             }
 362         };
 363     }
 364 
 365     public static CurrencyNameProvider getCurrencyNameProvider() {
 366         return new CurrencyNameProvider() {
 367             @Override
 368             public Locale[] getAvailableLocales() {
 369                 return supportedLocale;
 370             }
 371 
 372             @Override
 373             public boolean isSupportedLocale(Locale locale) {
 374                 // Ignore the extensions for now
 375                 return supportedLocaleSet.contains(locale.stripExtensions());
 376             }
 377 
 378             @Override
 379             public String getDisplayName(String code, Locale locale) {
 380                 return getDisplayString(locale.toLanguageTag(), DN_CURRENCY_CODE, code);
 381             }
 382 
 383             @Override
 384             public String getSymbol(String code, Locale locale) {
 385                 return getDisplayString(locale.toLanguageTag(), DN_CURRENCY_SYMBOL, code);
 386             }
 387         };
 388     }
 389 
 390     public static LocaleNameProvider getLocaleNameProvider() {
 391         return new LocaleNameProvider() {
 392             @Override
 393             public Locale[] getAvailableLocales() {
 394                 return supportedLocale;
 395             }
 396 
 397             @Override
 398             public boolean isSupportedLocale(Locale locale) {
 399                 // Ignore the extensions for now
 400                 return supportedLocaleSet.contains(locale.stripExtensions());
 401             }
 402 
 403             @Override
 404             public String getDisplayLanguage(String languageCode, Locale locale) {
 405                 return getDisplayString(locale.toLanguageTag(), DN_LOCALE_LANGUAGE, languageCode);
 406             }
 407 
 408             @Override
 409             public String getDisplayCountry(String countryCode, Locale locale) {
 410                 return getDisplayString(locale.toLanguageTag(), DN_LOCALE_REGION, countryCode);
 411             }
 412 
 413             @Override
 414             public String getDisplayScript(String scriptCode, Locale locale) {
 415                 return getDisplayString(locale.toLanguageTag(), DN_LOCALE_SCRIPT, scriptCode);
 416             }
 417 
 418             @Override
 419             public String getDisplayVariant(String variantCode, Locale locale) {
 420                 return getDisplayString(locale.toLanguageTag(), DN_LOCALE_VARIANT, variantCode);
 421             }
 422         };
 423     }
 424 
 425     public static TimeZoneNameProvider getTimeZoneNameProvider() {
 426         return new TimeZoneNameProvider() {
 427             @Override
 428             public Locale[] getAvailableLocales() {
 429                 return supportedLocale;
 430             }
 431 
 432             @Override
 433             public boolean isSupportedLocale(Locale locale) {
 434                 // Ignore the extensions for now
 435                 return supportedLocaleSet.contains(locale.stripExtensions());
 436             }
 437 
 438             @Override
 439             public String getDisplayName(String ID, boolean daylight, int style, Locale locale) {
 440                 return getTimeZoneDisplayString(locale.toLanguageTag(), style * 2 + (daylight ? 1 : 0), ID);
 441             }
 442         };
 443     }
 444 
 445     private static Locale[] getSupportedCalendarLocales() {
 446         if (supportedLocale.length != 0 &&
 447             supportedLocaleSet.contains(Locale.JAPAN) &&
 448             isJapaneseCalendar()) {
 449             Locale[] sup = new Locale[supportedLocale.length+1];
 450             sup[0] = JRELocaleConstants.JA_JP_JP;
 451             System.arraycopy(supportedLocale, 0, sup, 1, supportedLocale.length);
 452             return sup;
 453         }
 454         return supportedLocale;
 455     }
 456 
 457     private static boolean isSupportedCalendarLocale(Locale locale) {
 458         // special case for ja_JP_JP
 459         if (JRELocaleConstants.JA_JP_JP.equals(locale)) {
 460             return isJapaneseCalendar();
 461         }
 462 
 463         Locale base = locale.stripExtensions();
 464         if (!supportedLocaleSet.contains(base)) {
 465             return false;
 466         }
 467 
 468         String caltype = locale.getUnicodeLocaleType("ca");
 469         if (caltype == null) {
 470             return true;
 471         }
 472 
 473         return caltype.replaceFirst("gregory", "gregorian").equals(
 474                 getCalendarID(locale.toLanguageTag()));
 475     }
 476 
 477     private static boolean isJapaneseCalendar() {
 478         return getCalendarID("ja-JP").equals("japanese");
 479     }
 480 
 481     private static Locale getCalendarLocale(Locale locale) {
 482         Locale.Builder lb = new Locale.Builder().setLocale(locale);
 483         String calid = getCalendarID(locale.toLanguageTag());
 484         switch (calid) {
 485             case "gregorian":
 486                 calid = "gregory";
 487                 // FALL THROUGH!
 488             case "japanese":
 489             case "buddhist":
 490                 lb.setUnicodeLocaleKeyword("ca", calid);
 491                 return lb.build();
 492             default:
 493                 return locale;
 494         }
 495     }
 496 
 497     // The following methods are copied from CLDRConverter build tool.
 498     private static String translateDateFormatLetters(String calendarType, String cldrFormat) {
 499         String pattern = cldrFormat;
 500         int length = pattern.length();
 501         boolean inQuote = false;
 502         StringBuilder jrePattern = new StringBuilder(length);
 503         int count = 0;
 504         char lastLetter = 0;
 505 
 506         for (int i = 0; i < length; i++) {
 507             char c = pattern.charAt(i);
 508 
 509             if (c == '\'') {
 510                 // '' is treated as a single quote regardless of being
 511                 // in a quoted section.
 512                 if ((i + 1) < length) {
 513                     char nextc = pattern.charAt(i + 1);
 514                     if (nextc == '\'') {
 515                         i++;
 516                         if (count != 0) {
 517                             convert(calendarType, lastLetter, count, jrePattern);
 518                             lastLetter = 0;
 519                             count = 0;
 520                         }
 521                         jrePattern.append("''");
 522                         continue;
 523                     }
 524                 }
 525                 if (!inQuote) {
 526                     if (count != 0) {
 527                         convert(calendarType, lastLetter, count, jrePattern);
 528                         lastLetter = 0;
 529                         count = 0;
 530                     }
 531                     inQuote = true;
 532                 } else {
 533                     inQuote = false;
 534                 }
 535                 jrePattern.append(c);
 536                 continue;
 537             }
 538             if (inQuote) {
 539                 jrePattern.append(c);
 540                 continue;
 541             }
 542             if (!(c >= 'a' && c <= 'z' || c >= 'A' && c <= 'Z')) {
 543                 if (count != 0) {
 544                     convert(calendarType, lastLetter, count, jrePattern);
 545                     lastLetter = 0;
 546                     count = 0;
 547                 }
 548                 jrePattern.append(c);
 549                 continue;
 550             }
 551 
 552             if (lastLetter == 0 || lastLetter == c) {
 553                 lastLetter = c;
 554                 count++;
 555                 continue;
 556             }
 557             convert(calendarType, lastLetter, count, jrePattern);
 558             lastLetter = c;
 559             count = 1;
 560         }
 561 
 562         if (count != 0) {
 563             convert(calendarType, lastLetter, count, jrePattern);
 564         }
 565         if (cldrFormat.contentEquals(jrePattern)) {
 566             return cldrFormat;
 567         }
 568         return jrePattern.toString();
 569     }
 570 
 571     private static void convert(String calendarType, char cldrLetter, int count, StringBuilder sb) {
 572         switch (cldrLetter) {
 573         case 'G':
 574             if (!calendarType.equals("gregorian")) {
 575                 // Adjust the number of 'G's for JRE SimpleDateFormat
 576                 if (count == 5) {
 577                     // CLDR narrow -> JRE short
 578                     count = 1;
 579                 } else if (count == 1) {
 580                     // CLDR abbr -> JRE long
 581                     count = 4;
 582                 }
 583             }
 584             appendN(cldrLetter, count, sb);
 585             break;
 586 
 587         // TODO: support 'c' and 'e' in JRE SimpleDateFormat
 588         // Use 'u' and 'E' for now.
 589         case 'c':
 590         case 'e':
 591             switch (count) {
 592             case 1:
 593                 sb.append('u');
 594                 break;
 595             case 3:
 596             case 4:
 597                 appendN('E', count, sb);
 598                 break;
 599             case 5:
 600                 appendN('E', 3, sb);
 601                 break;
 602             }
 603             break;
 604 
 605         case 'v':
 606         case 'V':
 607             appendN('z', count, sb);
 608             break;
 609 
 610         case 'Z':
 611             if (count == 4 || count == 5) {
 612                 sb.append("XXX");
 613             }
 614             break;
 615 
 616         case 'u':
 617         case 'U':
 618         case 'q':
 619         case 'Q':
 620         case 'l':
 621         case 'g':
 622         case 'j':
 623         case 'A':
 624             // Unsupported letter. Just append it within quotes
 625             sb.append('\'');
 626             sb.append(cldrLetter);
 627             sb.append('\'');
 628             break;
 629 
 630         default:
 631             appendN(cldrLetter, count, sb);
 632             break;
 633         }
 634     }
 635 
 636     private static void appendN(char c, int n, StringBuilder sb) {
 637         for (int i = 0; i < n; i++) {
 638             sb.append(c);
 639         }
 640     }
 641 
 642     // initialize
 643     private static native String getDefaultLocale(int cat);
 644 
 645     // For DateFormatProvider
 646     private static native String getDateTimePatternNative(int dateStyle, int timeStyle, String langtag);
 647     private static native String getCalendarID(String langTag);
 648 
 649     // For NumberFormatProvider
 650     private static native String getNumberPatternNative(int style, String langtag);
 651 
 652     // For DateFormatSymbolsProvider
 653     private static native String[] getAmPmStrings(String langTag, String[] ampm);
 654     private static native String[] getEras(String langTag, String[] eras);
 655     private static native String[] getMonths(String langTag, String[] months);
 656     private static native String[] getShortMonths(String langTag, String[] smonths);
 657     private static native String[] getWeekdays(String langTag, String[] wdays);
 658     private static native String[] getShortWeekdays(String langTag, String[] swdays);
 659 
 660     // For DecimalFormatSymbolsProvider
 661     private static native String getCurrencySymbol(String langTag, String currencySymbol);
 662     private static native char getDecimalSeparator(String langTag, char decimalSeparator);
 663     private static native char getGroupingSeparator(String langTag, char groupingSeparator);
 664     private static native String getInfinity(String langTag, String infinity);
 665     private static native String getInternationalCurrencySymbol(String langTag, String internationalCurrencySymbol);
 666     private static native char getMinusSign(String langTag, char minusSign);
 667     private static native char getMonetaryDecimalSeparator(String langTag, char monetaryDecimalSeparator);
 668     private static native String getNaN(String langTag, String nan);
 669     private static native char getPercent(String langTag, char percent);
 670     private static native char getPerMill(String langTag, char perMill);
 671     private static native char getZeroDigit(String langTag, char zeroDigit);
 672     private static native String getExponentSeparator(String langTag, String exponent);
 673 
 674     // For CalendarDataProvider
 675     private static native int getCalendarInt(String langTag, int type);
 676 
 677     // For Locale/CurrencyNameProvider
 678     private static native String getDisplayString(String langTag, int key, String value);
 679 
 680     // For TimeZoneNameProvider
 681     private static native String getTimeZoneDisplayString(String langTag, int style, String value);
 682 }