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