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 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.HashSet; 41 import java.util.Locale; 42 import java.util.Map; 43 import java.util.ResourceBundle.Control; 44 import java.util.Set; 45 import java.util.TimeZone; 46 import java.util.concurrent.ConcurrentHashMap; 47 import java.util.concurrent.ConcurrentMap; 48 import java.util.concurrent.atomic.AtomicReferenceArray; 49 import java.util.spi.CalendarDataProvider; 50 import java.util.spi.CalendarNameProvider; 51 import sun.util.spi.CalendarProvider; 52 53 /** 54 * LocaleProviderdapter implementation for the Windows locale data. 55 * 56 * @author Naoto Sato 57 */ 58 public class HostLocaleProviderAdapterImpl { 59 60 // locale categories 61 private static final int CAT_DISPLAY = 0; 62 private static final int CAT_FORMAT = 1; 63 64 // NumberFormat styles 65 private static final int NF_NUMBER = 0; 66 private static final int NF_CURRENCY = 1; 67 private static final int NF_PERCENT = 2; 68 private static final int NF_INTEGER = 3; 69 private static final int NF_MAX = NF_INTEGER; 70 71 // CalendarData value types 72 private static final int CD_FIRSTDAYOFWEEK = 0; 73 private static final int CD_MINIMALDAYSINFIRSTWEEK = 1; 74 75 // Native Calendar ID to LDML calendar type map 76 private static final String[] calIDToLDML = { 77 "", 78 "gregory", 79 "gregory_en-US", 80 "japanese", 81 "roc", 82 "", // No appropriate type for CAL_KOREA 83 "islamic", 84 "buddhist", 85 "hebrew", 86 "gregory_fr", 87 "gregory_ar", 88 "gregory_en", 89 "gregory_fr", 90 }; 91 92 // Caches 93 private static ConcurrentMap<Locale, SoftReference<AtomicReferenceArray<String>>> dateFormatCache = new ConcurrentHashMap<>(); 94 private static ConcurrentMap<Locale, SoftReference<DateFormatSymbols>> dateFormatSymbolsCache = new ConcurrentHashMap<>(); 95 private static ConcurrentMap<Locale, SoftReference<AtomicReferenceArray<String>>> numberFormatCache = new ConcurrentHashMap<>(); 96 private static ConcurrentMap<Locale, SoftReference<DecimalFormatSymbols>> decimalFormatSymbolsCache = new ConcurrentHashMap<>(); 97 98 private static final Set<Locale> supportedLocaleSet; 99 static { 100 Set<Locale> tmpSet = new HashSet<>(); 101 if (initialize()) { 102 // Assuming the default locales do not include any extensions, so 103 // no stripping is needed here. 104 Locale l = Locale.forLanguageTag(getDefaultLocale(CAT_FORMAT).replace('_', '-')); 105 tmpSet.addAll(Control.getNoFallbackControl(Control.FORMAT_DEFAULT).getCandidateLocales("", l)); 106 l = Locale.forLanguageTag(getDefaultLocale(CAT_DISPLAY).replace('_', '-')); 107 tmpSet.addAll(Control.getNoFallbackControl(Control.FORMAT_DEFAULT).getCandidateLocales("", l)); 108 } 109 supportedLocaleSet = Collections.unmodifiableSet(tmpSet); 110 } 111 private final static Locale[] supportedLocale = supportedLocaleSet.toArray(new Locale[0]); 112 113 public static DateFormatProvider getDateFormatProvider() { 114 return new DateFormatProvider() { 115 @Override 116 public Locale[] getAvailableLocales() { 117 return getSupportedCalendarLocales(); 118 } 119 120 @Override 121 public boolean isSupportedLocale(Locale locale) { 122 return isSupportedCalendarLocale(locale); 123 } 124 125 @Override 126 public DateFormat getDateInstance(int style, Locale locale) { 127 AtomicReferenceArray<String> patterns = getDateTimePatterns(locale); 128 return new SimpleDateFormat(patterns.get(style/2), 129 getCalendarLocale(locale)); 130 } 131 132 @Override 133 public DateFormat getTimeInstance(int style, Locale locale) { 134 AtomicReferenceArray<String> patterns = getDateTimePatterns(locale); 135 return new SimpleDateFormat(patterns.get(style/2+2), 136 getCalendarLocale(locale)); 137 } 138 139 @Override 140 public DateFormat getDateTimeInstance(int dateStyle, 141 int timeStyle, Locale locale) { 142 AtomicReferenceArray<String> patterns = getDateTimePatterns(locale); 143 String pattern = new StringBuilder(patterns.get(dateStyle/2)) 144 .append(" ") 145 .append(patterns.get(timeStyle/2+2)) 146 .toString(); 147 return new SimpleDateFormat(pattern, getCalendarLocale(locale)); 148 } 149 150 private AtomicReferenceArray<String> getDateTimePatterns(Locale locale) { 151 AtomicReferenceArray<String> patterns; 152 SoftReference<AtomicReferenceArray<String>> ref = dateFormatCache.get(locale); 153 154 if (ref == null || (patterns = ref.get()) == null) { 155 String langtag = removeExtensions(locale).toLanguageTag(); 156 patterns = new AtomicReferenceArray<>(4); 157 patterns.compareAndSet(0, null, convertDateTimePattern( 158 getDateTimePattern(DateFormat.LONG, -1, langtag))); 159 patterns.compareAndSet(1, null, convertDateTimePattern( 160 getDateTimePattern(DateFormat.SHORT, -1, langtag))); 161 patterns.compareAndSet(2, null, convertDateTimePattern( 162 getDateTimePattern(-1, DateFormat.LONG, langtag))); 163 patterns.compareAndSet(3, null, convertDateTimePattern( 164 getDateTimePattern(-1, DateFormat.SHORT, langtag))); 165 ref = new SoftReference<>(patterns); 166 dateFormatCache.put(locale, ref); 167 } 168 169 return patterns; 170 } 171 }; 172 } 173 174 public static DateFormatSymbolsProvider getDateFormatSymbolsProvider() { 175 return new DateFormatSymbolsProvider() { 176 177 @Override 178 public Locale[] getAvailableLocales() { 179 return getSupportedCalendarLocales(); 180 } 181 182 @Override 183 public boolean isSupportedLocale(Locale locale) { 184 return isSupportedCalendarLocale(locale); 185 } 186 187 @Override 188 public DateFormatSymbols getInstance(Locale locale) { 189 DateFormatSymbols dfs; 190 SoftReference<DateFormatSymbols> ref = 191 dateFormatSymbolsCache.get(locale); 192 193 if (ref == null || (dfs = ref.get()) == null) { 194 dfs = new DateFormatSymbols(locale); 195 String langTag = removeExtensions(locale).toLanguageTag(); 196 197 dfs.setAmPmStrings(getAmPmStrings(langTag, dfs.getAmPmStrings())); 198 dfs.setEras(getEras(langTag, dfs.getEras())); 199 dfs.setMonths(getMonths(langTag, dfs.getMonths())); 200 dfs.setShortMonths(getShortMonths(langTag, dfs.getShortMonths())); 201 dfs.setWeekdays(getWeekdays(langTag, dfs.getWeekdays())); 202 dfs.setShortWeekdays(getShortWeekdays(langTag, dfs.getShortWeekdays())); 203 ref = new SoftReference<>(dfs); 204 dateFormatSymbolsCache.put(locale, ref); 205 } 206 return (DateFormatSymbols)dfs.clone(); 207 } 208 }; 209 } 210 211 public static NumberFormatProvider getNumberFormatProvider() { 212 return new NumberFormatProvider() { 213 214 @Override 215 public Locale[] getAvailableLocales() { 216 return getSupportedNativeDigitLocales(); 217 } 218 219 @Override 220 public boolean isSupportedLocale(Locale locale) { 221 return isSupportedNativeDigitLocale(locale); 222 } 223 224 @Override 225 public NumberFormat getCurrencyInstance(Locale locale) { 226 AtomicReferenceArray<String> patterns = getNumberPatterns(locale); 227 return new DecimalFormat(patterns.get(NF_CURRENCY), 228 DecimalFormatSymbols.getInstance(locale)); 229 } 230 231 @Override 232 public NumberFormat getIntegerInstance(Locale locale) { 233 AtomicReferenceArray<String> patterns = getNumberPatterns(locale); 234 return new DecimalFormat(patterns.get(NF_INTEGER), 235 DecimalFormatSymbols.getInstance(locale)); 236 } 237 238 @Override 239 public NumberFormat getNumberInstance(Locale locale) { 240 AtomicReferenceArray<String> patterns = getNumberPatterns(locale); 241 return new DecimalFormat(patterns.get(NF_NUMBER), 242 DecimalFormatSymbols.getInstance(locale)); 243 } 244 245 @Override 246 public NumberFormat getPercentInstance(Locale locale) { 247 AtomicReferenceArray<String> patterns = getNumberPatterns(locale); 248 return new DecimalFormat(patterns.get(NF_PERCENT), 249 DecimalFormatSymbols.getInstance(locale)); 250 } 251 252 private AtomicReferenceArray<String> getNumberPatterns(Locale locale) { 253 AtomicReferenceArray<String> patterns; 254 SoftReference<AtomicReferenceArray<String>> ref = numberFormatCache.get(locale); 255 256 if (ref == null || (patterns = ref.get()) == null) { 257 String langtag = locale.toLanguageTag(); 258 patterns = new AtomicReferenceArray<>(NF_MAX+1); 259 for (int i = 0; i <= NF_MAX; i++) { 260 patterns.compareAndSet(i, null, getNumberPattern(i, langtag)); 261 } 262 ref = new SoftReference<>(patterns); 263 numberFormatCache.put(locale, ref); 264 } 265 return patterns; 266 } 267 }; 268 } 269 270 public static DecimalFormatSymbolsProvider getDecimalFormatSymbolsProvider() { 271 return new DecimalFormatSymbolsProvider() { 272 273 @Override 274 public Locale[] getAvailableLocales() { 275 return getSupportedNativeDigitLocales(); 276 } 277 278 @Override 279 public boolean isSupportedLocale(Locale locale) { 280 return isSupportedNativeDigitLocale(locale); 281 } 282 283 @Override 284 public DecimalFormatSymbols getInstance(Locale locale) { 285 DecimalFormatSymbols dfs; 286 SoftReference<DecimalFormatSymbols> ref = 287 decimalFormatSymbolsCache.get(locale); 288 289 if (ref == null || (dfs = ref.get()) == null) { 290 dfs = new DecimalFormatSymbols(getNumberLocale(locale)); 291 String langTag = removeExtensions(locale).toLanguageTag(); 292 293 // DecimalFormatSymbols.setInternationalCurrencySymbol() has 294 // a side effect of setting the currency symbol as well. So 295 // the calling order is relevant here. 296 dfs.setInternationalCurrencySymbol(getInternationalCurrencySymbol(langTag, dfs.getInternationalCurrencySymbol())); 297 dfs.setCurrencySymbol(getCurrencySymbol(langTag, dfs.getCurrencySymbol())); 298 dfs.setDecimalSeparator(getDecimalSeparator(langTag, dfs.getDecimalSeparator())); 299 dfs.setGroupingSeparator(getGroupingSeparator(langTag, dfs.getGroupingSeparator())); 300 dfs.setInfinity(getInfinity(langTag, dfs.getInfinity())); 301 dfs.setMinusSign(getMinusSign(langTag, dfs.getMinusSign())); 302 dfs.setMonetaryDecimalSeparator(getMonetaryDecimalSeparator(langTag, dfs.getMonetaryDecimalSeparator())); 303 dfs.setNaN(getNaN(langTag, dfs.getNaN())); 304 dfs.setPercent(getPercent(langTag, dfs.getPercent())); 305 dfs.setPerMill(getPerMill(langTag, dfs.getPerMill())); 306 dfs.setZeroDigit(getZeroDigit(langTag, dfs.getZeroDigit())); 307 ref = new SoftReference<>(dfs); 308 decimalFormatSymbolsCache.put(locale, ref); 309 } 310 return (DecimalFormatSymbols)dfs.clone(); 311 } 312 }; 313 } 314 315 public static CalendarDataProvider getCalendarDataProvider() { 316 return new CalendarDataProvider() { 317 @Override 318 public Locale[] getAvailableLocales() { 319 return getSupportedCalendarLocales(); 320 } 321 322 @Override 323 public boolean isSupportedLocale(Locale locale) { 324 return isSupportedCalendarLocale(locale); 325 } 326 327 @Override 328 public int getFirstDayOfWeek(Locale locale) { 329 int first = getCalendarDataValue( 330 removeExtensions(locale).toLanguageTag(), 331 CD_FIRSTDAYOFWEEK); 332 if (first != -1) { 333 return (first + 1) % 7 + 1; 334 } else { 335 return 0; 336 } 337 } 338 339 @Override 340 public int getMinimalDaysInFirstWeek(Locale locale) { 341 return 0; 342 } 343 }; 344 } 345 346 public static CalendarNameProvider getCalendarNameProvider() { 347 return new CalendarNameProvider() { 348 @Override 349 public Locale[] getAvailableLocales() { 350 return getSupportedCalendarLocales(); 351 } 352 353 @Override 354 public boolean isSupportedLocale(Locale locale) { 355 return isSupportedCalendarLocale(locale); 356 } 357 358 @Override 359 public String getDisplayName(String calType, int field, int value, 360 int style, Locale locale) { 361 return null; 362 } 363 364 @Override 365 public Map<String, Integer> getDisplayNames(String calType, 366 int field, int style, Locale locale) { 367 return null; 368 } 369 }; 370 } 371 372 public static CalendarProvider getCalendarProvider() { 373 return new CalendarProvider() { 374 @Override 375 public Locale[] getAvailableLocales() { 376 return getSupportedCalendarLocales(); 377 } 378 379 @Override 380 public boolean isSupportedLocale(Locale locale) { 381 return isSupportedCalendarLocale(locale); 382 } 383 384 @Override 385 public Calendar getInstance(TimeZone zone, Locale locale) { 386 return new Calendar.Builder() 387 .setLocale(getCalendarLocale(locale)) 388 .setTimeZone(zone) 389 .setInstant(System.currentTimeMillis()) 390 .build(); 391 } 392 }; 393 } 394 395 private static String convertDateTimePattern(String winPattern) { 396 String ret = winPattern.replaceAll("dddd", "EEEE"); 397 ret = ret.replaceAll("ddd", "EEE"); 398 ret = ret.replaceAll("tt", "aa"); 399 ret = ret.replaceAll("g", "GG"); 400 return ret; 401 } 402 403 private static Locale[] getSupportedCalendarLocales() { 404 if (supportedLocale.length != 0 && 405 supportedLocaleSet.contains(Locale.JAPAN) && 406 isJapaneseCalendar()) { 407 Locale[] sup = new Locale[supportedLocale.length+1]; 408 sup[0] = JRELocaleConstants.JA_JP_JP; 409 System.arraycopy(supportedLocale, 0, sup, 1, supportedLocale.length); 410 return sup; 411 } 412 return supportedLocale; 413 } 414 415 private static boolean isSupportedCalendarLocale(Locale locale) { 416 Locale base = locale.stripExtensions(); 417 if (!supportedLocaleSet.contains(base)) { 418 return false; 419 } 420 421 int calid = getCalendarID(locale.toLanguageTag()); 422 if (calid <= 0 || calid >= calIDToLDML.length) { 423 return false; 424 } 425 426 String requestedCalType = locale.getUnicodeLocaleType("ca"); 427 String nativeCalType = calIDToLDML[calid] 428 .replaceFirst("_.*", ""); // remove locale part. 429 430 if (requestedCalType == null) { 431 return Calendar.getAvailableCalendarTypes().contains(nativeCalType); 432 } else { 433 return requestedCalType.equals(nativeCalType); 434 } 435 } 436 437 private static Locale[] getSupportedNativeDigitLocales() { 438 if (supportedLocale.length != 0 && 439 supportedLocaleSet.contains(JRELocaleConstants.TH_TH) && 440 isNativeDigit("th-TH")) { 441 Locale[] sup = new Locale[supportedLocale.length+1]; 442 sup[0] = JRELocaleConstants.TH_TH_TH; 443 System.arraycopy(supportedLocale, 0, sup, 1, supportedLocale.length); 444 return sup; 445 } 446 return supportedLocale; 447 } 448 449 private static boolean isSupportedNativeDigitLocale(Locale locale) { 450 // special case for th_TH_TH 451 if (JRELocaleConstants.TH_TH_TH.equals(locale)) { 452 return isNativeDigit("th-TH"); 453 } 454 455 String numtype = null; 456 Locale base = locale; 457 if (locale.hasExtensions()) { 458 numtype = locale.getUnicodeLocaleType("nu"); 459 base = locale.stripExtensions(); 460 } 461 462 if (supportedLocaleSet.contains(base)) { 463 // Only supports Latin or Thai (in thai locales) digits. 464 if (numtype == null || numtype.equals("latn")) { 465 return true; 466 } else if (locale.getLanguage().equals("th")) { 467 return "thai".equals(numtype) && 468 isNativeDigit(locale.toLanguageTag()); 469 } 470 } 471 472 return false; 473 } 474 475 private static Locale removeExtensions(Locale src) { 476 return new Locale.Builder().setLocale(src).clearExtensions().build(); 477 } 478 479 private static boolean isJapaneseCalendar() { 480 return getCalendarID("ja-JP") == 3; // 3: CAL_JAPAN 481 } 482 483 private static Locale getCalendarLocale(Locale locale) { 484 int calid = getCalendarID(locale.toLanguageTag()); 485 if (calid > 0 && calid < calIDToLDML.length) { 486 Locale.Builder lb = new Locale.Builder(); 487 String[] caltype = calIDToLDML[calid].split("_"); 488 if (caltype.length > 1) { 489 lb.setLocale(Locale.forLanguageTag(caltype[1])); 490 } else { 491 lb.setLocale(locale); 492 } 493 lb.setUnicodeLocaleKeyword("ca", caltype[0]); 494 return lb.build(); 495 } 496 497 return locale; 498 } 499 500 private static Locale getNumberLocale(Locale src) { 501 if (JRELocaleConstants.TH_TH.equals(src)) { 502 if (isNativeDigit("th-TH")) { 503 Locale.Builder lb = new Locale.Builder().setLocale(src); 504 lb.setUnicodeLocaleKeyword("nu", "thai"); 505 return lb.build(); 506 } 507 } 508 509 return src; 510 } 511 512 // native methods 513 514 // initialize 515 private static native boolean initialize(); 516 private static native String getDefaultLocale(int cat); 517 518 // For DateFormatProvider 519 private static native String getDateTimePattern(int dateStyle, int timeStyle, String langTag); 520 private static native int getCalendarID(String langTag); 521 522 // For DateFormatSymbolsProvider 523 private static native String[] getAmPmStrings(String langTag, String[] ampm); 524 private static native String[] getEras(String langTag, String[] eras); 525 private static native String[] getMonths(String langTag, String[] months); 526 private static native String[] getShortMonths(String langTag, String[] smonths); 527 private static native String[] getWeekdays(String langTag, String[] wdays); 528 private static native String[] getShortWeekdays(String langTag, String[] swdays); 529 530 // For NumberFormatProvider 531 private static native String getNumberPattern(int numberStyle, String langTag); 532 private static native boolean isNativeDigit(String langTag); 533 534 // For DecimalFormatSymbolsProvider 535 private static native String getCurrencySymbol(String langTag, String currencySymbol); 536 private static native char getDecimalSeparator(String langTag, char decimalSeparator); 537 private static native char getGroupingSeparator(String langTag, char groupingSeparator); 538 private static native String getInfinity(String langTag, String infinity); 539 private static native String getInternationalCurrencySymbol(String langTag, String internationalCurrencySymbol); 540 private static native char getMinusSign(String langTag, char minusSign); 541 private static native char getMonetaryDecimalSeparator(String langTag, char monetaryDecimalSeparator); 542 private static native String getNaN(String langTag, String nan); 543 private static native char getPercent(String langTag, char percent); 544 private static native char getPerMill(String langTag, char perMill); 545 private static native char getZeroDigit(String langTag, char zeroDigit); 546 547 // For CalendarDataProvider 548 private static native int getCalendarDataValue(String langTag, int type); 549 }