Path: blob/master/src/java.base/macosx/classes/sun/util/locale/provider/HostLocaleProviderAdapterImpl.java
41139 views
/*1* Copyright (c) 2012, 2019, 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*/2425package sun.util.locale.provider;2627import java.lang.ref.SoftReference;28import java.text.*;29import java.text.spi.DateFormatProvider;30import java.text.spi.DateFormatSymbolsProvider;31import java.text.spi.DecimalFormatSymbolsProvider;32import java.text.spi.NumberFormatProvider;33import java.util.Collections;34import java.util.Calendar;35import java.util.HashMap;36import java.util.HashSet;37import java.util.Locale;38import java.util.Map;39import java.util.ResourceBundle.Control;40import java.util.Set;41import java.util.TimeZone;42import java.util.concurrent.ConcurrentHashMap;43import java.util.concurrent.ConcurrentMap;44import java.util.concurrent.atomic.AtomicReferenceArray;45import java.util.spi.CalendarDataProvider;46import java.util.spi.CalendarNameProvider;47import java.util.spi.CurrencyNameProvider;48import java.util.spi.LocaleNameProvider;49import java.util.spi.TimeZoneNameProvider;50import sun.text.spi.JavaTimeDateTimePatternProvider;51import sun.util.spi.CalendarProvider;5253/**54* LocaleProviderAdapter implementation for the Mac OS X locale data55*56* @author Naoto Sato57*/58public class HostLocaleProviderAdapterImpl {5960// per supported locale instances61private static final ConcurrentMap<Locale, SoftReference<AtomicReferenceArray<String>>> dateFormatPatternsMap =62new ConcurrentHashMap<>(2);63private static final ConcurrentMap<Locale, SoftReference<AtomicReferenceArray<String>>> numberFormatPatternsMap =64new ConcurrentHashMap<>(2);65private static final ConcurrentMap<Locale, SoftReference<DateFormatSymbols>> dateFormatSymbolsMap =66new ConcurrentHashMap<>(2);67private static final ConcurrentMap<Locale, SoftReference<DecimalFormatSymbols>> decimalFormatSymbolsMap =68new ConcurrentHashMap<>(2);6970// locale categories71private static final int CAT_DISPLAY = 0;72private static final int CAT_FORMAT = 1;7374// NumberFormat styles75private static final int NF_NUMBER = 0;76private static final int NF_CURRENCY = 1;77private static final int NF_PERCENT = 2;78private static final int NF_INTEGER = 3;79private static final int NF_MAX = NF_INTEGER;8081// CalendarData value types82private static final int CD_FIRSTDAYOFWEEK = 0;83private static final int CD_MINIMALDAYSINFIRSTWEEK = 1;8485// Locale/Currency display name types86private static final int DN_LOCALE_LANGUAGE = 0;87private static final int DN_LOCALE_SCRIPT = 1;88private static final int DN_LOCALE_REGION = 2;89private static final int DN_LOCALE_VARIANT = 3;90private static final int DN_CURRENCY_CODE = 4;91private static final int DN_CURRENCY_SYMBOL = 5;9293// TimeZone display name types94private static final int DN_TZ_SHORT_STANDARD = 0;95private static final int DN_TZ_SHORT_DST = 1;96private static final int DN_TZ_LONG_STANDARD = 2;97private static final int DN_TZ_LONG_DST = 3;9899private static final Set<Locale> supportedLocaleSet;100static {101Set<Locale> tmpSet = new HashSet<>();102// Assuming the default locales do not include any extensions, so103// no stripping is needed here.104Locale l = convertMacOSXLocaleToJavaLocale(getDefaultLocale(CAT_FORMAT));105tmpSet.addAll(Control.getNoFallbackControl(Control.FORMAT_DEFAULT).getCandidateLocales("", l));106l = convertMacOSXLocaleToJavaLocale(getDefaultLocale(CAT_DISPLAY));107tmpSet.addAll(Control.getNoFallbackControl(Control.FORMAT_DEFAULT).getCandidateLocales("", l));108supportedLocaleSet = Collections.unmodifiableSet(tmpSet);109}110private static final Locale[] supportedLocale = supportedLocaleSet.toArray(new Locale[0]);111112@SuppressWarnings("fallthrough")113private static Locale convertMacOSXLocaleToJavaLocale(String macosxloc) {114// MacOSX may return ICU notation, here is the quote from CFLocale doc:115// "The corresponding value is a CFString containing the POSIX locale116// identifier as used by ICU, such as "ja_JP". If you have a variant117// locale or a different currency or calendar, it can be as complex as118// "en_US_POSIX@calendar=japanese;currency=EUR" or119// "az_Cyrl_AZ@calendar=buddhist;currency=JPY".120String[] tmp = macosxloc.split("@");121String langTag = tmp[0].replace('_', '-');122if (tmp.length > 1) {123String[] ext = tmp[1].split(";");124for (String keyval : ext) {125// We are only interested in "calendar" value for now.126if (keyval.startsWith("calendar=")) {127String calid = keyval.substring(keyval.indexOf('=')+1);128switch (calid) {129case "gregorian":130langTag += "-u-ca-gregory";131break;132case "japanese":133// Tweak for ja_JP_JP134if (tmp[0].equals("ja_JP")) {135return JRELocaleConstants.JA_JP_JP;136}137138// fall through139140default:141langTag += "-u-ca-" + calid;142break;143}144}145}146}147148return Locale.forLanguageTag(langTag);149}150151public static JavaTimeDateTimePatternProvider getJavaTimeDateTimePatternProvider() {152return new JavaTimeDateTimePatternProvider() {153@Override154public Locale[] getAvailableLocales() {155return getSupportedCalendarLocales();156}157158@Override159public boolean isSupportedLocale(Locale locale) {160return isSupportedCalendarLocale(locale);161}162163@Override164public String getJavaTimeDateTimePattern(int timeStyle, int dateStyle, String calType, Locale locale) {165return toJavaTimeDateTimePattern(calType, getDateTimePattern(dateStyle, timeStyle, locale));166167}168169private String getDateTimePattern(int dateStyle, int timeStyle, Locale locale) {170AtomicReferenceArray<String> dateFormatPatterns;171SoftReference<AtomicReferenceArray<String>> ref = dateFormatPatternsMap.get(locale);172173if (ref == null || (dateFormatPatterns = ref.get()) == null) {174dateFormatPatterns = new AtomicReferenceArray<>(5 * 5);175ref = new SoftReference<>(dateFormatPatterns);176dateFormatPatternsMap.put(locale, ref);177}178int index = (dateStyle + 1) * 5 + timeStyle + 1;179String pattern = dateFormatPatterns.get(index);180if (pattern == null) {181String langTag = locale.toLanguageTag();182pattern = translateDateFormatLetters(getCalendarID(langTag),183getDateTimePatternNative(dateStyle, timeStyle, langTag));184if (!dateFormatPatterns.compareAndSet(index, null, pattern)) {185pattern = dateFormatPatterns.get(index);186}187}188return pattern;189}190191/**192* This method will convert JRE Date/time Pattern String to JSR310193* type Date/Time Pattern194*/195private String toJavaTimeDateTimePattern(String calendarType, String jrePattern) {196int length = jrePattern.length();197StringBuilder sb = new StringBuilder(length);198boolean inQuote = false;199int count = 0;200char lastLetter = 0;201for (int i = 0; i < length; i++) {202char c = jrePattern.charAt(i);203if (c == '\'') {204// '' is treated as a single quote regardless of being205// in a quoted section.206if ((i + 1) < length) {207char nextc = jrePattern.charAt(i + 1);208if (nextc == '\'') {209i++;210if (count != 0) {211convert(calendarType, lastLetter, count, sb);212lastLetter = 0;213count = 0;214}215sb.append("''");216continue;217}218}219if (!inQuote) {220if (count != 0) {221convert(calendarType, lastLetter, count, sb);222lastLetter = 0;223count = 0;224}225inQuote = true;226} else {227inQuote = false;228}229sb.append(c);230continue;231}232if (inQuote) {233sb.append(c);234continue;235}236if (!(c >= 'a' && c <= 'z' || c >= 'A' && c <= 'Z')) {237if (count != 0) {238convert(calendarType, lastLetter, count, sb);239lastLetter = 0;240count = 0;241}242sb.append(c);243continue;244}245if (lastLetter == 0 || lastLetter == c) {246lastLetter = c;247count++;248continue;249}250convert(calendarType, lastLetter, count, sb);251lastLetter = c;252count = 1;253}254if (inQuote) {255// should not come here.256// returning null so that FALLBACK provider will kick in.257return null;258}259if (count != 0) {260convert(calendarType, lastLetter, count, sb);261}262return sb.toString();263}264265private void convert(String calendarType, char letter, int count, StringBuilder sb) {266switch (letter) {267case 'G':268if (calendarType.equals("japanese")) {269if (count >= 4) {270count = 1;271} else {272count = 5;273}274} else if (!calendarType.equals("iso8601")) {275// Gregorian calendar is iso8601 for java.time276// Adjust the number of 'G's277if (count >= 4) {278// JRE full -> JavaTime full279count = 4;280} else {281// JRE short -> JavaTime short282count = 1;283}284}285break;286case 'y':287if (calendarType.equals("japanese") && count >= 4) {288// JRE specific "gan-nen" support289count = 1;290}291break;292default:293// JSR 310 and CLDR define 5-letter patterns for narrow text.294if (count > 4) {295count = 4;296}297break;298}299appendN(letter, count, sb);300}301302private void appendN(char c, int n, StringBuilder sb) {303for (int i = 0; i < n; i++) {304sb.append(c);305}306}307};308}309310public static DateFormatProvider getDateFormatProvider() {311return new DateFormatProvider() {312313@Override314public Locale[] getAvailableLocales() {315return getSupportedCalendarLocales();316}317318@Override319public boolean isSupportedLocale(Locale locale) {320return isSupportedCalendarLocale(locale);321}322323@Override324public DateFormat getDateInstance(int style, Locale locale) {325return new SimpleDateFormat(getDateTimePattern(style, -1, locale),326getCalendarLocale(locale));327}328329@Override330public DateFormat getTimeInstance(int style, Locale locale) {331return new SimpleDateFormat(getDateTimePattern(-1, style, locale),332getCalendarLocale(locale));333}334335@Override336public DateFormat getDateTimeInstance(int dateStyle,337int timeStyle, Locale locale) {338return new SimpleDateFormat(getDateTimePattern(dateStyle, timeStyle, locale),339getCalendarLocale(locale));340}341342private String getDateTimePattern(int dateStyle, int timeStyle, Locale locale) {343AtomicReferenceArray<String> dateFormatPatterns;344SoftReference<AtomicReferenceArray<String>> ref = dateFormatPatternsMap.get(locale);345346if (ref == null || (dateFormatPatterns = ref.get()) == null) {347dateFormatPatterns = new AtomicReferenceArray<>(5 * 5);348ref = new SoftReference<>(dateFormatPatterns);349dateFormatPatternsMap.put(locale, ref);350}351352int index = (dateStyle + 1) * 5 + timeStyle + 1;353String pattern = dateFormatPatterns.get(index);354if (pattern == null) {355String langTag = locale.toLanguageTag();356pattern = translateDateFormatLetters(getCalendarID(langTag),357getDateTimePatternNative(dateStyle, timeStyle, langTag));358if (!dateFormatPatterns.compareAndSet(index, null, pattern)) {359pattern = dateFormatPatterns.get(index);360}361}362363return pattern;364}365};366}367368public static DateFormatSymbolsProvider getDateFormatSymbolsProvider() {369return new DateFormatSymbolsProvider() {370@Override371public Locale[] getAvailableLocales() {372if (isSupportedLocale(Locale.getDefault(Locale.Category.FORMAT))) {373return supportedLocale;374}375return new Locale[0];376}377378@Override379public boolean isSupportedLocale(Locale locale) {380// Only supports the locale with Gregorian calendar381Locale base = locale.stripExtensions();382if (supportedLocaleSet.contains(base)) {383return getCalendarID(locale.toLanguageTag()).equals("gregorian");384}385return false;386}387388@Override389public DateFormatSymbols getInstance(Locale locale) {390DateFormatSymbols dateFormatSymbols;391SoftReference<DateFormatSymbols> ref = dateFormatSymbolsMap.get(locale);392393if (ref == null || (dateFormatSymbols = ref.get()) == null) {394dateFormatSymbols = new DateFormatSymbols(locale);395String langTag = locale.toLanguageTag();396dateFormatSymbols.setAmPmStrings(getAmPmStrings(langTag, dateFormatSymbols.getAmPmStrings()));397dateFormatSymbols.setEras(getEras(langTag, dateFormatSymbols.getEras()));398dateFormatSymbols.setMonths(getMonths(langTag, dateFormatSymbols.getMonths()));399dateFormatSymbols.setShortMonths(getShortMonths(langTag, dateFormatSymbols.getShortMonths()));400dateFormatSymbols.setWeekdays(getWeekdays(langTag, dateFormatSymbols.getWeekdays()));401dateFormatSymbols.setShortWeekdays(getShortWeekdays(langTag, dateFormatSymbols.getShortWeekdays()));402ref = new SoftReference<>(dateFormatSymbols);403dateFormatSymbolsMap.put(locale, ref);404}405return (DateFormatSymbols)dateFormatSymbols.clone();406}407};408}409410public static NumberFormatProvider getNumberFormatProvider() {411return new NumberFormatProvider() {412@Override413public Locale[] getAvailableLocales() {414return supportedLocale;415}416417@Override418public boolean isSupportedLocale(Locale locale) {419// Ignore the extensions for now420return supportedLocaleSet.contains(locale.stripExtensions());421}422423@Override424public NumberFormat getCurrencyInstance(Locale locale) {425return new DecimalFormat(getNumberPattern(NF_CURRENCY, locale),426DecimalFormatSymbols.getInstance(locale));427}428429@Override430public NumberFormat getIntegerInstance(Locale locale) {431DecimalFormat format = new DecimalFormat(getNumberPattern(NF_INTEGER, locale),432DecimalFormatSymbols.getInstance(locale));433return HostLocaleProviderAdapter.makeIntegerFormatter(format);434}435436@Override437public NumberFormat getNumberInstance(Locale locale) {438return new DecimalFormat(getNumberPattern(NF_NUMBER, locale),439DecimalFormatSymbols.getInstance(locale));440}441442@Override443public NumberFormat getPercentInstance(Locale locale) {444return new DecimalFormat(getNumberPattern(NF_PERCENT, locale),445DecimalFormatSymbols.getInstance(locale));446}447448private String getNumberPattern(int style, Locale locale) {449AtomicReferenceArray<String> numberFormatPatterns;450SoftReference<AtomicReferenceArray<String>> ref = numberFormatPatternsMap.get(locale);451452if (ref == null || (numberFormatPatterns = ref.get()) == null) {453numberFormatPatterns = new AtomicReferenceArray<>(4);454ref = new SoftReference<>(numberFormatPatterns);455numberFormatPatternsMap.put(locale, ref);456}457458String pattern = numberFormatPatterns.get(style);459if (pattern == null) {460pattern = getNumberPatternNative(style, locale.toLanguageTag());461if (!numberFormatPatterns.compareAndSet(style, null, pattern)) {462pattern = numberFormatPatterns.get(style);463}464}465466return pattern;467}468};469}470471public static DecimalFormatSymbolsProvider getDecimalFormatSymbolsProvider() {472return new DecimalFormatSymbolsProvider() {473474@Override475public Locale[] getAvailableLocales() {476return supportedLocale;477}478479@Override480public boolean isSupportedLocale(Locale locale) {481// Ignore the extensions for now482return supportedLocaleSet.contains(locale.stripExtensions());483}484485@Override486public DecimalFormatSymbols getInstance(Locale locale) {487DecimalFormatSymbols decimalFormatSymbols;488SoftReference<DecimalFormatSymbols> ref = decimalFormatSymbolsMap.get(locale);489490if (ref == null || (decimalFormatSymbols = ref.get()) == null) {491decimalFormatSymbols = new DecimalFormatSymbols(locale);492String langTag = locale.toLanguageTag();493494// DecimalFormatSymbols.setInternationalCurrencySymbol() has495// a side effect of setting the currency symbol as well. So496// the calling order is relevant here.497decimalFormatSymbols.setInternationalCurrencySymbol(getInternationalCurrencySymbol(langTag, decimalFormatSymbols.getInternationalCurrencySymbol()));498decimalFormatSymbols.setCurrencySymbol(getCurrencySymbol(langTag, decimalFormatSymbols.getCurrencySymbol()));499decimalFormatSymbols.setDecimalSeparator(getDecimalSeparator(langTag, decimalFormatSymbols.getDecimalSeparator()));500decimalFormatSymbols.setGroupingSeparator(getGroupingSeparator(langTag, decimalFormatSymbols.getGroupingSeparator()));501decimalFormatSymbols.setInfinity(getInfinity(langTag, decimalFormatSymbols.getInfinity()));502decimalFormatSymbols.setMinusSign(getMinusSign(langTag, decimalFormatSymbols.getMinusSign()));503decimalFormatSymbols.setMonetaryDecimalSeparator(getMonetaryDecimalSeparator(langTag, decimalFormatSymbols.getMonetaryDecimalSeparator()));504decimalFormatSymbols.setNaN(getNaN(langTag, decimalFormatSymbols.getNaN()));505decimalFormatSymbols.setPercent(getPercent(langTag, decimalFormatSymbols.getPercent()));506decimalFormatSymbols.setPerMill(getPerMill(langTag, decimalFormatSymbols.getPerMill()));507decimalFormatSymbols.setZeroDigit(getZeroDigit(langTag, decimalFormatSymbols.getZeroDigit()));508decimalFormatSymbols.setExponentSeparator(getExponentSeparator(langTag, decimalFormatSymbols.getExponentSeparator()));509ref = new SoftReference<>(decimalFormatSymbols);510decimalFormatSymbolsMap.put(locale, ref);511}512return (DecimalFormatSymbols)decimalFormatSymbols.clone();513}514};515}516517public static CalendarDataProvider getCalendarDataProvider() {518return new CalendarDataProvider() {519@Override520public Locale[] getAvailableLocales() {521return getSupportedCalendarLocales();522}523524@Override525public boolean isSupportedLocale(Locale locale) {526return isSupportedCalendarLocale(locale);527}528529@Override530public int getFirstDayOfWeek(Locale locale) {531return getCalendarInt(locale.toLanguageTag(), CD_FIRSTDAYOFWEEK);532}533534@Override535public int getMinimalDaysInFirstWeek(Locale locale) {536return getCalendarInt(locale.toLanguageTag(), CD_MINIMALDAYSINFIRSTWEEK);537}538};539}540541public static CalendarNameProvider getCalendarNameProvider() {542return new CalendarNameProvider() {543@Override544public Locale[] getAvailableLocales() {545return getSupportedCalendarLocales();546}547548@Override549public boolean isSupportedLocale(Locale locale) {550return isSupportedCalendarLocale(locale);551}552553@Override554public String getDisplayName(String calendarType, int field,555int value, int style, Locale locale) {556String[] names = getCalendarDisplayStrings(locale.toLanguageTag(),557field, style);558if (names != null && value >= 0 && value < names.length) {559return names[value];560} else {561return null;562}563}564565@Override566public Map<String, Integer> getDisplayNames(String calendarType,567int field, int style, Locale locale) {568Map<String, Integer> map = null;569String[] names = getCalendarDisplayStrings(locale.toLanguageTag(),570field, style);571if (names != null) {572map = new HashMap<>((int)Math.ceil(names.length / 0.75));573for (int value = 0; value < names.length; value++) {574if (names[value] != null) {575map.put(names[value], value);576}577}578map = map.isEmpty() ? null : map;579}580return map;581}582};583}584585public static CalendarProvider getCalendarProvider() {586return new CalendarProvider() {587@Override588public Locale[] getAvailableLocales() {589return getSupportedCalendarLocales();590}591592@Override593public boolean isSupportedLocale(Locale locale) {594return isSupportedCalendarLocale(locale);595}596597@Override598public Calendar getInstance(TimeZone zone, Locale locale) {599return new Calendar.Builder()600.setLocale(locale)601.setCalendarType(getCalendarID(locale.toLanguageTag()))602.setTimeZone(zone)603.setInstant(System.currentTimeMillis())604.build();605}606};607}608609public static CurrencyNameProvider getCurrencyNameProvider() {610return new CurrencyNameProvider() {611@Override612public Locale[] getAvailableLocales() {613return supportedLocale;614}615616@Override617public boolean isSupportedLocale(Locale locale) {618// Ignore the extensions for now619return supportedLocaleSet.contains(locale.stripExtensions());620}621622@Override623public String getDisplayName(String code, Locale locale) {624return getDisplayString(locale.toLanguageTag(), DN_CURRENCY_CODE, code);625}626627@Override628public String getSymbol(String code, Locale locale) {629return getDisplayString(locale.toLanguageTag(), DN_CURRENCY_SYMBOL, code);630}631};632}633634public static LocaleNameProvider getLocaleNameProvider() {635return new LocaleNameProvider() {636@Override637public Locale[] getAvailableLocales() {638return supportedLocale;639}640641@Override642public boolean isSupportedLocale(Locale locale) {643// Ignore the extensions for now644return supportedLocaleSet.contains(locale.stripExtensions());645}646647@Override648public String getDisplayLanguage(String languageCode, Locale locale) {649return getDisplayString(locale.toLanguageTag(), DN_LOCALE_LANGUAGE, languageCode);650}651652@Override653public String getDisplayCountry(String countryCode, Locale locale) {654return getDisplayString(locale.toLanguageTag(), DN_LOCALE_REGION, countryCode);655}656657@Override658public String getDisplayScript(String scriptCode, Locale locale) {659return getDisplayString(locale.toLanguageTag(), DN_LOCALE_SCRIPT, scriptCode);660}661662@Override663public String getDisplayVariant(String variantCode, Locale locale) {664return getDisplayString(locale.toLanguageTag(), DN_LOCALE_VARIANT, variantCode);665}666};667}668669public static TimeZoneNameProvider getTimeZoneNameProvider() {670return new TimeZoneNameProvider() {671@Override672public Locale[] getAvailableLocales() {673return supportedLocale;674}675676@Override677public boolean isSupportedLocale(Locale locale) {678// Ignore the extensions for now679return supportedLocaleSet.contains(locale.stripExtensions());680}681682@Override683public String getDisplayName(String ID, boolean daylight, int style, Locale locale) {684return getTimeZoneDisplayString(locale.toLanguageTag(), style * 2 + (daylight ? 1 : 0), ID);685}686};687}688689private static Locale[] getSupportedCalendarLocales() {690if (supportedLocale.length != 0 &&691supportedLocaleSet.contains(Locale.JAPAN) &&692isJapaneseCalendar()) {693Locale[] sup = new Locale[supportedLocale.length+1];694sup[0] = JRELocaleConstants.JA_JP_JP;695System.arraycopy(supportedLocale, 0, sup, 1, supportedLocale.length);696return sup;697}698return supportedLocale;699}700701private static boolean isSupportedCalendarLocale(Locale locale) {702Locale base = locale;703704if (base.hasExtensions() || base.getVariant() != "") {705base = new Locale.Builder()706.setLocale(locale)707.clearExtensions()708.build();709}710711if (!supportedLocaleSet.contains(base)) {712return false;713}714715String requestedCalType = locale.getUnicodeLocaleType("ca");716String nativeCalType =717getCalendarID(base.toLanguageTag()).replaceFirst("gregorian", "gregory");718719if (requestedCalType == null) {720return Calendar.getAvailableCalendarTypes().contains(nativeCalType);721} else {722return requestedCalType.equals(nativeCalType);723}724}725726private static boolean isJapaneseCalendar() {727return getCalendarID("ja-JP").equals("japanese");728}729730private static Locale getCalendarLocale(Locale locale) {731String nativeCalType = getCalendarID(locale.toLanguageTag())732.replaceFirst("gregorian", "gregory");733if (Calendar.getAvailableCalendarTypes().contains(nativeCalType)) {734return new Locale.Builder()735.setLocale(locale)736.setUnicodeLocaleKeyword("ca", nativeCalType)737.build();738} else {739return locale;740}741}742743// The following methods are copied from CLDRConverter build tool.744private static String translateDateFormatLetters(String calendarType, String cldrFormat) {745String pattern = cldrFormat;746int length = pattern.length();747boolean inQuote = false;748StringBuilder jrePattern = new StringBuilder(length);749int count = 0;750char lastLetter = 0;751752for (int i = 0; i < length; i++) {753char c = pattern.charAt(i);754755if (c == '\'') {756// '' is treated as a single quote regardless of being757// in a quoted section.758if ((i + 1) < length) {759char nextc = pattern.charAt(i + 1);760if (nextc == '\'') {761i++;762if (count != 0) {763convert(calendarType, lastLetter, count, jrePattern);764lastLetter = 0;765count = 0;766}767jrePattern.append("''");768continue;769}770}771if (!inQuote) {772if (count != 0) {773convert(calendarType, lastLetter, count, jrePattern);774lastLetter = 0;775count = 0;776}777inQuote = true;778} else {779inQuote = false;780}781jrePattern.append(c);782continue;783}784if (inQuote) {785jrePattern.append(c);786continue;787}788if (!(c >= 'a' && c <= 'z' || c >= 'A' && c <= 'Z')) {789if (count != 0) {790convert(calendarType, lastLetter, count, jrePattern);791lastLetter = 0;792count = 0;793}794jrePattern.append(c);795continue;796}797798if (lastLetter == 0 || lastLetter == c) {799lastLetter = c;800count++;801continue;802}803convert(calendarType, lastLetter, count, jrePattern);804lastLetter = c;805count = 1;806}807808if (count != 0) {809convert(calendarType, lastLetter, count, jrePattern);810}811if (cldrFormat.contentEquals(jrePattern)) {812return cldrFormat;813}814return jrePattern.toString();815}816817private static void convert(String calendarType, char cldrLetter, int count, StringBuilder sb) {818switch (cldrLetter) {819case 'G':820if (!calendarType.equals("gregorian")) {821// Adjust the number of 'G's for JRE SimpleDateFormat822if (count == 5) {823// CLDR narrow -> JRE short824count = 1;825} else if (count == 1) {826// CLDR abbr -> JRE long827count = 4;828}829}830appendN(cldrLetter, count, sb);831break;832833// TODO: support 'c' and 'e' in JRE SimpleDateFormat834// Use 'u' and 'E' for now.835case 'c':836case 'e':837switch (count) {838case 1:839sb.append('u');840break;841case 3:842case 4:843appendN('E', count, sb);844break;845case 5:846appendN('E', 3, sb);847break;848}849break;850851case 'v':852case 'V':853appendN('z', count, sb);854break;855856case 'Z':857if (count == 4 || count == 5) {858sb.append("XXX");859}860break;861862case 'u':863case 'U':864case 'q':865case 'Q':866case 'l':867case 'g':868case 'j':869case 'A':870// Unsupported letter. Just append it within quotes871sb.append('\'');872sb.append(cldrLetter);873sb.append('\'');874break;875876default:877appendN(cldrLetter, count, sb);878break;879}880}881882private static void appendN(char c, int n, StringBuilder sb) {883for (int i = 0; i < n; i++) {884sb.append(c);885}886}887888// initialize889private static native String getDefaultLocale(int cat);890891// For DateFormatProvider892private static native String getDateTimePatternNative(int dateStyle, int timeStyle, String langtag);893private static native String getCalendarID(String langTag);894895// For NumberFormatProvider896private static native String getNumberPatternNative(int style, String langtag);897898// For DateFormatSymbolsProvider899private static native String[] getAmPmStrings(String langTag, String[] ampm);900private static native String[] getEras(String langTag, String[] eras);901private static native String[] getMonths(String langTag, String[] months);902private static native String[] getShortMonths(String langTag, String[] smonths);903private static native String[] getWeekdays(String langTag, String[] wdays);904private static native String[] getShortWeekdays(String langTag, String[] swdays);905906// For DecimalFormatSymbolsProvider907private static native String getCurrencySymbol(String langTag, String currencySymbol);908private static native char getDecimalSeparator(String langTag, char decimalSeparator);909private static native char getGroupingSeparator(String langTag, char groupingSeparator);910private static native String getInfinity(String langTag, String infinity);911private static native String getInternationalCurrencySymbol(String langTag, String internationalCurrencySymbol);912private static native char getMinusSign(String langTag, char minusSign);913private static native char getMonetaryDecimalSeparator(String langTag, char monetaryDecimalSeparator);914private static native String getNaN(String langTag, String nan);915private static native char getPercent(String langTag, char percent);916private static native char getPerMill(String langTag, char perMill);917private static native char getZeroDigit(String langTag, char zeroDigit);918private static native String getExponentSeparator(String langTag, String exponent);919920// For CalendarDataProvider921private static native int getCalendarInt(String langTag, int type);922923// For CalendarNameProvider924private static native String[] getCalendarDisplayStrings(String langTag, int field, int style);925926// For Locale/CurrencyNameProvider927private static native String getDisplayString(String langTag, int key, String value);928929// For TimeZoneNameProvider930private static native String getTimeZoneDisplayString(String langTag, int style, String value);931}932933934