--- /dev/null Mon Aug 13 12:10:59 2012 +++ new/src/macosx/classes/sun/util/locale/provider/HostLocaleProviderAdapterImpl.java Mon Aug 13 12:10:57 2012 @@ -0,0 +1,667 @@ +/* + * 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.ResourceBundle.Control; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.atomic.AtomicReferenceArray; +import java.util.spi.CalendarDataProvider; +import java.util.spi.CurrencyNameProvider; +import java.util.spi.LocaleNameProvider; +import java.util.spi.TimeZoneNameProvider; + +/** + * LocaleProviderAdapter implementation for the Mac OS X locale data + * + * @author Naoto Sato + */ +public class HostLocaleProviderAdapterImpl { + + // per supported locale instances + private static ConcurrentMap>> dateFormatPatternsMap = + new ConcurrentHashMap<>(2); + private static ConcurrentMap>> numberFormatPatternsMap = + new ConcurrentHashMap<>(2); + private static ConcurrentMap> dateFormatSymbolsMap = + new ConcurrentHashMap<>(2); + private static ConcurrentMap> decimalFormatSymbolsMap = + new ConcurrentHashMap<>(2); + + // 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; + + // Locale/Currency display name types + private static final int DN_LOCALE_LANGUAGE = 0; + private static final int DN_LOCALE_SCRIPT = 1; + private static final int DN_LOCALE_REGION = 2; + private static final int DN_LOCALE_VARIANT = 3; + private static final int DN_CURRENCY_CODE = 4; + private static final int DN_CURRENCY_SYMBOL = 5; + + // TimeZone display name types + private static final int DN_TZ_SHORT_STANDARD = 0; + private static final int DN_TZ_SHORT_DST = 1; + private static final int DN_TZ_LONG_STANDARD = 2; + private static final int DN_TZ_LONG_DST = 3; + + private static final Set supportedLocaleSet; + static { + Set tmpSet = new HashSet(); + // 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) { + return new SimpleDateFormat(getDateTimePattern(style, -1, locale), + getCalendarLocale(locale)); + } + + @Override + public DateFormat getTimeInstance(int style, Locale locale) { + return new SimpleDateFormat(getDateTimePattern(-1, style, locale), + getCalendarLocale(locale)); + } + + @Override + public DateFormat getDateTimeInstance(int dateStyle, + int timeStyle, Locale locale) { + return new SimpleDateFormat(getDateTimePattern(dateStyle, timeStyle, locale), + getCalendarLocale(locale)); + } + + private String getDateTimePattern(int dateStyle, int timeStyle, Locale locale) { + AtomicReferenceArray dateFormatPatterns; + SoftReference> ref = dateFormatPatternsMap.get(locale); + + if (ref == null || (dateFormatPatterns = ref.get()) == null) { + dateFormatPatterns = new AtomicReferenceArray<>(5 * 5); + ref = new SoftReference<>(dateFormatPatterns); + dateFormatPatternsMap.put(locale, ref); + } + + int index = (dateStyle + 1) * 5 + timeStyle + 1; + String pattern = dateFormatPatterns.get(index); + if (pattern == null) { + String langTag = locale.toLanguageTag(); + pattern = translateDateFormatLetters(getCalendarID(langTag), + getDateTimePatternNative(dateStyle, timeStyle, langTag)); + if (!dateFormatPatterns.compareAndSet(index, null, pattern)) { + pattern = dateFormatPatterns.get(index); + } + } + + return pattern; + } + }; + } + + 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 + Locale base = locale.stripExtensions(); + if (supportedLocaleSet.contains(base)) { + return getCalendarID(locale.toLanguageTag()).equals("gregorian"); + } + return false; + } + + @Override + public DateFormatSymbols getInstance(Locale locale) { + DateFormatSymbols dateFormatSymbols; + SoftReference ref = dateFormatSymbolsMap.get(locale); + + if (ref == null || (dateFormatSymbols = ref.get()) == null) { + dateFormatSymbols = new DateFormatSymbols(locale); + String langTag = locale.toLanguageTag(); + dateFormatSymbols.setAmPmStrings(getAmPmStrings(langTag, dateFormatSymbols.getAmPmStrings())); + dateFormatSymbols.setEras(getEras(langTag, dateFormatSymbols.getEras())); + dateFormatSymbols.setMonths(getMonths(langTag, dateFormatSymbols.getMonths())); + dateFormatSymbols.setShortMonths(getShortMonths(langTag, dateFormatSymbols.getShortMonths())); + dateFormatSymbols.setWeekdays(getWeekdays(langTag, dateFormatSymbols.getWeekdays())); + dateFormatSymbols.setShortWeekdays(getShortWeekdays(langTag, dateFormatSymbols.getShortWeekdays())); + ref = new SoftReference<>(dateFormatSymbols); + dateFormatSymbolsMap.put(locale, ref); + } + return (DateFormatSymbols)dateFormatSymbols.clone(); + } + }; + } + + public static NumberFormatProvider getNumberFormatProvider() { + return new NumberFormatProvider() { + @Override + public Locale[] getAvailableLocales() { + return supportedLocale; + } + + @Override + public boolean isSupportedLocale(Locale locale) { + // Ignore the extensions for now + return supportedLocaleSet.contains(locale.stripExtensions()); + } + + @Override + public NumberFormat getCurrencyInstance(Locale locale) { + return new DecimalFormat(getNumberPattern(NF_CURRENCY, locale), + DecimalFormatSymbols.getInstance(locale)); + } + + @Override + public NumberFormat getIntegerInstance(Locale locale) { + return new DecimalFormat(getNumberPattern(NF_INTEGER, locale), + DecimalFormatSymbols.getInstance(locale)); + } + + @Override + public NumberFormat getNumberInstance(Locale locale) { + return new DecimalFormat(getNumberPattern(NF_NUMBER, locale), + DecimalFormatSymbols.getInstance(locale)); + } + + @Override + public NumberFormat getPercentInstance(Locale locale) { + return new DecimalFormat(getNumberPattern(NF_PERCENT, locale), + DecimalFormatSymbols.getInstance(locale)); + } + + private String getNumberPattern(int style, Locale locale) { + AtomicReferenceArray numberFormatPatterns; + SoftReference> ref = numberFormatPatternsMap.get(locale); + + if (ref == null || (numberFormatPatterns = ref.get()) == null) { + numberFormatPatterns = new AtomicReferenceArray<>(4); + ref = new SoftReference<>(numberFormatPatterns); + numberFormatPatternsMap.put(locale, ref); + } + + String pattern = numberFormatPatterns.get(style); + if (pattern == null) { + pattern = getNumberPatternNative(style, locale.toLanguageTag()); + if (!numberFormatPatterns.compareAndSet(style, null, pattern)) { + pattern = numberFormatPatterns.get(style); + } + } + + return pattern; + } + }; + } + + public static DecimalFormatSymbolsProvider getDecimalFormatSymbolsProvider() { + return new DecimalFormatSymbolsProvider() { + + @Override + public Locale[] getAvailableLocales() { + return supportedLocale; + } + + @Override + public boolean isSupportedLocale(Locale locale) { + // Ignore the extensions for now + return supportedLocaleSet.contains(locale.stripExtensions()); + } + + @Override + public DecimalFormatSymbols getInstance(Locale locale) { + DecimalFormatSymbols decimalFormatSymbols; + SoftReference ref = decimalFormatSymbolsMap.get(locale); + + if (ref == null || (decimalFormatSymbols = ref.get()) == null) { + decimalFormatSymbols = new DecimalFormatSymbols(locale); + String langTag = locale.toLanguageTag(); + + // DecimalFormatSymbols.setInternationalCurrencySymbol() has + // a side effect of setting the currency symbol as well. So + // the calling order is relevant here. + decimalFormatSymbols.setInternationalCurrencySymbol(getInternationalCurrencySymbol(langTag, decimalFormatSymbols.getInternationalCurrencySymbol())); + decimalFormatSymbols.setCurrencySymbol(getCurrencySymbol(langTag, decimalFormatSymbols.getCurrencySymbol())); + decimalFormatSymbols.setDecimalSeparator(getDecimalSeparator(langTag, decimalFormatSymbols.getDecimalSeparator())); + decimalFormatSymbols.setGroupingSeparator(getGroupingSeparator(langTag, decimalFormatSymbols.getGroupingSeparator())); + decimalFormatSymbols.setInfinity(getInfinity(langTag, decimalFormatSymbols.getInfinity())); + decimalFormatSymbols.setMinusSign(getMinusSign(langTag, decimalFormatSymbols.getMinusSign())); + decimalFormatSymbols.setMonetaryDecimalSeparator(getMonetaryDecimalSeparator(langTag, decimalFormatSymbols.getMonetaryDecimalSeparator())); + decimalFormatSymbols.setNaN(getNaN(langTag, decimalFormatSymbols.getNaN())); + decimalFormatSymbols.setPercent(getPercent(langTag, decimalFormatSymbols.getPercent())); + decimalFormatSymbols.setPerMill(getPerMill(langTag, decimalFormatSymbols.getPerMill())); + decimalFormatSymbols.setZeroDigit(getZeroDigit(langTag, decimalFormatSymbols.getZeroDigit())); + decimalFormatSymbols.setExponentSeparator(getExponentSeparator(langTag, decimalFormatSymbols.getExponentSeparator())); + ref = new SoftReference<>(decimalFormatSymbols); + decimalFormatSymbolsMap.put(locale, ref); + } + return (DecimalFormatSymbols)decimalFormatSymbols.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) { + return getCalendarInt(locale.toLanguageTag(), CD_FIRSTDAYOFWEEK); + } + + @Override + public int getMinimalDaysInFirstWeek(Locale locale) { + return getCalendarInt(locale.toLanguageTag(), CD_MINIMALDAYSINFIRSTWEEK); + } + }; + } + + public static CurrencyNameProvider getCurrencyNameProvider() { + return new CurrencyNameProvider() { + @Override + public Locale[] getAvailableLocales() { + return supportedLocale; + } + + @Override + public boolean isSupportedLocale(Locale locale) { + // Ignore the extensions for now + return supportedLocaleSet.contains(locale.stripExtensions()); + } + + @Override + public String getDisplayName(String code, Locale locale) { + return getDisplayString(locale.toLanguageTag(), DN_CURRENCY_CODE, code); + } + + @Override + public String getSymbol(String code, Locale locale) { + return getDisplayString(locale.toLanguageTag(), DN_CURRENCY_SYMBOL, code); + } + }; + } + + public static LocaleNameProvider getLocaleNameProvider() { + return new LocaleNameProvider() { + @Override + public Locale[] getAvailableLocales() { + return supportedLocale; + } + + @Override + public boolean isSupportedLocale(Locale locale) { + // Ignore the extensions for now + return supportedLocaleSet.contains(locale.stripExtensions()); + } + + @Override + public String getDisplayLanguage(String languageCode, Locale locale) { + return getDisplayString(locale.toLanguageTag(), DN_LOCALE_LANGUAGE, languageCode); + } + + @Override + public String getDisplayCountry(String countryCode, Locale locale) { + return getDisplayString(locale.toLanguageTag(), DN_LOCALE_REGION, countryCode); + } + + @Override + public String getDisplayScript(String scriptCode, Locale locale) { + return getDisplayString(locale.toLanguageTag(), DN_LOCALE_SCRIPT, scriptCode); + } + + @Override + public String getDisplayVariant(String variantCode, Locale locale) { + return getDisplayString(locale.toLanguageTag(), DN_LOCALE_VARIANT, variantCode); + } + }; + } + + public static TimeZoneNameProvider getTimeZoneNameProvider() { + return new TimeZoneNameProvider() { + @Override + public Locale[] getAvailableLocales() { + return supportedLocale; + } + + @Override + public boolean isSupportedLocale(Locale locale) { + // Ignore the extensions for now + return supportedLocaleSet.contains(locale.stripExtensions()); + } + + @Override + public String getDisplayName(String ID, boolean daylight, int style, Locale locale) { + return getTimeZoneDisplayString(locale.toLanguageTag(), style * 2 + (daylight ? 1 : 0), ID); + } + }; + } + + 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.replaceFirst("gregory", "gregorian").equals( + getCalendarID(locale.toLanguageTag())); + } + + private static boolean isJapaneseCalendar() { + return getCalendarID("ja-JP").equals("japanese"); + } + + private static Locale getCalendarLocale(Locale locale) { + Locale.Builder lb = new Locale.Builder().setLocale(locale); + String calid = getCalendarID(locale.toLanguageTag()); + switch (calid) { + case "gregorian": + calid = "gregory"; + // FALL THROUGH! + case "japanese": + case "buddhist": + lb.setUnicodeLocaleKeyword("ca", calid); + return lb.build(); + default: + return locale; + } + } + + // The following methods are copied from CLDRConverter build tool. + private static String translateDateFormatLetters(String calendarType, String cldrFormat) { + String pattern = cldrFormat; + int length = pattern.length(); + boolean inQuote = false; + StringBuilder jrePattern = new StringBuilder(length); + int count = 0; + char lastLetter = 0; + + for (int i = 0; i < length; i++) { + char c = pattern.charAt(i); + + if (c == '\'') { + // '' is treated as a single quote regardless of being + // in a quoted section. + if ((i + 1) < length) { + char nextc = pattern.charAt(i + 1); + if (nextc == '\'') { + i++; + if (count != 0) { + convert(calendarType, lastLetter, count, jrePattern); + lastLetter = 0; + count = 0; + } + jrePattern.append("''"); + continue; + } + } + if (!inQuote) { + if (count != 0) { + convert(calendarType, lastLetter, count, jrePattern); + lastLetter = 0; + count = 0; + } + inQuote = true; + } else { + inQuote = false; + } + jrePattern.append(c); + continue; + } + if (inQuote) { + jrePattern.append(c); + continue; + } + if (!(c >= 'a' && c <= 'z' || c >= 'A' && c <= 'Z')) { + if (count != 0) { + convert(calendarType, lastLetter, count, jrePattern); + lastLetter = 0; + count = 0; + } + jrePattern.append(c); + continue; + } + + if (lastLetter == 0 || lastLetter == c) { + lastLetter = c; + count++; + continue; + } + convert(calendarType, lastLetter, count, jrePattern); + lastLetter = c; + count = 1; + } + + if (count != 0) { + convert(calendarType, lastLetter, count, jrePattern); + } + if (cldrFormat.contentEquals(jrePattern)) { + return cldrFormat; + } + return jrePattern.toString(); + } + + private static void convert(String calendarType, char cldrLetter, int count, StringBuilder sb) { + switch (cldrLetter) { + case 'G': + if (!calendarType.equals("gregorian")) { + // Adjust the number of 'G's for JRE SimpleDateFormat + if (count == 5) { + // CLDR narrow -> JRE short + count = 1; + } else if (count == 1) { + // CLDR abbr -> JRE long + count = 4; + } + } + appendN(cldrLetter, count, sb); + break; + + // TODO: support 'c' and 'e' in JRE SimpleDateFormat + // Use 'u' and 'E' for now. + case 'c': + case 'e': + switch (count) { + case 1: + sb.append('u'); + break; + case 3: + case 4: + appendN('E', count, sb); + break; + case 5: + appendN('E', 3, sb); + break; + } + break; + + case 'v': + case 'V': + appendN('z', count, sb); + break; + + case 'Z': + if (count == 4 || count == 5) { + sb.append("XXX"); + } + break; + + case 'u': + case 'U': + case 'q': + case 'Q': + case 'l': + case 'g': + case 'j': + case 'A': + // Unsupported letter. Just append it within quotes + sb.append('\''); + sb.append(cldrLetter); + sb.append('\''); + break; + + default: + appendN(cldrLetter, count, sb); + break; + } + } + + private static void appendN(char c, int n, StringBuilder sb) { + for (int i = 0; i < n; i++) { + sb.append(c); + } + } + + // initialize + private static native String getDefaultLocale(int cat); + + // For DateFormatProvider + private static native String getDateTimePatternNative(int dateStyle, int timeStyle, String langtag); + private static native String getCalendarID(String langTag); + + // For NumberFormatProvider + private static native String getNumberPatternNative(int style, 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 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); + private static native String getExponentSeparator(String langTag, String exponent); + + // For CalendarDataProvider + private static native int getCalendarInt(String langTag, int type); + + // For Locale/CurrencyNameProvider + private static native String getDisplayString(String langTag, int key, String value); + + // For TimeZoneNameProvider + private static native String getTimeZoneDisplayString(String langTag, int style, String value); +}