Path: blob/aarch64-shenandoah-jdk8u272-b10/jdk/src/windows/classes/sun/util/locale/provider/HostLocaleProviderAdapterImpl.java
32302 views
/*1* Copyright (c) 2012, 2013, Oracle and/or its affiliates. All rights reserved.2* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.3*4* This code is free software; you can redistribute it and/or modify it5* under the terms of the GNU General Public License version 2 only, as6* published by the Free Software Foundation. Oracle designates this7* particular file as subject to the "Classpath" exception as provided8* by Oracle in the LICENSE file that accompanied this code.9*10* This code is distributed in the hope that it will be useful, but WITHOUT11* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or12* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License13* version 2 for more details (a copy is included in the LICENSE file that14* accompanied this code).15*16* You should have received a copy of the GNU General Public License version17* 2 along with this work; if not, write to the Free Software Foundation,18* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.19*20* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA21* or visit www.oracle.com if you need additional information or have any22* questions.23*/24package sun.util.locale.provider;2526import java.lang.ref.SoftReference;27import java.text.DateFormat;28import java.text.DateFormatSymbols;29import java.text.DecimalFormat;30import java.text.DecimalFormatSymbols;31import java.text.NumberFormat;32import java.text.SimpleDateFormat;33import java.text.spi.DateFormatProvider;34import java.text.spi.DateFormatSymbolsProvider;35import java.text.spi.DecimalFormatSymbolsProvider;36import java.text.spi.NumberFormatProvider;37import java.util.Calendar;38import java.util.Collections;39import java.util.Currency;40import java.util.HashSet;41import java.util.Locale;42import java.util.Map;43import java.util.ResourceBundle.Control;44import java.util.Set;45import java.util.TimeZone;46import java.util.concurrent.ConcurrentHashMap;47import java.util.concurrent.ConcurrentMap;48import java.util.concurrent.atomic.AtomicReferenceArray;49import java.util.spi.CalendarDataProvider;50import java.util.spi.CurrencyNameProvider;51import java.util.spi.LocaleNameProvider;52import sun.util.spi.CalendarProvider;5354/**55* LocaleProviderdapter implementation for the Windows locale data.56*57* @author Naoto Sato58*/59public class HostLocaleProviderAdapterImpl {6061// locale categories62private static final int CAT_DISPLAY = 0;63private static final int CAT_FORMAT = 1;6465// NumberFormat styles66private static final int NF_NUMBER = 0;67private static final int NF_CURRENCY = 1;68private static final int NF_PERCENT = 2;69private static final int NF_INTEGER = 3;70private static final int NF_MAX = NF_INTEGER;7172// CalendarData value types73private static final int CD_FIRSTDAYOFWEEK = 0;74private static final int CD_MINIMALDAYSINFIRSTWEEK = 1;7576// Currency/Locale display name types77private static final int DN_CURRENCY_NAME = 0;78private static final int DN_CURRENCY_SYMBOL = 1;79private static final int DN_LOCALE_LANGUAGE = 2;80private static final int DN_LOCALE_SCRIPT = 3;81private static final int DN_LOCALE_REGION = 4;82private static final int DN_LOCALE_VARIANT = 5;8384// Native Calendar ID to LDML calendar type map85private static final String[] calIDToLDML = {86"",87"gregory",88"gregory_en-US",89"japanese",90"roc",91"", // No appropriate type for CAL_KOREA92"islamic",93"buddhist",94"hebrew",95"gregory_fr",96"gregory_ar",97"gregory_en",98"gregory_fr",99};100101// Caches102private static ConcurrentMap<Locale, SoftReference<AtomicReferenceArray<String>>> dateFormatCache = new ConcurrentHashMap<>();103private static ConcurrentMap<Locale, SoftReference<DateFormatSymbols>> dateFormatSymbolsCache = new ConcurrentHashMap<>();104private static ConcurrentMap<Locale, SoftReference<AtomicReferenceArray<String>>> numberFormatCache = new ConcurrentHashMap<>();105private static ConcurrentMap<Locale, SoftReference<DecimalFormatSymbols>> decimalFormatSymbolsCache = new ConcurrentHashMap<>();106107private static final Set<Locale> supportedLocaleSet;108private static final String nativeDisplayLanguage;109static {110Set<Locale> tmpSet = new HashSet<>();111if (initialize()) {112// Assuming the default locales do not include any extensions, so113// no stripping is needed here.114Control c = Control.getNoFallbackControl(Control.FORMAT_DEFAULT);115String displayLocale = getDefaultLocale(CAT_DISPLAY);116Locale l = Locale.forLanguageTag(displayLocale.replace('_', '-'));117tmpSet.addAll(c.getCandidateLocales("", l));118nativeDisplayLanguage = l.getLanguage();119120String formatLocale = getDefaultLocale(CAT_FORMAT);121if (!formatLocale.equals(displayLocale)) {122l = Locale.forLanguageTag(formatLocale.replace('_', '-'));123tmpSet.addAll(c.getCandidateLocales("", l));124}125} else {126nativeDisplayLanguage = "";127}128supportedLocaleSet = Collections.unmodifiableSet(tmpSet);129}130private final static Locale[] supportedLocale = supportedLocaleSet.toArray(new Locale[0]);131132public static DateFormatProvider getDateFormatProvider() {133return new DateFormatProvider() {134@Override135public Locale[] getAvailableLocales() {136return getSupportedCalendarLocales();137}138139@Override140public boolean isSupportedLocale(Locale locale) {141return isSupportedCalendarLocale(locale);142}143144@Override145public DateFormat getDateInstance(int style, Locale locale) {146AtomicReferenceArray<String> patterns = getDateTimePatterns(locale);147return new SimpleDateFormat(patterns.get(style/2),148getCalendarLocale(locale));149}150151@Override152public DateFormat getTimeInstance(int style, Locale locale) {153AtomicReferenceArray<String> patterns = getDateTimePatterns(locale);154return new SimpleDateFormat(patterns.get(style/2+2),155getCalendarLocale(locale));156}157158@Override159public DateFormat getDateTimeInstance(int dateStyle,160int timeStyle, Locale locale) {161AtomicReferenceArray<String> patterns = getDateTimePatterns(locale);162String pattern = new StringBuilder(patterns.get(dateStyle/2))163.append(" ")164.append(patterns.get(timeStyle/2+2))165.toString();166return new SimpleDateFormat(pattern, getCalendarLocale(locale));167}168169private AtomicReferenceArray<String> getDateTimePatterns(Locale locale) {170AtomicReferenceArray<String> patterns;171SoftReference<AtomicReferenceArray<String>> ref = dateFormatCache.get(locale);172173if (ref == null || (patterns = ref.get()) == null) {174String langtag = removeExtensions(locale).toLanguageTag();175patterns = new AtomicReferenceArray<>(4);176patterns.compareAndSet(0, null, convertDateTimePattern(177getDateTimePattern(DateFormat.LONG, -1, langtag)));178patterns.compareAndSet(1, null, convertDateTimePattern(179getDateTimePattern(DateFormat.SHORT, -1, langtag)));180patterns.compareAndSet(2, null, convertDateTimePattern(181getDateTimePattern(-1, DateFormat.LONG, langtag)));182patterns.compareAndSet(3, null, convertDateTimePattern(183getDateTimePattern(-1, DateFormat.SHORT, langtag)));184ref = new SoftReference<>(patterns);185dateFormatCache.put(locale, ref);186}187188return patterns;189}190};191}192193public static DateFormatSymbolsProvider getDateFormatSymbolsProvider() {194return new DateFormatSymbolsProvider() {195196@Override197public Locale[] getAvailableLocales() {198return getSupportedCalendarLocales();199}200201@Override202public boolean isSupportedLocale(Locale locale) {203return isSupportedCalendarLocale(locale);204}205206@Override207public DateFormatSymbols getInstance(Locale locale) {208DateFormatSymbols dfs;209SoftReference<DateFormatSymbols> ref =210dateFormatSymbolsCache.get(locale);211212if (ref == null || (dfs = ref.get()) == null) {213dfs = new DateFormatSymbols(locale);214String langTag = removeExtensions(locale).toLanguageTag();215216dfs.setAmPmStrings(getAmPmStrings(langTag, dfs.getAmPmStrings()));217dfs.setEras(getEras(langTag, dfs.getEras()));218dfs.setMonths(getMonths(langTag, dfs.getMonths()));219dfs.setShortMonths(getShortMonths(langTag, dfs.getShortMonths()));220dfs.setWeekdays(getWeekdays(langTag, dfs.getWeekdays()));221dfs.setShortWeekdays(getShortWeekdays(langTag, dfs.getShortWeekdays()));222ref = new SoftReference<>(dfs);223dateFormatSymbolsCache.put(locale, ref);224}225return (DateFormatSymbols)dfs.clone();226}227};228}229230public static NumberFormatProvider getNumberFormatProvider() {231return new NumberFormatProvider() {232233@Override234public Locale[] getAvailableLocales() {235return getSupportedNativeDigitLocales();236}237238@Override239public boolean isSupportedLocale(Locale locale) {240return isSupportedNativeDigitLocale(locale);241}242243@Override244public NumberFormat getCurrencyInstance(Locale locale) {245AtomicReferenceArray<String> patterns = getNumberPatterns(locale);246return new DecimalFormat(patterns.get(NF_CURRENCY),247DecimalFormatSymbols.getInstance(locale));248}249250@Override251public NumberFormat getIntegerInstance(Locale locale) {252AtomicReferenceArray<String> patterns = getNumberPatterns(locale);253return new DecimalFormat(patterns.get(NF_INTEGER),254DecimalFormatSymbols.getInstance(locale));255}256257@Override258public NumberFormat getNumberInstance(Locale locale) {259AtomicReferenceArray<String> patterns = getNumberPatterns(locale);260return new DecimalFormat(patterns.get(NF_NUMBER),261DecimalFormatSymbols.getInstance(locale));262}263264@Override265public NumberFormat getPercentInstance(Locale locale) {266AtomicReferenceArray<String> patterns = getNumberPatterns(locale);267return new DecimalFormat(patterns.get(NF_PERCENT),268DecimalFormatSymbols.getInstance(locale));269}270271private AtomicReferenceArray<String> getNumberPatterns(Locale locale) {272AtomicReferenceArray<String> patterns;273SoftReference<AtomicReferenceArray<String>> ref = numberFormatCache.get(locale);274275if (ref == null || (patterns = ref.get()) == null) {276String langtag = locale.toLanguageTag();277patterns = new AtomicReferenceArray<>(NF_MAX+1);278for (int i = 0; i <= NF_MAX; i++) {279patterns.compareAndSet(i, null, getNumberPattern(i, langtag));280}281ref = new SoftReference<>(patterns);282numberFormatCache.put(locale, ref);283}284return patterns;285}286};287}288289public static DecimalFormatSymbolsProvider getDecimalFormatSymbolsProvider() {290return new DecimalFormatSymbolsProvider() {291292@Override293public Locale[] getAvailableLocales() {294return getSupportedNativeDigitLocales();295}296297@Override298public boolean isSupportedLocale(Locale locale) {299return isSupportedNativeDigitLocale(locale);300}301302@Override303public DecimalFormatSymbols getInstance(Locale locale) {304DecimalFormatSymbols dfs;305SoftReference<DecimalFormatSymbols> ref =306decimalFormatSymbolsCache.get(locale);307308if (ref == null || (dfs = ref.get()) == null) {309dfs = new DecimalFormatSymbols(getNumberLocale(locale));310String langTag = removeExtensions(locale).toLanguageTag();311312// DecimalFormatSymbols.setInternationalCurrencySymbol() has313// a side effect of setting the currency symbol as well. So314// the calling order is relevant here.315dfs.setInternationalCurrencySymbol(getInternationalCurrencySymbol(langTag, dfs.getInternationalCurrencySymbol()));316dfs.setCurrencySymbol(getCurrencySymbol(langTag, dfs.getCurrencySymbol()));317dfs.setDecimalSeparator(getDecimalSeparator(langTag, dfs.getDecimalSeparator()));318dfs.setGroupingSeparator(getGroupingSeparator(langTag, dfs.getGroupingSeparator()));319dfs.setInfinity(getInfinity(langTag, dfs.getInfinity()));320dfs.setMinusSign(getMinusSign(langTag, dfs.getMinusSign()));321dfs.setMonetaryDecimalSeparator(getMonetaryDecimalSeparator(langTag, dfs.getMonetaryDecimalSeparator()));322dfs.setNaN(getNaN(langTag, dfs.getNaN()));323dfs.setPercent(getPercent(langTag, dfs.getPercent()));324dfs.setPerMill(getPerMill(langTag, dfs.getPerMill()));325dfs.setZeroDigit(getZeroDigit(langTag, dfs.getZeroDigit()));326ref = new SoftReference<>(dfs);327decimalFormatSymbolsCache.put(locale, ref);328}329return (DecimalFormatSymbols)dfs.clone();330}331};332}333334public static CalendarDataProvider getCalendarDataProvider() {335return new CalendarDataProvider() {336@Override337public Locale[] getAvailableLocales() {338return getSupportedCalendarLocales();339}340341@Override342public boolean isSupportedLocale(Locale locale) {343return isSupportedCalendarLocale(locale);344}345346@Override347public int getFirstDayOfWeek(Locale locale) {348int first = getCalendarDataValue(349removeExtensions(locale).toLanguageTag(),350CD_FIRSTDAYOFWEEK);351if (first != -1) {352return (first + 1) % 7 + 1;353} else {354return 0;355}356}357358@Override359public int getMinimalDaysInFirstWeek(Locale locale) {360return 0;361}362};363}364365public static CalendarProvider getCalendarProvider() {366return new CalendarProvider() {367@Override368public Locale[] getAvailableLocales() {369return getSupportedCalendarLocales();370}371372@Override373public boolean isSupportedLocale(Locale locale) {374return isSupportedCalendarLocale(locale);375}376377@Override378public Calendar getInstance(TimeZone zone, Locale locale) {379return new Calendar.Builder()380.setLocale(getCalendarLocale(locale))381.setTimeZone(zone)382.setInstant(System.currentTimeMillis())383.build();384}385};386}387388public static CurrencyNameProvider getCurrencyNameProvider() {389return new CurrencyNameProvider() {390@Override391public Locale[] getAvailableLocales() {392return supportedLocale;393}394395@Override396public boolean isSupportedLocale(Locale locale) {397// Ignore the extensions for now398return supportedLocaleSet.contains(locale.stripExtensions()) &&399locale.getLanguage().equals(nativeDisplayLanguage);400}401402@Override403public String getSymbol(String currencyCode, Locale locale) {404// Retrieves the currency symbol by calling405// GetLocaleInfoEx(LOCALE_SCURRENCY).406// It only works with the "locale"'s currency in its native407// language.408try {409if (Currency.getInstance(locale).getCurrencyCode()410.equals(currencyCode)) {411return getDisplayString(locale.toLanguageTag(),412DN_CURRENCY_SYMBOL, currencyCode);413}414} catch (IllegalArgumentException iae) {}415return null;416}417418@Override419public String getDisplayName(String currencyCode, Locale locale) {420// Retrieves the display name by calling421// GetLocaleInfoEx(LOCALE_SNATIVECURRNAME).422// It only works with the "locale"'s currency in its native423// language.424try {425if (Currency.getInstance(locale).getCurrencyCode()426.equals(currencyCode)) {427return getDisplayString(locale.toLanguageTag(),428DN_CURRENCY_NAME, currencyCode);429}430} catch (IllegalArgumentException iae) {}431return null;432}433};434}435436public static LocaleNameProvider getLocaleNameProvider() {437return new LocaleNameProvider() {438@Override439public Locale[] getAvailableLocales() {440return supportedLocale;441}442443@Override444public boolean isSupportedLocale(Locale locale) {445return supportedLocaleSet.contains(locale.stripExtensions()) &&446locale.getLanguage().equals(nativeDisplayLanguage);447}448449@Override450public String getDisplayLanguage(String languageCode, Locale locale) {451// Retrieves the display language name by calling452// GetLocaleInfoEx(LOCALE_SLOCALIZEDLANGUAGENAME).453return getDisplayString(locale.toLanguageTag(),454DN_LOCALE_LANGUAGE, languageCode);455}456457@Override458public String getDisplayCountry(String countryCode, Locale locale) {459// Retrieves the display country name by calling460// GetLocaleInfoEx(LOCALE_SLOCALIZEDCOUNTRYNAME).461return getDisplayString(locale.toLanguageTag(),462DN_LOCALE_REGION, nativeDisplayLanguage+"-"+countryCode);463}464465@Override466public String getDisplayScript(String scriptCode, Locale locale) {467return null;468}469470@Override471public String getDisplayVariant(String variantCode, Locale locale) {472return null;473}474};475}476477478private static String convertDateTimePattern(String winPattern) {479String ret = winPattern.replaceAll("dddd", "EEEE");480ret = ret.replaceAll("ddd", "EEE");481ret = ret.replaceAll("tt", "aa");482ret = ret.replaceAll("g", "GG");483return ret;484}485486private static Locale[] getSupportedCalendarLocales() {487if (supportedLocale.length != 0 &&488supportedLocaleSet.contains(Locale.JAPAN) &&489isJapaneseCalendar()) {490Locale[] sup = new Locale[supportedLocale.length+1];491sup[0] = JRELocaleConstants.JA_JP_JP;492System.arraycopy(supportedLocale, 0, sup, 1, supportedLocale.length);493return sup;494}495return supportedLocale;496}497498private static boolean isSupportedCalendarLocale(Locale locale) {499Locale base = locale;500501if (base.hasExtensions() || base.getVariant() != "") {502// strip off extensions and variant.503base = new Locale.Builder()504.setLocale(locale)505.clearExtensions()506.build();507}508509if (!supportedLocaleSet.contains(base)) {510return false;511}512513int calid = getCalendarID(base.toLanguageTag());514if (calid <= 0 || calid >= calIDToLDML.length) {515return false;516}517518String requestedCalType = locale.getUnicodeLocaleType("ca");519String nativeCalType = calIDToLDML[calid]520.replaceFirst("_.*", ""); // remove locale part.521522if (requestedCalType == null) {523return Calendar.getAvailableCalendarTypes().contains(nativeCalType);524} else {525return requestedCalType.equals(nativeCalType);526}527}528529private static Locale[] getSupportedNativeDigitLocales() {530if (supportedLocale.length != 0 &&531supportedLocaleSet.contains(JRELocaleConstants.TH_TH) &&532isNativeDigit("th-TH")) {533Locale[] sup = new Locale[supportedLocale.length+1];534sup[0] = JRELocaleConstants.TH_TH_TH;535System.arraycopy(supportedLocale, 0, sup, 1, supportedLocale.length);536return sup;537}538return supportedLocale;539}540541private static boolean isSupportedNativeDigitLocale(Locale locale) {542// special case for th_TH_TH543if (JRELocaleConstants.TH_TH_TH.equals(locale)) {544return isNativeDigit("th-TH");545}546547String numtype = null;548Locale base = locale;549if (locale.hasExtensions()) {550numtype = locale.getUnicodeLocaleType("nu");551base = locale.stripExtensions();552}553554if (supportedLocaleSet.contains(base)) {555// Only supports Latin or Thai (in thai locales) digits.556if (numtype == null || numtype.equals("latn")) {557return true;558} else if (locale.getLanguage().equals("th")) {559return "thai".equals(numtype) &&560isNativeDigit(locale.toLanguageTag());561}562}563564return false;565}566567private static Locale removeExtensions(Locale src) {568return new Locale.Builder().setLocale(src).clearExtensions().build();569}570571private static boolean isJapaneseCalendar() {572return getCalendarID("ja-JP") == 3; // 3: CAL_JAPAN573}574575private static Locale getCalendarLocale(Locale locale) {576int calid = getCalendarID(locale.toLanguageTag());577if (calid > 0 && calid < calIDToLDML.length) {578Locale.Builder lb = new Locale.Builder();579String[] caltype = calIDToLDML[calid].split("_");580if (caltype.length > 1) {581lb.setLocale(Locale.forLanguageTag(caltype[1]));582} else {583lb.setLocale(locale);584}585lb.setUnicodeLocaleKeyword("ca", caltype[0]);586return lb.build();587}588589return locale;590}591592private static Locale getNumberLocale(Locale src) {593if (JRELocaleConstants.TH_TH.equals(src)) {594if (isNativeDigit("th-TH")) {595Locale.Builder lb = new Locale.Builder().setLocale(src);596lb.setUnicodeLocaleKeyword("nu", "thai");597return lb.build();598}599}600601return src;602}603604// native methods605606// initialize607private static native boolean initialize();608private static native String getDefaultLocale(int cat);609610// For DateFormatProvider611private static native String getDateTimePattern(int dateStyle, int timeStyle, String langTag);612private static native int getCalendarID(String langTag);613614// For DateFormatSymbolsProvider615private static native String[] getAmPmStrings(String langTag, String[] ampm);616private static native String[] getEras(String langTag, String[] eras);617private static native String[] getMonths(String langTag, String[] months);618private static native String[] getShortMonths(String langTag, String[] smonths);619private static native String[] getWeekdays(String langTag, String[] wdays);620private static native String[] getShortWeekdays(String langTag, String[] swdays);621622// For NumberFormatProvider623private static native String getNumberPattern(int numberStyle, String langTag);624private static native boolean isNativeDigit(String langTag);625626// For DecimalFormatSymbolsProvider627private static native String getCurrencySymbol(String langTag, String currencySymbol);628private static native char getDecimalSeparator(String langTag, char decimalSeparator);629private static native char getGroupingSeparator(String langTag, char groupingSeparator);630private static native String getInfinity(String langTag, String infinity);631private static native String getInternationalCurrencySymbol(String langTag, String internationalCurrencySymbol);632private static native char getMinusSign(String langTag, char minusSign);633private static native char getMonetaryDecimalSeparator(String langTag, char monetaryDecimalSeparator);634private static native String getNaN(String langTag, String nan);635private static native char getPercent(String langTag, char percent);636private static native char getPerMill(String langTag, char perMill);637private static native char getZeroDigit(String langTag, char zeroDigit);638639// For CalendarDataProvider640private static native int getCalendarDataValue(String langTag, int type);641642// For Locale/CurrencyNameProvider643private static native String getDisplayString(String langTag, int key, String value);644}645646647