Path: blob/master/src/java.base/share/classes/sun/util/locale/provider/LocaleResources.java
41161 views
/*1* Copyright (c) 2012, 2020, 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*/2425/*26* (C) Copyright Taligent, Inc. 1996, 1997 - All Rights Reserved27* (C) Copyright IBM Corp. 1996 - 1998 - All Rights Reserved28*29* The original version of this source code and documentation30* is copyrighted and owned by Taligent, Inc., a wholly-owned31* subsidiary of IBM. These materials are provided under terms32* of a License Agreement between Taligent and Sun. This technology33* is protected by multiple US and International patents.34*35* This notice and attribution to Taligent may not be removed.36* Taligent is a registered trademark of Taligent, Inc.37*38*/3940package sun.util.locale.provider;4142import java.lang.ref.ReferenceQueue;43import java.lang.ref.SoftReference;44import java.text.MessageFormat;45import java.text.NumberFormat;46import java.util.Arrays;47import java.util.Calendar;48import java.util.HashSet;49import java.util.LinkedHashSet;50import java.util.Locale;51import java.util.Objects;52import java.util.ResourceBundle;53import java.util.Set;54import java.util.TimeZone;55import java.util.concurrent.ConcurrentHashMap;56import java.util.concurrent.ConcurrentMap;57import sun.security.action.GetPropertyAction;58import sun.util.resources.LocaleData;59import sun.util.resources.OpenListResourceBundle;60import sun.util.resources.ParallelListResourceBundle;61import sun.util.resources.TimeZoneNamesBundle;6263/**64* Central accessor to locale-dependent resources for JRE/CLDR provider adapters.65*66* @author Masayoshi Okutsu67* @author Naoto Sato68*/69public class LocaleResources {7071private final Locale locale;72private final LocaleData localeData;73private final LocaleProviderAdapter.Type type;7475// Resource cache76private final ConcurrentMap<String, ResourceReference> cache = new ConcurrentHashMap<>();77private final ReferenceQueue<Object> referenceQueue = new ReferenceQueue<>();7879// cache key prefixes80private static final String BREAK_ITERATOR_INFO = "BII.";81private static final String CALENDAR_DATA = "CALD.";82private static final String COLLATION_DATA_CACHEKEY = "COLD";83private static final String DECIMAL_FORMAT_SYMBOLS_DATA_CACHEKEY = "DFSD";84private static final String CURRENCY_NAMES = "CN.";85private static final String LOCALE_NAMES = "LN.";86private static final String TIME_ZONE_NAMES = "TZN.";87private static final String ZONE_IDS_CACHEKEY = "ZID";88private static final String CALENDAR_NAMES = "CALN.";89private static final String NUMBER_PATTERNS_CACHEKEY = "NP";90private static final String COMPACT_NUMBER_PATTERNS_CACHEKEY = "CNP";91private static final String DATE_TIME_PATTERN = "DTP.";92private static final String RULES_CACHEKEY = "RULE";9394// TimeZoneNamesBundle exemplar city prefix95private static final String TZNB_EXCITY_PREFIX = "timezone.excity.";9697// null singleton cache value98private static final Object NULLOBJECT = new Object();99100LocaleResources(ResourceBundleBasedAdapter adapter, Locale locale) {101this.locale = locale;102this.localeData = adapter.getLocaleData();103type = ((LocaleProviderAdapter)adapter).getAdapterType();104}105106private void removeEmptyReferences() {107Object ref;108while ((ref = referenceQueue.poll()) != null) {109cache.remove(((ResourceReference)ref).getCacheKey());110}111}112113Object getBreakIteratorInfo(String key) {114Object biInfo;115String cacheKey = BREAK_ITERATOR_INFO + key;116117removeEmptyReferences();118ResourceReference data = cache.get(cacheKey);119if (data == null || ((biInfo = data.get()) == null)) {120biInfo = localeData.getBreakIteratorInfo(locale).getObject(key);121cache.put(cacheKey, new ResourceReference(cacheKey, biInfo, referenceQueue));122}123124return biInfo;125}126127byte[] getBreakIteratorResources(String key) {128return (byte[]) localeData.getBreakIteratorResources(locale).getObject(key);129}130131public String getCalendarData(String key) {132String caldata = "";133String cacheKey = CALENDAR_DATA + key;134135removeEmptyReferences();136137ResourceReference data = cache.get(cacheKey);138if (data == null || ((caldata = (String) data.get()) == null)) {139ResourceBundle rb = localeData.getCalendarData(locale);140if (rb.containsKey(key)) {141caldata = rb.getString(key);142}143144cache.put(cacheKey,145new ResourceReference(cacheKey, caldata, referenceQueue));146}147148return caldata;149}150151public String getCollationData() {152String key = "Rule";153String coldata = "";154155removeEmptyReferences();156ResourceReference data = cache.get(COLLATION_DATA_CACHEKEY);157if (data == null || ((coldata = (String) data.get()) == null)) {158ResourceBundle rb = localeData.getCollationData(locale);159if (rb.containsKey(key)) {160coldata = rb.getString(key);161}162cache.put(COLLATION_DATA_CACHEKEY,163new ResourceReference(COLLATION_DATA_CACHEKEY, coldata, referenceQueue));164}165166return coldata;167}168169public Object[] getDecimalFormatSymbolsData() {170Object[] dfsdata;171172removeEmptyReferences();173ResourceReference data = cache.get(DECIMAL_FORMAT_SYMBOLS_DATA_CACHEKEY);174if (data == null || ((dfsdata = (Object[]) data.get()) == null)) {175// Note that only dfsdata[0] is prepared here in this method. Other176// elements are provided by the caller, yet they are cached here.177ResourceBundle rb = localeData.getNumberFormatData(locale);178dfsdata = new Object[3];179dfsdata[0] = getNumberStrings(rb, "NumberElements");180181cache.put(DECIMAL_FORMAT_SYMBOLS_DATA_CACHEKEY,182new ResourceReference(DECIMAL_FORMAT_SYMBOLS_DATA_CACHEKEY, dfsdata, referenceQueue));183}184185return dfsdata;186}187188private String[] getNumberStrings(ResourceBundle rb, String type) {189String[] ret = null;190String key;191String numSys;192193// Number strings look up. First, try the Unicode extension194numSys = locale.getUnicodeLocaleType("nu");195if (numSys != null) {196key = numSys + "." + type;197if (rb.containsKey(key)) {198ret = rb.getStringArray(key);199}200}201202// Next, try DefaultNumberingSystem value203if (ret == null && rb.containsKey("DefaultNumberingSystem")) {204key = rb.getString("DefaultNumberingSystem") + "." + type;205if (rb.containsKey(key)) {206ret = rb.getStringArray(key);207}208}209210// Last resort. No need to check the availability.211// Just let it throw MissingResourceException when needed.212if (ret == null) {213ret = rb.getStringArray(type);214}215216return ret;217}218219public String getCurrencyName(String key) {220Object currencyName = null;221String cacheKey = CURRENCY_NAMES + key;222223removeEmptyReferences();224ResourceReference data = cache.get(cacheKey);225226if (data != null && ((currencyName = data.get()) != null)) {227if (currencyName.equals(NULLOBJECT)) {228currencyName = null;229}230231return (String) currencyName;232}233234OpenListResourceBundle olrb = localeData.getCurrencyNames(locale);235236if (olrb.containsKey(key)) {237currencyName = olrb.getObject(key);238cache.put(cacheKey,239new ResourceReference(cacheKey, currencyName, referenceQueue));240}241242return (String) currencyName;243}244245public String getLocaleName(String key) {246Object localeName = null;247String cacheKey = LOCALE_NAMES + key;248249removeEmptyReferences();250ResourceReference data = cache.get(cacheKey);251252if (data != null && ((localeName = data.get()) != null)) {253if (localeName.equals(NULLOBJECT)) {254localeName = null;255}256257return (String) localeName;258}259260OpenListResourceBundle olrb = localeData.getLocaleNames(locale);261262if (olrb.containsKey(key)) {263localeName = olrb.getObject(key);264cache.put(cacheKey,265new ResourceReference(cacheKey, localeName, referenceQueue));266}267268return (String) localeName;269}270271public Object getTimeZoneNames(String key) {272Object val = null;273String cacheKey = TIME_ZONE_NAMES + key;274275removeEmptyReferences();276ResourceReference data = cache.get(cacheKey);277278if (Objects.isNull(data) || Objects.isNull(val = data.get())) {279TimeZoneNamesBundle tznb = localeData.getTimeZoneNames(locale);280if (key.startsWith(TZNB_EXCITY_PREFIX)) {281if (tznb.containsKey(key)) {282val = tznb.getString(key);283assert val instanceof String;284trace("tznb: %s key: %s, val: %s\n", tznb, key, val);285}286} else {287String[] names = null;288if (tznb.containsKey(key)) {289names = tznb.getStringArray(key);290} else {291var tz = TimeZoneNameUtility.canonicalTZID(key).orElse(key);292if (tznb.containsKey(tz)) {293names = tznb.getStringArray(tz);294}295}296297if (names != null) {298names[0] = key;299trace("tznb: %s key: %s, names: %s, %s, %s, %s, %s, %s, %s\n", tznb, key,300names[0], names[1], names[2], names[3], names[4], names[5], names[6]);301val = names;302}303}304if (val != null) {305cache.put(cacheKey,306new ResourceReference(cacheKey, val, referenceQueue));307}308}309310return val;311}312313@SuppressWarnings("unchecked")314Set<String> getZoneIDs() {315Set<String> zoneIDs;316317removeEmptyReferences();318ResourceReference data = cache.get(ZONE_IDS_CACHEKEY);319if (data == null || ((zoneIDs = (Set<String>) data.get()) == null)) {320TimeZoneNamesBundle rb = localeData.getTimeZoneNames(locale);321zoneIDs = rb.keySet();322cache.put(ZONE_IDS_CACHEKEY,323new ResourceReference(ZONE_IDS_CACHEKEY, zoneIDs, referenceQueue));324}325326return zoneIDs;327}328329// zoneStrings are cached separately in TimeZoneNameUtility.330String[][] getZoneStrings() {331TimeZoneNamesBundle rb = localeData.getTimeZoneNames(locale);332Set<String> keyset = getZoneIDs();333// Use a LinkedHashSet to preseve the order334Set<String[]> value = new LinkedHashSet<>();335Set<String> tzIds = new HashSet<>(Arrays.asList(TimeZone.getAvailableIDs()));336for (String key : keyset) {337if (!key.startsWith(TZNB_EXCITY_PREFIX)) {338value.add(rb.getStringArray(key));339tzIds.remove(key);340}341}342343if (type == LocaleProviderAdapter.Type.CLDR) {344// Note: TimeZoneNamesBundle creates a String[] on each getStringArray call.345346// Add timezones which are not present in this keyset,347// so that their fallback names will be generated at runtime.348tzIds.stream().filter(i -> (!i.startsWith("Etc/GMT")349&& !i.startsWith("GMT")350&& !i.startsWith("SystemV")))351.forEach(tzid -> {352String[] val = new String[7];353if (keyset.contains(tzid)) {354val = rb.getStringArray(tzid);355} else {356var canonID = TimeZoneNameUtility.canonicalTZID(tzid)357.orElse(tzid);358if (keyset.contains(canonID)) {359val = rb.getStringArray(canonID);360}361}362val[0] = tzid;363value.add(val);364});365}366return value.toArray(new String[0][]);367}368369String[] getCalendarNames(String key) {370String[] names = null;371String cacheKey = CALENDAR_NAMES + key;372373removeEmptyReferences();374ResourceReference data = cache.get(cacheKey);375376if (data == null || ((names = (String[]) data.get()) == null)) {377ResourceBundle rb = localeData.getDateFormatData(locale);378if (rb.containsKey(key)) {379names = rb.getStringArray(key);380cache.put(cacheKey,381new ResourceReference(cacheKey, names, referenceQueue));382}383}384385return names;386}387388String[] getJavaTimeNames(String key) {389String[] names = null;390String cacheKey = CALENDAR_NAMES + key;391392removeEmptyReferences();393ResourceReference data = cache.get(cacheKey);394395if (data == null || ((names = (String[]) data.get()) == null)) {396ResourceBundle rb = getJavaTimeFormatData();397if (rb.containsKey(key)) {398names = rb.getStringArray(key);399cache.put(cacheKey,400new ResourceReference(cacheKey, names, referenceQueue));401}402}403404return names;405}406407public String getDateTimePattern(int timeStyle, int dateStyle, Calendar cal) {408if (cal == null) {409cal = Calendar.getInstance(locale);410}411return getDateTimePattern(null, timeStyle, dateStyle, cal.getCalendarType());412}413414/**415* Returns a date-time format pattern416* @param timeStyle style of time; one of FULL, LONG, MEDIUM, SHORT in DateFormat,417* or -1 if not required418* @param dateStyle style of time; one of FULL, LONG, MEDIUM, SHORT in DateFormat,419* or -1 if not required420* @param calType the calendar type for the pattern421* @return the pattern string422*/423public String getJavaTimeDateTimePattern(int timeStyle, int dateStyle, String calType) {424calType = CalendarDataUtility.normalizeCalendarType(calType);425String pattern;426pattern = getDateTimePattern("java.time.", timeStyle, dateStyle, calType);427if (pattern == null) {428pattern = getDateTimePattern(null, timeStyle, dateStyle, calType);429}430return pattern;431}432433private String getDateTimePattern(String prefix, int timeStyle, int dateStyle, String calType) {434String pattern;435String timePattern = null;436String datePattern = null;437438if (timeStyle >= 0) {439if (prefix != null) {440timePattern = getDateTimePattern(prefix, "TimePatterns", timeStyle, calType);441}442if (timePattern == null) {443timePattern = getDateTimePattern(null, "TimePatterns", timeStyle, calType);444}445}446if (dateStyle >= 0) {447if (prefix != null) {448datePattern = getDateTimePattern(prefix, "DatePatterns", dateStyle, calType);449}450if (datePattern == null) {451datePattern = getDateTimePattern(null, "DatePatterns", dateStyle, calType);452}453}454if (timeStyle >= 0) {455if (dateStyle >= 0) {456String dateTimePattern = null;457int dateTimeStyle = Math.max(dateStyle, timeStyle);458if (prefix != null) {459dateTimePattern = getDateTimePattern(prefix, "DateTimePatterns", dateTimeStyle, calType);460}461if (dateTimePattern == null) {462dateTimePattern = getDateTimePattern(null, "DateTimePatterns", dateTimeStyle, calType);463}464pattern = switch (Objects.requireNonNull(dateTimePattern)) {465case "{1} {0}" -> datePattern + " " + timePattern;466case "{0} {1}" -> timePattern + " " + datePattern;467default -> MessageFormat.format(dateTimePattern.replaceAll("'", "''"), timePattern, datePattern);468};469} else {470pattern = timePattern;471}472} else if (dateStyle >= 0) {473pattern = datePattern;474} else {475throw new IllegalArgumentException("No date or time style specified");476}477return pattern;478}479480public String[] getNumberPatterns() {481String[] numberPatterns;482483removeEmptyReferences();484ResourceReference data = cache.get(NUMBER_PATTERNS_CACHEKEY);485486if (data == null || ((numberPatterns = (String[]) data.get()) == null)) {487ResourceBundle resource = localeData.getNumberFormatData(locale);488numberPatterns = getNumberStrings(resource, "NumberPatterns");489cache.put(NUMBER_PATTERNS_CACHEKEY,490new ResourceReference(NUMBER_PATTERNS_CACHEKEY, numberPatterns, referenceQueue));491}492493return numberPatterns;494}495496/**497* Returns the compact number format patterns.498* @param formatStyle the style for formatting a number499* @return an array of compact number patterns500*/501public String[] getCNPatterns(NumberFormat.Style formatStyle) {502503Objects.requireNonNull(formatStyle);504String[] compactNumberPatterns;505removeEmptyReferences();506String width = (formatStyle == NumberFormat.Style.LONG) ? "long" : "short";507String cacheKey = width + "." + COMPACT_NUMBER_PATTERNS_CACHEKEY;508ResourceReference data = cache.get(cacheKey);509if (data == null || ((compactNumberPatterns510= (String[]) data.get()) == null)) {511ResourceBundle resource = localeData.getNumberFormatData(locale);512compactNumberPatterns = (String[]) resource513.getObject(width + ".CompactNumberPatterns");514cache.put(cacheKey, new ResourceReference(cacheKey, compactNumberPatterns, referenceQueue));515}516return compactNumberPatterns;517}518519520/**521* Returns the FormatData resource bundle of this LocaleResources.522* The FormatData should be used only for accessing extra523* resources required by JSR 310.524*/525public ResourceBundle getJavaTimeFormatData() {526ResourceBundle rb = localeData.getDateFormatData(locale);527if (rb instanceof ParallelListResourceBundle) {528localeData.setSupplementary((ParallelListResourceBundle) rb);529}530return rb;531}532533private String getDateTimePattern(String prefix, String key, int styleIndex, String calendarType) {534StringBuilder sb = new StringBuilder();535if (prefix != null) {536sb.append(prefix);537}538if (!"gregory".equals(calendarType)) {539sb.append(calendarType).append('.');540}541sb.append(key);542String resourceKey = sb.toString();543String cacheKey = sb.insert(0, DATE_TIME_PATTERN).toString();544545removeEmptyReferences();546ResourceReference data = cache.get(cacheKey);547Object value = NULLOBJECT;548549if (data == null || ((value = data.get()) == null)) {550ResourceBundle r = (prefix != null) ? getJavaTimeFormatData() : localeData.getDateFormatData(locale);551if (r.containsKey(resourceKey)) {552value = r.getStringArray(resourceKey);553} else {554assert !resourceKey.equals(key);555if (r.containsKey(key)) {556value = r.getStringArray(key);557}558}559cache.put(cacheKey,560new ResourceReference(cacheKey, value, referenceQueue));561}562if (value == NULLOBJECT) {563assert prefix != null;564return null;565}566567// for DateTimePatterns. CLDR has multiple styles, while JRE has one.568String[] styles = (String[])value;569return (styles.length > 1 ? styles[styleIndex] : styles[0]);570}571572public String[] getRules() {573String[] rules;574575removeEmptyReferences();576ResourceReference data = cache.get(RULES_CACHEKEY);577578if (data == null || ((rules = (String[]) data.get()) == null)) {579ResourceBundle rb = localeData.getDateFormatData(locale);580rules = new String[2];581rules[0] = rules[1] = "";582if (rb.containsKey("PluralRules")) {583rules[0] = rb.getString("PluralRules");584}585if (rb.containsKey("DayPeriodRules")) {586rules[1] = rb.getString("DayPeriodRules");587}588cache.put(RULES_CACHEKEY, new ResourceReference(RULES_CACHEKEY, rules, referenceQueue));589}590591return rules;592}593594private static class ResourceReference extends SoftReference<Object> {595private final String cacheKey;596597ResourceReference(String cacheKey, Object o, ReferenceQueue<Object> q) {598super(o, q);599this.cacheKey = cacheKey;600}601602String getCacheKey() {603return cacheKey;604}605}606607private static final boolean TRACE_ON = Boolean.valueOf(608GetPropertyAction.privilegedGetProperty("locale.resources.debug", "false"));609610public static void trace(String format, Object... params) {611if (TRACE_ON) {612System.out.format(format, params);613}614}615}616617618