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 if (isNativeDigit(langTag)) { 307 dfs.setZeroDigit(getZeroDigit(langTag, dfs.getZeroDigit())); 308 } 309 ref = new SoftReference<>(dfs); 310 decimalFormatSymbolsCache.put(locale, ref); 311 } 312 return (DecimalFormatSymbols)dfs.clone(); 313 } 314 }; 315 } 316 317 public static CalendarDataProvider getCalendarDataProvider() { 318 return new CalendarDataProvider() { 319 @Override 320 public Locale[] getAvailableLocales() { 321 return getSupportedCalendarLocales(); 322 } 323 324 @Override 325 public boolean isSupportedLocale(Locale locale) { 326 return isSupportedCalendarLocale(locale); 327 } 328 329 @Override 330 public int getFirstDayOfWeek(Locale locale) { 331 int first = getCalendarDataValue( 332 removeExtensions(locale).toLanguageTag(), 333 CD_FIRSTDAYOFWEEK); 334 if (first != -1) { 335 return (first + 1) % 7 + 1; 336 } else { 337 return 0; 338 } 339 } 340 341 @Override 342 public int getMinimalDaysInFirstWeek(Locale locale) { 343 return 0; 344 } 345 }; 346 } 347 348 public static CalendarNameProvider getCalendarNameProvider() { 349 return new CalendarNameProvider() { 350 @Override 351 public Locale[] getAvailableLocales() { 352 return getSupportedCalendarLocales(); 353 } 354 355 @Override 356 public boolean isSupportedLocale(Locale locale) { 357 return isSupportedCalendarLocale(locale); 358 } 359 360 @Override 361 public String getDisplayName(String calType, int field, int value, 362 int style, Locale locale) { 363 return null; 364 } 365 366 @Override 367 public Map<String, Integer> getDisplayNames(String calType, 368 int field, int style, Locale locale) { 369 return null; 370 } 371 }; 372 } 373 374 public static CalendarProvider getCalendarProvider() { 375 return new CalendarProvider() { 376 @Override 377 public Locale[] getAvailableLocales() { 378 return getSupportedCalendarLocales(); 379 } 380 381 @Override 382 public boolean isSupportedLocale(Locale locale) { 383 return isSupportedCalendarLocale(locale); 384 } 385 386 @Override 387 public Calendar getInstance(TimeZone zone, Locale locale) { 388 return new Calendar.Builder() 389 .setLocale(getCalendarLocale(locale)) 390 .setTimeZone(zone) 391 .setInstant(System.currentTimeMillis()) 392 .build(); 393 } 394 }; 395 } 396 397 private static String convertDateTimePattern(String winPattern) { 398 String ret = winPattern.replaceAll("dddd", "EEEE"); 399 ret = ret.replaceAll("ddd", "EEE"); 400 ret = ret.replaceAll("tt", "aa"); 401 ret = ret.replaceAll("g", "GG"); 402 return ret; 403 } 404 405 private static Locale[] getSupportedCalendarLocales() { 406 if (supportedLocale.length != 0 && 407 supportedLocaleSet.contains(Locale.JAPAN) && 408 isJapaneseCalendar()) { 409 Locale[] sup = new Locale[supportedLocale.length+1]; 410 sup[0] = JRELocaleConstants.JA_JP_JP; 411 System.arraycopy(supportedLocale, 0, sup, 1, supportedLocale.length); 412 return sup; 413 } 414 return supportedLocale; 415 } 416 417 private static boolean isSupportedCalendarLocale(Locale locale) { 418 Locale base = locale.stripExtensions(); 419 if (!supportedLocaleSet.contains(base)) { 420 return false; 421 } 422 423 String requestedCalType = locale.getUnicodeLocaleType("ca"); 424 String nativeCalType = 425 calIDToLDML[getCalendarID(locale.toLanguageTag())] 426 .replaceFirst("_.*", ""); // remove locale part. 427 428 if (requestedCalType == null) { 429 return Calendar.getAvailableCalendarTypes().contains(nativeCalType); 430 } else { 431 return requestedCalType.equals(nativeCalType); 432 } 433 } 434 435 private static Locale[] getSupportedNativeDigitLocales() { 436 if (supportedLocale.length != 0 && 437 supportedLocaleSet.contains(JRELocaleConstants.TH_TH) && 438 isNativeDigit("th-TH")) { 439 Locale[] sup = new Locale[supportedLocale.length+1]; 440 sup[0] = JRELocaleConstants.TH_TH_TH; 441 System.arraycopy(supportedLocale, 0, sup, 1, supportedLocale.length); 442 return sup; 443 } 444 return supportedLocale; 445 } 446 447 private static boolean isSupportedNativeDigitLocale(Locale locale) { 448 // special case for th_TH_TH 449 if (JRELocaleConstants.TH_TH_TH.equals(locale)) { 450 return isNativeDigit("th-TH"); 451 } 452 453 String numtype = null; 454 Locale base = locale; 455 if (locale.hasExtensions()) { 456 numtype = locale.getUnicodeLocaleType("nu"); 457 base = locale.stripExtensions(); 458 } 459 460 if (supportedLocaleSet.contains(base)) { 461 // Only supports Latin or Thai (in thai locales) digits. 462 if (numtype == null || numtype.equals("latn")) { 463 return true; 464 } else if (locale.getLanguage().equals("th")) { 465 return "thai".equals(numtype) && 466 isNativeDigit(locale.toLanguageTag()); 467 } 468 } 469 470 return false; 471 } 472 473 private static Locale removeExtensions(Locale src) { 474 return new Locale.Builder().setLocale(src).clearExtensions().build(); 475 } 476 477 private static boolean isJapaneseCalendar() { 478 return getCalendarID("ja-JP") == 3; // 3: CAL_JAPAN 479 } 480 481 private static Locale getCalendarLocale(Locale locale) { 482 int calid = getCalendarID(locale.toLanguageTag()); 483 if (calid > 0 && calid < calIDToLDML.length) { 484 Locale.Builder lb = new Locale.Builder(); 485 String[] caltype = calIDToLDML[calid].split("_"); 486 if (caltype.length > 1) { 487 lb.setLocale(Locale.forLanguageTag(caltype[1])); 488 } else { 489 lb.setLocale(locale); 490 } 491 lb.setUnicodeLocaleKeyword("ca", caltype[0]); 492 return lb.build(); 493 } 494 495 return locale; 496 } 497 498 private static Locale getNumberLocale(Locale src) { 499 if (JRELocaleConstants.TH_TH.equals(src)) { 500 if (isNativeDigit("th-TH")) { 501 Locale.Builder lb = new Locale.Builder().setLocale(src); 502 lb.setUnicodeLocaleKeyword("nu", "thai"); 503 return lb.build(); 504 } 505 } 506 507 return src; 508 } 509 510 // native methods 511 512 // initialize 513 private static native boolean initialize(); 514 private static native String getDefaultLocale(int cat); 515 516 // For DateFormatProvider 517 private static native String getDateTimePattern(int dateStyle, int timeStyle, String langTag); 518 private static native int getCalendarID(String langTag); 519 520 // For DateFormatSymbolsProvider 521 private static native String[] getAmPmStrings(String langTag, String[] ampm); 522 private static native String[] getEras(String langTag, String[] eras); 523 private static native String[] getMonths(String langTag, String[] months); 524 private static native String[] getShortMonths(String langTag, String[] smonths); 525 private static native String[] getWeekdays(String langTag, String[] wdays); 526 private static native String[] getShortWeekdays(String langTag, String[] swdays); 527 528 // For NumberFormatProvider 529 private static native String getNumberPattern(int numberStyle, String langTag); 530 private static native boolean isNativeDigit(String langTag); 531 532 // For DecimalFormatSymbolsProvider 533 private static native String getCurrencySymbol(String langTag, String currencySymbol); 534 private static native char getDecimalSeparator(String langTag, char decimalSeparator); 535 private static native char getGroupingSeparator(String langTag, char groupingSeparator); 536 private static native String getInfinity(String langTag, String infinity); 537 private static native String getInternationalCurrencySymbol(String langTag, String internationalCurrencySymbol); 538 private static native char getMinusSign(String langTag, char minusSign); 539 private static native char getMonetaryDecimalSeparator(String langTag, char monetaryDecimalSeparator); 540 private static native String getNaN(String langTag, String nan); 541 private static native char getPercent(String langTag, char percent); 542 private static native char getPerMill(String langTag, char perMill); 543 private static native char getZeroDigit(String langTag, char zeroDigit); 544 545 // For CalendarDataProvider 546 private static native int getCalendarDataValue(String langTag, int type); 547 }