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 }