1 /* 2 * Copyright (c) 2012, 2019, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 package sun.util.locale.provider; 26 27 import java.lang.ref.SoftReference; 28 import java.text.DateFormat; 29 import java.text.DateFormatSymbols; 30 import java.text.DecimalFormat; 31 import java.text.DecimalFormatSymbols; 32 import java.text.NumberFormat; 33 import java.text.SimpleDateFormat; 34 import java.text.spi.DateFormatProvider; 35 import java.text.spi.DateFormatSymbolsProvider; 36 import java.text.spi.DecimalFormatSymbolsProvider; 37 import java.text.spi.NumberFormatProvider; 38 import java.util.Calendar; 39 import java.util.Collections; 40 import java.util.Currency; 41 import java.util.HashMap; 42 import java.util.HashSet; 43 import java.util.Locale; 44 import java.util.Map; 45 import java.util.ResourceBundle.Control; 46 import java.util.Set; 47 import java.util.TimeZone; 48 import java.util.concurrent.ConcurrentHashMap; 49 import java.util.concurrent.ConcurrentMap; 50 import java.util.concurrent.atomic.AtomicReferenceArray; 51 import java.util.spi.CalendarDataProvider; 52 import java.util.spi.CalendarNameProvider; 53 import java.util.spi.CurrencyNameProvider; 54 import java.util.spi.LocaleNameProvider; 55 import sun.text.spi.JavaTimeDateTimePatternProvider; 56 import sun.util.spi.CalendarProvider; 57 58 /** 59 * LocaleProviderdapter implementation for the Windows locale data. 60 * 61 * @author Naoto Sato 62 */ 63 public class HostLocaleProviderAdapterImpl { 64 65 // locale categories 66 private static final int CAT_DISPLAY = 0; 67 private static final int CAT_FORMAT = 1; 68 69 // NumberFormat styles 70 private static final int NF_NUMBER = 0; 71 private static final int NF_CURRENCY = 1; 72 private static final int NF_PERCENT = 2; 73 private static final int NF_INTEGER = 3; 74 private static final int NF_MAX = NF_INTEGER; 75 76 // CalendarData value types 77 private static final int CD_FIRSTDAYOFWEEK = 0; 78 private static final int CD_MINIMALDAYSINFIRSTWEEK = 1; 79 80 // Currency/Locale display name types 81 private static final int DN_CURRENCY_NAME = 0; 82 private static final int DN_CURRENCY_SYMBOL = 1; 83 private static final int DN_LOCALE_LANGUAGE = 2; 84 private static final int DN_LOCALE_SCRIPT = 3; 85 private static final int DN_LOCALE_REGION = 4; 86 private static final int DN_LOCALE_VARIANT = 5; 87 88 // Windows Calendar IDs 89 private static final int CAL_JAPAN = 3; 90 91 // Native Calendar ID to LDML calendar type map 92 private static final String[] calIDToLDML = { 93 "", 94 "gregory", 95 "gregory_en-US", 96 "japanese", 97 "roc", 98 "", // No appropriate type for CAL_KOREA 99 "islamic", 100 "buddhist", 101 "hebrew", 102 "gregory_fr", 103 "gregory_ar", 104 "gregory_en", 105 "gregory_fr", "", "", "", "", "", "", "", "", "", "", 106 "islamic-umalqura", 107 }; 108 109 // Caches 110 private static final ConcurrentMap<Locale, SoftReference<AtomicReferenceArray<String>>> dateFormatCache = new ConcurrentHashMap<>(); 111 private static final ConcurrentMap<Locale, SoftReference<DateFormatSymbols>> dateFormatSymbolsCache = new ConcurrentHashMap<>(); 112 private static final ConcurrentMap<Locale, SoftReference<AtomicReferenceArray<String>>> numberFormatCache = new ConcurrentHashMap<>(); 113 private static final ConcurrentMap<Locale, SoftReference<DecimalFormatSymbols>> decimalFormatSymbolsCache = new ConcurrentHashMap<>(); 114 115 private static final Set<Locale> supportedLocaleSet; 116 private static final String nativeDisplayLanguage; 117 static { 118 Set<Locale> tmpSet = new HashSet<>(); 119 if (initialize()) { 120 // Assuming the default locales do not include any extensions, so 121 // no stripping is needed here. 122 Control c = Control.getNoFallbackControl(Control.FORMAT_DEFAULT); 123 String displayLocale = getDefaultLocale(CAT_DISPLAY); 124 Locale l = Locale.forLanguageTag(displayLocale.replace('_', '-')); 125 tmpSet.addAll(c.getCandidateLocales("", l)); 126 nativeDisplayLanguage = l.getLanguage(); 127 128 String formatLocale = getDefaultLocale(CAT_FORMAT); 129 if (!formatLocale.equals(displayLocale)) { 130 l = Locale.forLanguageTag(formatLocale.replace('_', '-')); 131 tmpSet.addAll(c.getCandidateLocales("", l)); 132 } 133 } else { 134 nativeDisplayLanguage = ""; 135 } 136 supportedLocaleSet = Collections.unmodifiableSet(tmpSet); 137 } 138 private static final Locale[] supportedLocale = supportedLocaleSet.toArray(new Locale[0]); 139 140 public static DateFormatProvider getDateFormatProvider() { 141 return new DateFormatProvider() { 142 @Override 143 public Locale[] getAvailableLocales() { 144 return getSupportedCalendarLocales(); 145 } 146 147 @Override 148 public boolean isSupportedLocale(Locale locale) { 149 return isSupportedCalendarLocale(locale); 150 } 151 152 @Override 153 public DateFormat getDateInstance(int style, Locale locale) { 154 AtomicReferenceArray<String> patterns = getDateTimePatterns(locale); 155 return new SimpleDateFormat(patterns.get(style/2), 156 getCalendarLocale(locale)); 157 } 158 159 @Override 160 public DateFormat getTimeInstance(int style, Locale locale) { 161 AtomicReferenceArray<String> patterns = getDateTimePatterns(locale); 162 return new SimpleDateFormat(patterns.get(style/2+2), 163 getCalendarLocale(locale)); 164 } 165 166 @Override 167 public DateFormat getDateTimeInstance(int dateStyle, 168 int timeStyle, Locale locale) { 169 AtomicReferenceArray<String> patterns = getDateTimePatterns(locale); 170 String pattern = new StringBuilder(patterns.get(dateStyle/2)) 171 .append(" ") 172 .append(patterns.get(timeStyle/2+2)) 173 .toString(); 174 return new SimpleDateFormat(pattern, getCalendarLocale(locale)); 175 } 176 177 private AtomicReferenceArray<String> getDateTimePatterns(Locale locale) { 178 AtomicReferenceArray<String> patterns; 179 SoftReference<AtomicReferenceArray<String>> ref = dateFormatCache.get(locale); 180 181 if (ref == null || (patterns = ref.get()) == null) { 182 String langtag = removeExtensions(locale).toLanguageTag(); 183 patterns = new AtomicReferenceArray<>(4); 184 patterns.compareAndSet(0, null, convertDateTimePattern( 185 getDateTimePattern(DateFormat.LONG, -1, langtag))); 186 patterns.compareAndSet(1, null, convertDateTimePattern( 187 getDateTimePattern(DateFormat.SHORT, -1, langtag))); 188 patterns.compareAndSet(2, null, convertDateTimePattern( 189 getDateTimePattern(-1, DateFormat.LONG, langtag))); 190 patterns.compareAndSet(3, null, convertDateTimePattern( 191 getDateTimePattern(-1, DateFormat.SHORT, langtag))); 192 ref = new SoftReference<>(patterns); 193 dateFormatCache.put(locale, ref); 194 } 195 196 return patterns; 197 } 198 }; 199 } 200 201 public static DateFormatSymbolsProvider getDateFormatSymbolsProvider() { 202 return new DateFormatSymbolsProvider() { 203 204 @Override 205 public Locale[] getAvailableLocales() { 206 return getSupportedCalendarLocales(); 207 } 208 209 @Override 210 public boolean isSupportedLocale(Locale locale) { 211 return isSupportedCalendarLocale(locale); 212 } 213 214 @Override 215 public DateFormatSymbols getInstance(Locale locale) { 216 DateFormatSymbols dfs; 217 SoftReference<DateFormatSymbols> ref = 218 dateFormatSymbolsCache.get(locale); 219 220 if (ref == null || (dfs = ref.get()) == null) { 221 dfs = new DateFormatSymbols(locale); 222 String langTag = removeExtensions(locale).toLanguageTag(); 223 224 dfs.setAmPmStrings(getAmPmStrings(langTag, dfs.getAmPmStrings())); 225 dfs.setEras(getEras(langTag, dfs.getEras())); 226 dfs.setMonths(getMonths(langTag, dfs.getMonths())); 227 dfs.setShortMonths(getShortMonths(langTag, dfs.getShortMonths())); 228 dfs.setWeekdays(getWeekdays(langTag, dfs.getWeekdays())); 229 dfs.setShortWeekdays(getShortWeekdays(langTag, dfs.getShortWeekdays())); 230 ref = new SoftReference<>(dfs); 231 dateFormatSymbolsCache.put(locale, ref); 232 } 233 return (DateFormatSymbols)dfs.clone(); 234 } 235 }; 236 } 237 238 public static NumberFormatProvider getNumberFormatProvider() { 239 return new NumberFormatProvider() { 240 241 @Override 242 public Locale[] getAvailableLocales() { 243 return getSupportedNativeDigitLocales(); 244 } 245 246 @Override 247 public boolean isSupportedLocale(Locale locale) { 248 return isSupportedNativeDigitLocale(locale); 249 } 250 251 @Override 252 public NumberFormat getCurrencyInstance(Locale locale) { 253 AtomicReferenceArray<String> patterns = getNumberPatterns(locale); 254 return new DecimalFormat(patterns.get(NF_CURRENCY), 255 DecimalFormatSymbols.getInstance(locale)); 256 } 257 258 @Override 259 public NumberFormat getIntegerInstance(Locale locale) { 260 AtomicReferenceArray<String> patterns = getNumberPatterns(locale); 261 return new DecimalFormat(patterns.get(NF_INTEGER), 262 DecimalFormatSymbols.getInstance(locale)); 263 } 264 265 @Override 266 public NumberFormat getNumberInstance(Locale locale) { 267 AtomicReferenceArray<String> patterns = getNumberPatterns(locale); 268 return new DecimalFormat(patterns.get(NF_NUMBER), 269 DecimalFormatSymbols.getInstance(locale)); 270 } 271 272 @Override 273 public NumberFormat getPercentInstance(Locale locale) { 274 AtomicReferenceArray<String> patterns = getNumberPatterns(locale); 275 return new DecimalFormat(patterns.get(NF_PERCENT), 276 DecimalFormatSymbols.getInstance(locale)); 277 } 278 279 private AtomicReferenceArray<String> getNumberPatterns(Locale locale) { 280 AtomicReferenceArray<String> patterns; 281 SoftReference<AtomicReferenceArray<String>> ref = numberFormatCache.get(locale); 282 283 if (ref == null || (patterns = ref.get()) == null) { 284 String langtag = locale.toLanguageTag(); 285 patterns = new AtomicReferenceArray<>(NF_MAX+1); 286 for (int i = 0; i <= NF_MAX; i++) { 287 patterns.compareAndSet(i, null, getNumberPattern(i, langtag)); 288 } 289 ref = new SoftReference<>(patterns); 290 numberFormatCache.put(locale, ref); 291 } 292 return patterns; 293 } 294 }; 295 } 296 297 public static DecimalFormatSymbolsProvider getDecimalFormatSymbolsProvider() { 298 return new DecimalFormatSymbolsProvider() { 299 300 @Override 301 public Locale[] getAvailableLocales() { 302 return getSupportedNativeDigitLocales(); 303 } 304 305 @Override 306 public boolean isSupportedLocale(Locale locale) { 307 return isSupportedNativeDigitLocale(locale); 308 } 309 310 @Override 311 public DecimalFormatSymbols getInstance(Locale locale) { 312 DecimalFormatSymbols dfs; 313 SoftReference<DecimalFormatSymbols> ref = 314 decimalFormatSymbolsCache.get(locale); 315 316 if (ref == null || (dfs = ref.get()) == null) { 317 dfs = new DecimalFormatSymbols(getNumberLocale(locale)); 318 String langTag = removeExtensions(locale).toLanguageTag(); 319 320 // DecimalFormatSymbols.setInternationalCurrencySymbol() has 321 // a side effect of setting the currency symbol as well. So 322 // the calling order is relevant here. 323 dfs.setInternationalCurrencySymbol(getInternationalCurrencySymbol(langTag, dfs.getInternationalCurrencySymbol())); 324 dfs.setCurrencySymbol(getCurrencySymbol(langTag, dfs.getCurrencySymbol())); 325 dfs.setDecimalSeparator(getDecimalSeparator(langTag, dfs.getDecimalSeparator())); 326 dfs.setGroupingSeparator(getGroupingSeparator(langTag, dfs.getGroupingSeparator())); 327 dfs.setInfinity(getInfinity(langTag, dfs.getInfinity())); 328 dfs.setMinusSign(getMinusSign(langTag, dfs.getMinusSign())); 329 dfs.setMonetaryDecimalSeparator(getMonetaryDecimalSeparator(langTag, dfs.getMonetaryDecimalSeparator())); 330 dfs.setNaN(getNaN(langTag, dfs.getNaN())); 331 dfs.setPercent(getPercent(langTag, dfs.getPercent())); 332 dfs.setPerMill(getPerMill(langTag, dfs.getPerMill())); 333 dfs.setZeroDigit(getZeroDigit(langTag, dfs.getZeroDigit())); 334 ref = new SoftReference<>(dfs); 335 decimalFormatSymbolsCache.put(locale, ref); 336 } 337 return (DecimalFormatSymbols)dfs.clone(); 338 } 339 }; 340 } 341 342 public static CalendarDataProvider getCalendarDataProvider() { 343 return new CalendarDataProvider() { 344 @Override 345 public Locale[] getAvailableLocales() { 346 return getSupportedCalendarLocales(); 347 } 348 349 @Override 350 public boolean isSupportedLocale(Locale locale) { 351 return isSupportedCalendarLocale(locale); 352 } 353 354 @Override 355 public int getFirstDayOfWeek(Locale locale) { 356 int first = getCalendarDataValue( 357 removeExtensions(locale).toLanguageTag(), 358 CD_FIRSTDAYOFWEEK); 359 if (first != -1) { 360 return (first + 1) % 7 + 1; 361 } else { 362 return 0; 363 } 364 } 365 366 @Override 367 public int getMinimalDaysInFirstWeek(Locale locale) { 368 return 0; 369 } 370 }; 371 } 372 373 public static CalendarNameProvider getCalendarNameProvider() { 374 return new CalendarNameProvider() { 375 @Override 376 public Locale[] getAvailableLocales() { 377 return getSupportedCalendarLocales(); 378 } 379 380 @Override 381 public boolean isSupportedLocale(Locale locale) { 382 return isSupportedCalendarLocale(locale); 383 } 384 385 @Override 386 public String getDisplayName(String calendarType, int field, 387 int value, int style, Locale locale) { 388 String[] names = getCalendarDisplayStrings(removeExtensions(locale).toLanguageTag(), 389 getCalendarIDFromLDMLType(calendarType), field, style); 390 if (names != null && value >= 0 && value < names.length) { 391 return names[value]; 392 } else { 393 return null; 394 } 395 } 396 397 @Override 398 public Map<String, Integer> getDisplayNames(String calendarType, 399 int field, int style, Locale locale) { 400 Map<String, Integer> map = null; 401 String[] names = getCalendarDisplayStrings(removeExtensions(locale).toLanguageTag(), 402 getCalendarIDFromLDMLType(calendarType), field, style); 403 if (names != null) { 404 map = new HashMap<>(); 405 for (int value = 0; value < names.length; value++) { 406 if (names[value] != null) { 407 map.put(names[value], value); 408 } 409 } 410 map = map.isEmpty() ? null : map; 411 } 412 return map; 413 } 414 }; 415 } 416 417 public static CalendarProvider getCalendarProvider() { 418 return new CalendarProvider() { 419 @Override 420 public Locale[] getAvailableLocales() { 421 return getSupportedCalendarLocales(); 422 } 423 424 @Override 425 public boolean isSupportedLocale(Locale locale) { 426 return isSupportedCalendarLocale(locale); 427 } 428 429 @Override 430 public Calendar getInstance(TimeZone zone, Locale locale) { 431 return new Calendar.Builder() 432 .setLocale(getCalendarLocale(locale)) 433 .setTimeZone(zone) 434 .setInstant(System.currentTimeMillis()) 435 .build(); 436 } 437 }; 438 } 439 440 public static CurrencyNameProvider getCurrencyNameProvider() { 441 return new CurrencyNameProvider() { 442 @Override 443 public Locale[] getAvailableLocales() { 444 return supportedLocale; 445 } 446 447 @Override 448 public boolean isSupportedLocale(Locale locale) { 449 // Ignore the extensions for now 450 return supportedLocaleSet.contains(locale.stripExtensions()) && 451 locale.getLanguage().equals(nativeDisplayLanguage); 452 } 453 454 @Override 455 public String getSymbol(String currencyCode, Locale locale) { 456 // Retrieves the currency symbol by calling 457 // GetLocaleInfoEx(LOCALE_SCURRENCY). 458 // It only works with the "locale"'s currency in its native 459 // language. 460 try { 461 if (Currency.getInstance(locale).getCurrencyCode() 462 .equals(currencyCode)) { 463 return getDisplayString(locale.toLanguageTag(), 464 DN_CURRENCY_SYMBOL, currencyCode); 465 } 466 } catch (IllegalArgumentException iae) {} 467 return null; 468 } 469 470 @Override 471 public String getDisplayName(String currencyCode, Locale locale) { 472 // Retrieves the display name by calling 473 // GetLocaleInfoEx(LOCALE_SNATIVECURRNAME). 474 // It only works with the "locale"'s currency in its native 475 // language. 476 try { 477 if (Currency.getInstance(locale).getCurrencyCode() 478 .equals(currencyCode)) { 479 return getDisplayString(locale.toLanguageTag(), 480 DN_CURRENCY_NAME, currencyCode); 481 } 482 } catch (IllegalArgumentException iae) {} 483 return null; 484 } 485 }; 486 } 487 488 public static LocaleNameProvider getLocaleNameProvider() { 489 return new LocaleNameProvider() { 490 @Override 491 public Locale[] getAvailableLocales() { 492 return supportedLocale; 493 } 494 495 @Override 496 public boolean isSupportedLocale(Locale locale) { 497 return supportedLocaleSet.contains(locale.stripExtensions()) && 498 locale.getLanguage().equals(nativeDisplayLanguage); 499 } 500 501 @Override 502 public String getDisplayLanguage(String languageCode, Locale locale) { 503 // Retrieves the display language name by calling 504 // GetLocaleInfoEx(LOCALE_SLOCALIZEDLANGUAGENAME). 505 return getDisplayString(locale.toLanguageTag(), 506 DN_LOCALE_LANGUAGE, languageCode); 507 } 508 509 @Override 510 public String getDisplayCountry(String countryCode, Locale locale) { 511 // Retrieves the display country name by calling 512 // GetLocaleInfoEx(LOCALE_SLOCALIZEDCOUNTRYNAME). 513 String str = getDisplayString(locale.toLanguageTag(), 514 DN_LOCALE_REGION, 515 nativeDisplayLanguage+"-"+countryCode); 516 // Hack: Windows 10 returns translated "Unknown Region (XX)" 517 // for localized XX region name. Take that as not known. 518 if (str != null && str.contains("("+countryCode+")")) { 519 return null; 520 } 521 return str; 522 } 523 524 @Override 525 public String getDisplayScript(String scriptCode, Locale locale) { 526 return null; 527 } 528 529 @Override 530 public String getDisplayVariant(String variantCode, Locale locale) { 531 return null; 532 } 533 }; 534 } 535 536 public static JavaTimeDateTimePatternProvider getJavaTimeDateTimePatternProvider() { 537 return new JavaTimeDateTimePatternProvider() { 538 @Override 539 public Locale[] getAvailableLocales() { 540 return getSupportedCalendarLocales(); 541 } 542 543 @Override 544 public boolean isSupportedLocale(Locale locale) { 545 return isSupportedCalendarLocale(locale); 546 } 547 548 @Override 549 public String getJavaTimeDateTimePattern(int timeStyle, int dateStyle, String calType, Locale locale) { 550 AtomicReferenceArray<String> patterns = getDateTimePatterns(locale); 551 String pattern = new StringBuilder(patterns.get(dateStyle / 2)) 552 .append(" ") 553 .append(patterns.get(timeStyle / 2 + 2)) 554 .toString(); 555 return toJavaTimeDateTimePattern(calType, pattern); 556 557 } 558 559 private AtomicReferenceArray<String> getDateTimePatterns(Locale locale) { 560 AtomicReferenceArray<String> patterns; 561 SoftReference<AtomicReferenceArray<String>> ref = dateFormatCache.get(locale); 562 563 if (ref == null || (patterns = ref.get()) == null) { 564 String langtag = removeExtensions(locale).toLanguageTag(); 565 patterns = new AtomicReferenceArray<>(4); 566 patterns.compareAndSet(0, null, convertDateTimePattern( 567 getDateTimePattern(DateFormat.LONG, -1, langtag))); 568 patterns.compareAndSet(1, null, convertDateTimePattern( 569 getDateTimePattern(DateFormat.SHORT, -1, langtag))); 570 patterns.compareAndSet(2, null, convertDateTimePattern( 571 getDateTimePattern(-1, DateFormat.LONG, langtag))); 572 patterns.compareAndSet(3, null, convertDateTimePattern( 573 getDateTimePattern(-1, DateFormat.SHORT, langtag))); 574 ref = new SoftReference<>(patterns); 575 dateFormatCache.put(locale, ref); 576 } 577 return patterns; 578 } 579 /** 580 * This method will convert JRE Date/time Pattern String to JSR310 581 * type Date/Time Pattern 582 */ 583 private String toJavaTimeDateTimePattern(String calendarType, String jrePattern) { 584 int length = jrePattern.length(); 585 StringBuilder sb = new StringBuilder(length); 586 boolean inQuote = false; 587 int count = 0; 588 char lastLetter = 0; 589 for (int i = 0; i < length; i++) { 590 char c = jrePattern.charAt(i); 591 if (c == '\'') { 592 // '' is treated as a single quote regardless of being 593 // in a quoted section. 594 if ((i + 1) < length) { 595 char nextc = jrePattern.charAt(i + 1); 596 if (nextc == '\'') { 597 i++; 598 if (count != 0) { 599 convert(calendarType, lastLetter, count, sb); 600 lastLetter = 0; 601 count = 0; 602 } 603 sb.append("''"); 604 continue; 605 } 606 } 607 if (!inQuote) { 608 if (count != 0) { 609 convert(calendarType, lastLetter, count, sb); 610 lastLetter = 0; 611 count = 0; 612 } 613 inQuote = true; 614 } else { 615 inQuote = false; 616 } 617 sb.append(c); 618 continue; 619 } 620 if (inQuote) { 621 sb.append(c); 622 continue; 623 } 624 if (!(c >= 'a' && c <= 'z' || c >= 'A' && c <= 'Z')) { 625 if (count != 0) { 626 convert(calendarType, lastLetter, count, sb); 627 lastLetter = 0; 628 count = 0; 629 } 630 sb.append(c); 631 continue; 632 } 633 if (lastLetter == 0 || lastLetter == c) { 634 lastLetter = c; 635 count++; 636 continue; 637 } 638 convert(calendarType, lastLetter, count, sb); 639 lastLetter = c; 640 count = 1; 641 } 642 if (inQuote) { 643 // should not come here. 644 // returning null so that FALLBACK provider will kick in. 645 return null; 646 } 647 if (count != 0) { 648 convert(calendarType, lastLetter, count, sb); 649 } 650 return sb.toString(); 651 } 652 653 private void convert(String calendarType, char letter, int count, StringBuilder sb) { 654 switch (letter) { 655 case 'G': 656 if (calendarType.equals("japanese")) { 657 if (count >= 4) { 658 count = 1; 659 } else { 660 count = 5; 661 } 662 } else if (!calendarType.equals("iso8601")) { 663 // Adjust the number of 'G's 664 // Gregorian calendar is iso8601 for java.time 665 if (count >= 4) { 666 // JRE full -> JavaTime full 667 count = 4; 668 } else { 669 // JRE short -> JavaTime short 670 count = 1; 671 } 672 } 673 break; 674 case 'y': 675 if (calendarType.equals("japanese") && count >= 4) { 676 // JRE specific "gan-nen" support 677 count = 1; 678 } 679 break; 680 default: 681 // JSR 310 and CLDR define 5-letter patterns for narrow text. 682 if (count > 4) { 683 count = 4; 684 } 685 break; 686 } 687 appendN(letter, count, sb); 688 } 689 690 private void appendN(char c, int n, StringBuilder sb) { 691 for (int i = 0; i < n; i++) { 692 sb.append(c); 693 } 694 } 695 }; 696 } 697 698 private static String convertDateTimePattern(String winPattern) { 699 String ret = winPattern.replaceAll("dddd", "EEEE"); 700 ret = ret.replaceAll("ddd", "EEE"); 701 ret = ret.replaceAll("tt", "aa"); 702 ret = ret.replaceAll("g", "GG"); 703 return ret; 704 } 705 706 private static Locale[] getSupportedCalendarLocales() { 707 if (supportedLocale.length != 0 && 708 supportedLocaleSet.contains(Locale.JAPAN) && 709 isJapaneseCalendar()) { 710 Locale[] sup = new Locale[supportedLocale.length+1]; 711 sup[0] = JRELocaleConstants.JA_JP_JP; 712 System.arraycopy(supportedLocale, 0, sup, 1, supportedLocale.length); 713 return sup; 714 } 715 return supportedLocale; 716 } 717 718 private static boolean isSupportedCalendarLocale(Locale locale) { 719 Locale base = stripVariantAndExtensions(locale); 720 721 if (!supportedLocaleSet.contains(base)) { 722 return false; 723 } 724 725 int calid = getCalendarID(base.toLanguageTag()); 726 if (calid <= 0 || calid >= calIDToLDML.length) { 727 return false; 728 } 729 730 String requestedCalType = locale.getUnicodeLocaleType("ca"); 731 String nativeCalType = calIDToLDML[calid] 732 .replaceFirst("_.*", ""); // remove locale part. 733 734 if (requestedCalType == null) { 735 return Calendar.getAvailableCalendarTypes().contains(nativeCalType); 736 } else { 737 return requestedCalType.equals(nativeCalType); 738 } 739 } 740 741 private static Locale[] getSupportedNativeDigitLocales() { 742 if (supportedLocale.length != 0 && 743 supportedLocaleSet.contains(JRELocaleConstants.TH_TH) && 744 isNativeDigit("th-TH")) { 745 Locale[] sup = new Locale[supportedLocale.length+1]; 746 sup[0] = JRELocaleConstants.TH_TH_TH; 747 System.arraycopy(supportedLocale, 0, sup, 1, supportedLocale.length); 748 return sup; 749 } 750 return supportedLocale; 751 } 752 753 private static boolean isSupportedNativeDigitLocale(Locale locale) { 754 // special case for th_TH_TH 755 if (JRELocaleConstants.TH_TH_TH.equals(locale)) { 756 return isNativeDigit("th-TH"); 757 } 758 759 String numtype = null; 760 Locale base = locale; 761 if (locale.hasExtensions()) { 762 numtype = locale.getUnicodeLocaleType("nu"); 763 base = locale.stripExtensions(); 764 } 765 766 if (supportedLocaleSet.contains(base)) { 767 // Only supports Latin or Thai (in thai locales) digits. 768 if (numtype == null || numtype.equals("latn")) { 769 return true; 770 } else if (locale.getLanguage().equals("th")) { 771 return "thai".equals(numtype) && 772 isNativeDigit(locale.toLanguageTag()); 773 } 774 } 775 776 return false; 777 } 778 779 private static Locale removeExtensions(Locale src) { 780 return new Locale.Builder().setLocale(src).clearExtensions().build(); 781 } 782 783 private static boolean isJapaneseCalendar() { 784 return getCalendarID("ja-JP") == CAL_JAPAN; 785 } 786 787 private static Locale stripVariantAndExtensions(Locale locale) { 788 if (locale.hasExtensions() || locale.getVariant() != "") { 789 // strip off extensions and variant. 790 locale = new Locale.Builder() 791 .setLocale(locale) 792 .clearExtensions() 793 .build(); 794 } 795 796 return locale; 797 } 798 799 private static Locale getCalendarLocale(Locale locale) { 800 int calid = getCalendarID(stripVariantAndExtensions(locale).toLanguageTag()); 801 if (calid > 0 && calid < calIDToLDML.length) { 802 Locale.Builder lb = new Locale.Builder(); 803 String[] caltype = calIDToLDML[calid].split("_"); 804 if (caltype.length > 1) { 805 lb.setLocale(Locale.forLanguageTag(caltype[1])); 806 } else { 807 lb.setLocale(locale); 808 } 809 lb.setUnicodeLocaleKeyword("ca", caltype[0]); 810 return lb.build(); 811 } 812 813 return locale; 814 } 815 816 private static int getCalendarIDFromLDMLType(String ldmlType) { 817 for (int i = 0; i < calIDToLDML.length; i++) { 818 if (calIDToLDML[i].startsWith(ldmlType)) { 819 return i; 820 } 821 } 822 return -1; 823 } 824 825 private static Locale getNumberLocale(Locale src) { 826 if (JRELocaleConstants.TH_TH.equals(src)) { 827 if (isNativeDigit("th-TH")) { 828 Locale.Builder lb = new Locale.Builder().setLocale(src); 829 lb.setUnicodeLocaleKeyword("nu", "thai"); 830 return lb.build(); 831 } 832 } 833 834 return src; 835 } 836 837 // native methods 838 839 // initialize 840 private static native boolean initialize(); 841 private static native String getDefaultLocale(int cat); 842 843 // For DateFormatProvider 844 private static native String getDateTimePattern(int dateStyle, int timeStyle, String langTag); 845 private static native int getCalendarID(String langTag); 846 847 // For DateFormatSymbolsProvider 848 private static native String[] getAmPmStrings(String langTag, String[] ampm); 849 private static native String[] getEras(String langTag, String[] eras); 850 private static native String[] getMonths(String langTag, String[] months); 851 private static native String[] getShortMonths(String langTag, String[] smonths); 852 private static native String[] getWeekdays(String langTag, String[] wdays); 853 private static native String[] getShortWeekdays(String langTag, String[] swdays); 854 855 // For NumberFormatProvider 856 private static native String getNumberPattern(int numberStyle, String langTag); 857 private static native boolean isNativeDigit(String langTag); 858 859 // For DecimalFormatSymbolsProvider 860 private static native String getCurrencySymbol(String langTag, String currencySymbol); 861 private static native char getDecimalSeparator(String langTag, char decimalSeparator); 862 private static native char getGroupingSeparator(String langTag, char groupingSeparator); 863 private static native String getInfinity(String langTag, String infinity); 864 private static native String getInternationalCurrencySymbol(String langTag, String internationalCurrencySymbol); 865 private static native char getMinusSign(String langTag, char minusSign); 866 private static native char getMonetaryDecimalSeparator(String langTag, char monetaryDecimalSeparator); 867 private static native String getNaN(String langTag, String nan); 868 private static native char getPercent(String langTag, char percent); 869 private static native char getPerMill(String langTag, char perMill); 870 private static native char getZeroDigit(String langTag, char zeroDigit); 871 872 // For CalendarDataProvider 873 private static native int getCalendarDataValue(String langTag, int type); 874 875 // For CalendarNameProvider 876 private static native String[] getCalendarDisplayStrings(String langTag, int calid, int field, int style); 877 878 // For Locale/CurrencyNameProvider 879 private static native String getDisplayString(String langTag, int key, String value); 880 }