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 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 return getDisplayString(locale.toLanguageTag(), 514 DN_LOCALE_REGION, nativeDisplayLanguage+"-"+countryCode); 515 } 516 517 @Override 518 public String getDisplayScript(String scriptCode, Locale locale) { 519 return null; 520 } 521 522 @Override 523 public String getDisplayVariant(String variantCode, Locale locale) { 524 return null; 525 } 526 }; 527 } 528 529 public static JavaTimeDateTimePatternProvider getJavaTimeDateTimePatternProvider() { 530 return new JavaTimeDateTimePatternProvider() { 531 @Override 532 public Locale[] getAvailableLocales() { 533 return getSupportedCalendarLocales(); 534 } 535 536 @Override 537 public boolean isSupportedLocale(Locale locale) { 538 return isSupportedCalendarLocale(locale); 539 } 540 541 @Override 542 public String getJavaTimeDateTimePattern(int timeStyle, int dateStyle, String calType, Locale locale) { 543 AtomicReferenceArray<String> patterns = getDateTimePatterns(locale); 544 String pattern = new StringBuilder(patterns.get(dateStyle / 2)) 545 .append(" ") 546 .append(patterns.get(timeStyle / 2 + 2)) 547 .toString(); 548 return toJavaTimeDateTimePattern(calType, pattern); 549 550 } 551 552 private AtomicReferenceArray<String> getDateTimePatterns(Locale locale) { 553 AtomicReferenceArray<String> patterns; 554 SoftReference<AtomicReferenceArray<String>> ref = dateFormatCache.get(locale); 555 556 if (ref == null || (patterns = ref.get()) == null) { 557 String langtag = removeExtensions(locale).toLanguageTag(); 558 patterns = new AtomicReferenceArray<>(4); 559 patterns.compareAndSet(0, null, convertDateTimePattern( 560 getDateTimePattern(DateFormat.LONG, -1, langtag))); 561 patterns.compareAndSet(1, null, convertDateTimePattern( 562 getDateTimePattern(DateFormat.SHORT, -1, langtag))); 563 patterns.compareAndSet(2, null, convertDateTimePattern( 564 getDateTimePattern(-1, DateFormat.LONG, langtag))); 565 patterns.compareAndSet(3, null, convertDateTimePattern( 566 getDateTimePattern(-1, DateFormat.SHORT, langtag))); 567 ref = new SoftReference<>(patterns); 568 dateFormatCache.put(locale, ref); 569 } 570 return patterns; 571 } 572 /** 573 * This method will convert JRE Date/time Pattern String to JSR310 574 * type Date/Time Pattern 575 */ 576 private String toJavaTimeDateTimePattern(String calendarType, String jrePattern) { 577 int length = jrePattern.length(); 578 StringBuilder sb = new StringBuilder(length); 579 boolean inQuote = false; 580 int count = 0; 581 char lastLetter = 0; 582 for (int i = 0; i < length; i++) { 583 char c = jrePattern.charAt(i); 584 if (c == '\'') { 585 // '' is treated as a single quote regardless of being 586 // in a quoted section. 587 if ((i + 1) < length) { 588 char nextc = jrePattern.charAt(i + 1); 589 if (nextc == '\'') { 590 i++; 591 if (count != 0) { 592 convert(calendarType, lastLetter, count, sb); 593 lastLetter = 0; 594 count = 0; 595 } 596 sb.append("''"); 597 continue; 598 } 599 } 600 if (!inQuote) { 601 if (count != 0) { 602 convert(calendarType, lastLetter, count, sb); 603 lastLetter = 0; 604 count = 0; 605 } 606 inQuote = true; 607 } else { 608 inQuote = false; 609 } 610 sb.append(c); 611 continue; 612 } 613 if (inQuote) { 614 sb.append(c); 615 continue; 616 } 617 if (!(c >= 'a' && c <= 'z' || c >= 'A' && c <= 'Z')) { 618 if (count != 0) { 619 convert(calendarType, lastLetter, count, sb); 620 lastLetter = 0; 621 count = 0; 622 } 623 sb.append(c); 624 continue; 625 } 626 if (lastLetter == 0 || lastLetter == c) { 627 lastLetter = c; 628 count++; 629 continue; 630 } 631 convert(calendarType, lastLetter, count, sb); 632 lastLetter = c; 633 count = 1; 634 } 635 if (inQuote) { 636 // should not come here. 637 // returning null so that FALLBACK provider will kick in. 638 return null; 639 } 640 if (count != 0) { 641 convert(calendarType, lastLetter, count, sb); 642 } 643 return sb.toString(); 644 } 645 646 private void convert(String calendarType, char letter, int count, StringBuilder sb) { 647 switch (letter) { 648 case 'G': 649 if (calendarType.equals("japanese")) { 650 if (count >= 4) { 651 count = 1; 652 } else { 653 count = 5; 654 } 655 } else if (!calendarType.equals("iso8601")) { 656 // Adjust the number of 'G's 657 // Gregorian calendar is iso8601 for java.time 658 if (count >= 4) { 659 // JRE full -> JavaTime full 660 count = 4; 661 } else { 662 // JRE short -> JavaTime short 663 count = 1; 664 } 665 } 666 break; 667 case 'y': 668 if (calendarType.equals("japanese") && count >= 4) { 669 // JRE specific "gan-nen" support 670 count = 1; 671 } 672 break; 673 default: 674 // JSR 310 and CLDR define 5-letter patterns for narrow text. 675 if (count > 4) { 676 count = 4; 677 } 678 break; 679 } 680 appendN(letter, count, sb); 681 } 682 683 private void appendN(char c, int n, StringBuilder sb) { 684 for (int i = 0; i < n; i++) { 685 sb.append(c); 686 } 687 } 688 }; 689 } 690 691 private static String convertDateTimePattern(String winPattern) { 692 String ret = winPattern.replaceAll("dddd", "EEEE"); 693 ret = ret.replaceAll("ddd", "EEE"); 694 ret = ret.replaceAll("tt", "aa"); 695 ret = ret.replaceAll("g", "GG"); 696 return ret; 697 } 698 699 private static Locale[] getSupportedCalendarLocales() { 700 if (supportedLocale.length != 0 && 701 supportedLocaleSet.contains(Locale.JAPAN) && 702 isJapaneseCalendar()) { 703 Locale[] sup = new Locale[supportedLocale.length+1]; 704 sup[0] = JRELocaleConstants.JA_JP_JP; 705 System.arraycopy(supportedLocale, 0, sup, 1, supportedLocale.length); 706 return sup; 707 } 708 return supportedLocale; 709 } 710 711 private static boolean isSupportedCalendarLocale(Locale locale) { 712 Locale base = stripVariantAndExtensions(locale); 713 714 if (!supportedLocaleSet.contains(base)) { 715 return false; 716 } 717 718 int calid = getCalendarID(base.toLanguageTag()); 719 if (calid <= 0 || calid >= calIDToLDML.length) { 720 return false; 721 } 722 723 String requestedCalType = locale.getUnicodeLocaleType("ca"); 724 String nativeCalType = calIDToLDML[calid] 725 .replaceFirst("_.*", ""); // remove locale part. 726 727 if (requestedCalType == null) { 728 return Calendar.getAvailableCalendarTypes().contains(nativeCalType); 729 } else { 730 return requestedCalType.equals(nativeCalType); 731 } 732 } 733 734 private static Locale[] getSupportedNativeDigitLocales() { 735 if (supportedLocale.length != 0 && 736 supportedLocaleSet.contains(JRELocaleConstants.TH_TH) && 737 isNativeDigit("th-TH")) { 738 Locale[] sup = new Locale[supportedLocale.length+1]; 739 sup[0] = JRELocaleConstants.TH_TH_TH; 740 System.arraycopy(supportedLocale, 0, sup, 1, supportedLocale.length); 741 return sup; 742 } 743 return supportedLocale; 744 } 745 746 private static boolean isSupportedNativeDigitLocale(Locale locale) { 747 // special case for th_TH_TH 748 if (JRELocaleConstants.TH_TH_TH.equals(locale)) { 749 return isNativeDigit("th-TH"); 750 } 751 752 String numtype = null; 753 Locale base = locale; 754 if (locale.hasExtensions()) { 755 numtype = locale.getUnicodeLocaleType("nu"); 756 base = locale.stripExtensions(); 757 } 758 759 if (supportedLocaleSet.contains(base)) { 760 // Only supports Latin or Thai (in thai locales) digits. 761 if (numtype == null || numtype.equals("latn")) { 762 return true; 763 } else if (locale.getLanguage().equals("th")) { 764 return "thai".equals(numtype) && 765 isNativeDigit(locale.toLanguageTag()); 766 } 767 } 768 769 return false; 770 } 771 772 private static Locale removeExtensions(Locale src) { 773 return new Locale.Builder().setLocale(src).clearExtensions().build(); 774 } 775 776 private static boolean isJapaneseCalendar() { 777 return getCalendarID("ja-JP") == CAL_JAPAN; 778 } 779 780 private static Locale stripVariantAndExtensions(Locale locale) { 781 if (locale.hasExtensions() || locale.getVariant() != "") { 782 // strip off extensions and variant. 783 locale = new Locale.Builder() 784 .setLocale(locale) 785 .clearExtensions() 786 .build(); 787 } 788 789 return locale; 790 } 791 792 private static Locale getCalendarLocale(Locale locale) { 793 int calid = getCalendarID(stripVariantAndExtensions(locale).toLanguageTag()); 794 if (calid > 0 && calid < calIDToLDML.length) { 795 Locale.Builder lb = new Locale.Builder(); 796 String[] caltype = calIDToLDML[calid].split("_"); 797 if (caltype.length > 1) { 798 lb.setLocale(Locale.forLanguageTag(caltype[1])); 799 } else { 800 lb.setLocale(locale); 801 } 802 lb.setUnicodeLocaleKeyword("ca", caltype[0]); 803 return lb.build(); 804 } 805 806 return locale; 807 } 808 809 private static int getCalendarIDFromLDMLType(String ldmlType) { 810 for (int i = 0; i < calIDToLDML.length; i++) { 811 if (calIDToLDML[i].startsWith(ldmlType)) { 812 return i; 813 } 814 } 815 return -1; 816 } 817 818 private static Locale getNumberLocale(Locale src) { 819 if (JRELocaleConstants.TH_TH.equals(src)) { 820 if (isNativeDigit("th-TH")) { 821 Locale.Builder lb = new Locale.Builder().setLocale(src); 822 lb.setUnicodeLocaleKeyword("nu", "thai"); 823 return lb.build(); 824 } 825 } 826 827 return src; 828 } 829 830 // native methods 831 832 // initialize 833 private static native boolean initialize(); 834 private static native String getDefaultLocale(int cat); 835 836 // For DateFormatProvider 837 private static native String getDateTimePattern(int dateStyle, int timeStyle, String langTag); 838 private static native int getCalendarID(String langTag); 839 840 // For DateFormatSymbolsProvider 841 private static native String[] getAmPmStrings(String langTag, String[] ampm); 842 private static native String[] getEras(String langTag, String[] eras); 843 private static native String[] getMonths(String langTag, String[] months); 844 private static native String[] getShortMonths(String langTag, String[] smonths); 845 private static native String[] getWeekdays(String langTag, String[] wdays); 846 private static native String[] getShortWeekdays(String langTag, String[] swdays); 847 848 // For NumberFormatProvider 849 private static native String getNumberPattern(int numberStyle, String langTag); 850 private static native boolean isNativeDigit(String langTag); 851 852 // For DecimalFormatSymbolsProvider 853 private static native String getCurrencySymbol(String langTag, String currencySymbol); 854 private static native char getDecimalSeparator(String langTag, char decimalSeparator); 855 private static native char getGroupingSeparator(String langTag, char groupingSeparator); 856 private static native String getInfinity(String langTag, String infinity); 857 private static native String getInternationalCurrencySymbol(String langTag, String internationalCurrencySymbol); 858 private static native char getMinusSign(String langTag, char minusSign); 859 private static native char getMonetaryDecimalSeparator(String langTag, char monetaryDecimalSeparator); 860 private static native String getNaN(String langTag, String nan); 861 private static native char getPercent(String langTag, char percent); 862 private static native char getPerMill(String langTag, char perMill); 863 private static native char getZeroDigit(String langTag, char zeroDigit); 864 865 // For CalendarDataProvider 866 private static native int getCalendarDataValue(String langTag, int type); 867 868 // For CalendarNameProvider 869 private static native String[] getCalendarDisplayStrings(String langTag, int calid, int field, int style); 870 871 // For Locale/CurrencyNameProvider 872 private static native String getDisplayString(String langTag, int key, String value); 873 }