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