--- /dev/null Fri Aug 17 22:47:08 2012 +++ new/src/windows/classes/sun/util/locale/provider/HostLocaleProviderAdapterImpl.java Fri Aug 17 22:47:06 2012 @@ -0,0 +1,516 @@ +/* + * Copyright (c) 2012, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package sun.util.locale.provider; + +import java.lang.ref.SoftReference; +import java.text.*; +import java.text.spi.DateFormatProvider; +import java.text.spi.DateFormatSymbolsProvider; +import java.text.spi.DecimalFormatSymbolsProvider; +import java.text.spi.NumberFormatProvider; +import java.util.Collections; +import java.util.HashSet; +import java.util.Locale; +import java.util.Map; +import java.util.Set; +import java.util.ResourceBundle.Control; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.atomic.AtomicReferenceArray; +import java.util.spi.CalendarDataProvider; + +/** + * LocaleProviderdapter implementation for the Windows locale data. + * + * @author Naoto Sato + */ +public class HostLocaleProviderAdapterImpl { + + // locale categories + private static final int CAT_DISPLAY = 0; + private static final int CAT_FORMAT = 1; + + // NumberFormat styles + private static final int NF_NUMBER = 0; + private static final int NF_CURRENCY = 1; + private static final int NF_PERCENT = 2; + private static final int NF_INTEGER = 3; + private static final int NF_MAX = NF_INTEGER; + + // CalendarData value types + private static final int CD_FIRSTDAYOFWEEK = 0; + private static final int CD_MINIMALDAYSINFIRSTWEEK = 1; + + // Native Calendar ID to LDML calendar type map + private static final String[] calIDToLDML = { + "", + "gregory", + "gregory_en-US", + "japanese", + "roc", + "", // No appropriate type for CAL_KOREA + "islamic", + "buddhist", + "hebrew", + "gregory_fr", + "gregory_ar", + "gregory_en", + "gregory_fr", + }; + + // Caches + private static ConcurrentMap>> dateFormatCache = new ConcurrentHashMap<>(); + private static ConcurrentMap> dateFormatSymbolsCache = new ConcurrentHashMap<>(); + private static ConcurrentMap>> numberFormatCache = new ConcurrentHashMap<>(); + private static ConcurrentMap> decimalFormatSymbolsCache = new ConcurrentHashMap<>(); + + private static final Set supportedLocaleSet; + static { + Set tmpSet = new HashSet(); + if (initialize()) { + // Assuming the default locales do not include any extensions, so + // no stripping is needed here. + Locale l = Locale.forLanguageTag(getDefaultLocale(CAT_FORMAT).replaceAll("_","-")); + tmpSet.addAll(Control.getNoFallbackControl(Control.FORMAT_DEFAULT).getCandidateLocales("", l)); + l = Locale.forLanguageTag(getDefaultLocale(CAT_DISPLAY).replaceAll("_","-")); + tmpSet.addAll(Control.getNoFallbackControl(Control.FORMAT_DEFAULT).getCandidateLocales("", l)); + } + supportedLocaleSet = Collections.unmodifiableSet(tmpSet); + } + private final static Locale[] supportedLocale = supportedLocaleSet.toArray(new Locale[0]); + + public static DateFormatProvider getDateFormatProvider() { + return new DateFormatProvider() { + @Override + public Locale[] getAvailableLocales() { + return getSupportedCalendarLocales(); + } + + @Override + public boolean isSupportedLocale(Locale locale) { + return isSupportedCalendarLocale(locale); + } + + @Override + public DateFormat getDateInstance(int style, Locale locale) { + AtomicReferenceArray patterns = getDateTimePatterns(locale); + return new SimpleDateFormat(patterns.get(style/2), + getCalendarLocale(locale)); + } + + @Override + public DateFormat getTimeInstance(int style, Locale locale) { + AtomicReferenceArray patterns = getDateTimePatterns(locale); + return new SimpleDateFormat(patterns.get(style/2+2), + getCalendarLocale(locale)); + } + + @Override + public DateFormat getDateTimeInstance(int dateStyle, + int timeStyle, Locale locale) { + AtomicReferenceArray patterns = getDateTimePatterns(locale); + String pattern = new StringBuilder(patterns.get(dateStyle/2)) + .append(" ") + .append(patterns.get(timeStyle/2+2)) + .toString(); + return new SimpleDateFormat(pattern, getCalendarLocale(locale)); + } + + private AtomicReferenceArray getDateTimePatterns(Locale locale) { + AtomicReferenceArray patterns; + SoftReference> ref = dateFormatCache.get(locale); + + if (ref == null || (patterns = ref.get()) == null) { + String langtag = removeExtensions(locale).toLanguageTag(); + patterns = new AtomicReferenceArray<>(4); + patterns.compareAndSet(0, null, convertDateTimePattern( + getDateTimePattern(DateFormat.LONG, -1, langtag))); + patterns.compareAndSet(1, null, convertDateTimePattern( + getDateTimePattern(DateFormat.SHORT, -1, langtag))); + patterns.compareAndSet(2, null, convertDateTimePattern( + getDateTimePattern(-1, DateFormat.LONG, langtag))); + patterns.compareAndSet(3, null, convertDateTimePattern( + getDateTimePattern(-1, DateFormat.SHORT, langtag))); + ref = new SoftReference<>(patterns); + dateFormatCache.put(locale, ref); + } + + return patterns; + } + }; + } + + public static DateFormatSymbolsProvider getDateFormatSymbolsProvider() { + return new DateFormatSymbolsProvider() { + + @Override + public Locale[] getAvailableLocales() { + if (isSupportedLocale(Locale.getDefault(Locale.Category.FORMAT))) { + return supportedLocale; + } + + return new Locale[0]; + } + + @Override + public boolean isSupportedLocale(Locale locale) { + // Only supports the locale with Gregorian calendar + if (supportedLocale.length != 0) { + int calid = getCalendarID(locale.toLanguageTag()); + if (calid > 0 && calid < calIDToLDML.length) { + return calIDToLDML[calid].startsWith("gregory"); + } + } + + return false; + } + + @Override + public DateFormatSymbols getInstance(Locale locale) { + DateFormatSymbols dfs; + SoftReference ref = + dateFormatSymbolsCache.get(locale); + + if (ref == null || (dfs = ref.get()) == null) { + dfs = new DateFormatSymbols(locale); + String langTag = removeExtensions(locale).toLanguageTag(); + + dfs.setAmPmStrings(getAmPmStrings(langTag, dfs.getAmPmStrings())); + dfs.setEras(getEras(langTag, dfs.getEras())); + dfs.setMonths(getMonths(langTag, dfs.getMonths())); + dfs.setShortMonths(getShortMonths(langTag, dfs.getShortMonths())); + dfs.setWeekdays(getWeekdays(langTag, dfs.getWeekdays())); + dfs.setShortWeekdays(getShortWeekdays(langTag, dfs.getShortWeekdays())); + ref = new SoftReference<>(dfs); + dateFormatSymbolsCache.put(locale, ref); + } + return (DateFormatSymbols)dfs.clone(); + } + }; + } + + public static NumberFormatProvider getNumberFormatProvider() { + return new NumberFormatProvider() { + + @Override + public Locale[] getAvailableLocales() { + return getSupportedNativeDigitLocales(); + } + + @Override + public boolean isSupportedLocale(Locale locale) { + return isSupportedNativeDigitLocale(locale); + } + + @Override + public NumberFormat getCurrencyInstance(Locale locale) { + AtomicReferenceArray patterns = getNumberPatterns(locale); + return new DecimalFormat(patterns.get(NF_CURRENCY), + DecimalFormatSymbols.getInstance(locale)); + } + + @Override + public NumberFormat getIntegerInstance(Locale locale) { + AtomicReferenceArray patterns = getNumberPatterns(locale); + return new DecimalFormat(patterns.get(NF_INTEGER), + DecimalFormatSymbols.getInstance(locale)); + } + + @Override + public NumberFormat getNumberInstance(Locale locale) { + AtomicReferenceArray patterns = getNumberPatterns(locale); + return new DecimalFormat(patterns.get(NF_NUMBER), + DecimalFormatSymbols.getInstance(locale)); + } + + @Override + public NumberFormat getPercentInstance(Locale locale) { + AtomicReferenceArray patterns = getNumberPatterns(locale); + return new DecimalFormat(patterns.get(NF_PERCENT), + DecimalFormatSymbols.getInstance(locale)); + } + + private AtomicReferenceArray getNumberPatterns(Locale locale) { + AtomicReferenceArray patterns; + SoftReference> ref = numberFormatCache.get(locale); + + if (ref == null || (patterns = ref.get()) == null) { + String langtag = locale.toLanguageTag(); + patterns = new AtomicReferenceArray(NF_MAX+1); + for (int i = 0; i <= NF_MAX; i++) { + patterns.compareAndSet(i, null, getNumberPattern(i, langtag)); + } + ref = new SoftReference<>(patterns); + numberFormatCache.put(locale, ref); + } + return patterns; + } + }; + } + + public static DecimalFormatSymbolsProvider getDecimalFormatSymbolsProvider() { + return new DecimalFormatSymbolsProvider() { + + @Override + public Locale[] getAvailableLocales() { + return getSupportedNativeDigitLocales(); + } + + @Override + public boolean isSupportedLocale(Locale locale) { + return isSupportedNativeDigitLocale(locale); + } + + @Override + public DecimalFormatSymbols getInstance(Locale locale) { + DecimalFormatSymbols dfs; + SoftReference ref = + decimalFormatSymbolsCache.get(locale); + + if (ref == null || (dfs = ref.get()) == null) { + dfs = new DecimalFormatSymbols(getNumberLocale(locale)); + String langTag = removeExtensions(locale).toLanguageTag(); + + // DecimalFormatSymbols.setInternationalCurrencySymbol() has + // a side effect of setting the currency symbol as well. So + // the calling order is relevant here. + dfs.setInternationalCurrencySymbol(getInternationalCurrencySymbol(langTag, dfs.getInternationalCurrencySymbol())); + dfs.setCurrencySymbol(getCurrencySymbol(langTag, dfs.getCurrencySymbol())); + dfs.setDecimalSeparator(getDecimalSeparator(langTag, dfs.getDecimalSeparator())); + dfs.setGroupingSeparator(getGroupingSeparator(langTag, dfs.getGroupingSeparator())); + dfs.setInfinity(getInfinity(langTag, dfs.getInfinity())); + dfs.setMinusSign(getMinusSign(langTag, dfs.getMinusSign())); + dfs.setMonetaryDecimalSeparator(getMonetaryDecimalSeparator(langTag, dfs.getMonetaryDecimalSeparator())); + dfs.setNaN(getNaN(langTag, dfs.getNaN())); + dfs.setPercent(getPercent(langTag, dfs.getPercent())); + dfs.setPerMill(getPerMill(langTag, dfs.getPerMill())); + if (isNativeDigit(langTag)) { + dfs.setZeroDigit(getZeroDigit(langTag, dfs.getZeroDigit())); + } + ref = new SoftReference<>(dfs); + decimalFormatSymbolsCache.put(locale, ref); + } + return (DecimalFormatSymbols)dfs.clone(); + } + }; + } + + public static CalendarDataProvider getCalendarDataProvider() { + return new CalendarDataProvider() { + @Override + public Locale[] getAvailableLocales() { + return getSupportedCalendarLocales(); + } + + @Override + public boolean isSupportedLocale(Locale locale) { + return isSupportedCalendarLocale(locale); + } + + @Override + public String getDisplayName(String calType, int field, int value, + int style, Locale locale) { + return null; + } + + @Override + public Map getDisplayNames(String calType, + int field, int style, Locale locale) { + return null; + } + + @Override + public int getFirstDayOfWeek(Locale locale) { + int first = getCalendarDataValue( + removeExtensions(locale).toLanguageTag(), + CD_FIRSTDAYOFWEEK); + if (first != -1) { + return (first + 1) % 7 + 1; + } else { + return 0; + } + } + + @Override + public int getMinimalDaysInFirstWeek(Locale locale) { + return 0; + } + }; + } + + private static String convertDateTimePattern(String winPattern) { + String ret = winPattern.replaceAll("dddd", "EEEE"); + ret = ret.replaceAll("ddd", "EEE"); + ret = ret.replaceAll("tt", "aa"); + ret = ret.replaceAll("g", "GG"); + return ret; + } + + private static Locale[] getSupportedCalendarLocales() { + if (supportedLocale.length != 0 && + supportedLocaleSet.contains(Locale.JAPAN) && + isJapaneseCalendar()) { + Locale[] sup = new Locale[supportedLocale.length+1]; + sup[0] = JRELocaleConstants.JA_JP_JP; + System.arraycopy(supportedLocale, 0, sup, 1, supportedLocale.length); + return sup; + } + return supportedLocale; + } + + private static boolean isSupportedCalendarLocale(Locale locale) { + // special case for ja_JP_JP + if (JRELocaleConstants.JA_JP_JP.equals(locale)) { + return isJapaneseCalendar(); + } + + Locale base = locale.stripExtensions(); + if (!supportedLocaleSet.contains(base)) { + return false; + } + + String caltype = locale.getUnicodeLocaleType("ca"); + if (caltype == null) { + return true; + } + + return caltype.equals( + calIDToLDML[getCalendarID(locale.toLanguageTag())] + .replaceFirst("_.*", "")); + } + + private static Locale[] getSupportedNativeDigitLocales() { + if (supportedLocale.length != 0 && + supportedLocaleSet.contains(JRELocaleConstants.TH_TH) && + isNativeDigit("th-TH")) { + Locale[] sup = new Locale[supportedLocale.length+1]; + sup[0] = JRELocaleConstants.TH_TH_TH; + System.arraycopy(supportedLocale, 0, sup, 1, supportedLocale.length); + return sup; + } + return supportedLocale; + } + + private static boolean isSupportedNativeDigitLocale(Locale locale) { + // special case for th_TH_TH + if (JRELocaleConstants.TH_TH_TH.equals(locale)) { + return isNativeDigit("th-TH"); + } + + String numtype = null; + Locale base = locale; + if (locale.hasExtensions()) { + numtype = locale.getUnicodeLocaleType("nu"); + base = locale.stripExtensions(); + } + + if (supportedLocaleSet.contains(base)) { + // Only supports Latin or Thai (in thai locales) digits. + if (numtype == null || numtype.equals("latn")) { + return true; + } else if (locale.getLanguage().equals("th")) { + return "thai".equals(numtype) && + isNativeDigit(locale.toLanguageTag()); + } + } + + return false; + } + + private static Locale removeExtensions(Locale src) { + return new Locale.Builder().setLocale(src).clearExtensions().build(); + } + + private static boolean isJapaneseCalendar() { + return getCalendarID("ja-JP") == 3; // 3: CAL_JAPAN + } + + private static Locale getCalendarLocale(Locale locale) { + int calid = getCalendarID(locale.toLanguageTag()); + if (calid > 0 && calid < calIDToLDML.length) { + Locale.Builder lb = new Locale.Builder(); + String[] caltype = calIDToLDML[calid].split("_"); + if (caltype.length > 1) { + lb.setLocale(Locale.forLanguageTag(caltype[1])); + } else { + lb.setLocale(locale); + } + lb.setUnicodeLocaleKeyword("ca", caltype[0]); + return lb.build(); + } + + return locale; + } + + private static Locale getNumberLocale(Locale src) { + if (JRELocaleConstants.TH_TH.equals(src)) { + if (isNativeDigit("th-TH")) { + Locale.Builder lb = new Locale.Builder().setLocale(src); + lb.setUnicodeLocaleKeyword("nu", "thai"); + return lb.build(); + } + } + + return src; + } + + // native methods + + // initialize + private static native boolean initialize(); + private static native String getDefaultLocale(int cat); + + // For DateFormatProvider + private static native String getDateTimePattern(int dateStyle, int timeStyle, String langTag); + private static native int getCalendarID(String langTag); + + // For DateFormatSymbolsProvider + private static native String[] getAmPmStrings(String langTag, String[] ampm); + private static native String[] getEras(String langTag, String[] eras); + private static native String[] getMonths(String langTag, String[] months); + private static native String[] getShortMonths(String langTag, String[] smonths); + private static native String[] getWeekdays(String langTag, String[] wdays); + private static native String[] getShortWeekdays(String langTag, String[] swdays); + + // For NumberFormatProvider + private static native String getNumberPattern(int numberStyle, String langTag); + private static native boolean isNativeDigit(String langTag); + + // For DecimalFormatSymbolsProvider + private static native String getCurrencySymbol(String langTag, String currencySymbol); + private static native char getDecimalSeparator(String langTag, char decimalSeparator); + private static native char getGroupingSeparator(String langTag, char groupingSeparator); + private static native String getInfinity(String langTag, String infinity); + private static native String getInternationalCurrencySymbol(String langTag, String internationalCurrencySymbol); + private static native char getMinusSign(String langTag, char minusSign); + private static native char getMonetaryDecimalSeparator(String langTag, char monetaryDecimalSeparator); + private static native String getNaN(String langTag, String nan); + private static native char getPercent(String langTag, char percent); + private static native char getPerMill(String langTag, char perMill); + private static native char getZeroDigit(String langTag, char zeroDigit); + + // For CalendarDataProvider + private static native int getCalendarDataValue(String langTag, int type); +}