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 }