| /* |
| * Copyright (c) 2012, 2016, 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. |
| */ |
| |
| /* |
| * (C) Copyright Taligent, Inc. 1996, 1997 - All Rights Reserved |
| * (C) Copyright IBM Corp. 1996 - 1998 - All Rights Reserved |
| * |
| * The original version of this source code and documentation |
| * is copyrighted and owned by Taligent, Inc., a wholly-owned |
| * subsidiary of IBM. These materials are provided under terms |
| * of a License Agreement between Taligent and Sun. This technology |
| * is protected by multiple US and International patents. |
| * |
| * This notice and attribution to Taligent may not be removed. |
| * Taligent is a registered trademark of Taligent, Inc. |
| * |
| */ |
| |
| package sun.util.locale.provider; |
| |
| import java.lang.ref.ReferenceQueue; |
| import java.lang.ref.SoftReference; |
| import java.text.MessageFormat; |
| import java.util.Calendar; |
| import java.util.LinkedHashSet; |
| import java.util.Locale; |
| import java.util.Map; |
| import java.util.Objects; |
| import java.util.ResourceBundle; |
| import java.util.Set; |
| import java.util.concurrent.ConcurrentHashMap; |
| import java.util.concurrent.ConcurrentMap; |
| import sun.util.calendar.ZoneInfo; |
| import sun.util.resources.LocaleData; |
| import sun.util.resources.OpenListResourceBundle; |
| import sun.util.resources.ParallelListResourceBundle; |
| import sun.util.resources.TimeZoneNamesBundle; |
| |
| /** |
| * Central accessor to locale-dependent resources for JRE/CLDR provider adapters. |
| * |
| * @author Masayoshi Okutsu |
| * @author Naoto Sato |
| */ |
| public class LocaleResources { |
| |
| private final Locale locale; |
| private final LocaleData localeData; |
| private final LocaleProviderAdapter.Type type; |
| |
| // Resource cache |
| private final ConcurrentMap<String, ResourceReference> cache = new ConcurrentHashMap<>(); |
| private final ReferenceQueue<Object> referenceQueue = new ReferenceQueue<>(); |
| |
| // cache key prefixes |
| private static final String BREAK_ITERATOR_INFO = "BII."; |
| private static final String CALENDAR_DATA = "CALD."; |
| private static final String COLLATION_DATA_CACHEKEY = "COLD"; |
| private static final String DECIMAL_FORMAT_SYMBOLS_DATA_CACHEKEY = "DFSD"; |
| private static final String CURRENCY_NAMES = "CN."; |
| private static final String LOCALE_NAMES = "LN."; |
| private static final String TIME_ZONE_NAMES = "TZN."; |
| private static final String ZONE_IDS_CACHEKEY = "ZID"; |
| private static final String CALENDAR_NAMES = "CALN."; |
| private static final String NUMBER_PATTERNS_CACHEKEY = "NP"; |
| private static final String DATE_TIME_PATTERN = "DTP."; |
| |
| // null singleton cache value |
| private static final Object NULLOBJECT = new Object(); |
| |
| LocaleResources(ResourceBundleBasedAdapter adapter, Locale locale) { |
| this.locale = locale; |
| this.localeData = adapter.getLocaleData(); |
| type = ((LocaleProviderAdapter)adapter).getAdapterType(); |
| } |
| |
| private void removeEmptyReferences() { |
| Object ref; |
| while ((ref = referenceQueue.poll()) != null) { |
| cache.remove(((ResourceReference)ref).getCacheKey()); |
| } |
| } |
| |
| Object getBreakIteratorInfo(String key) { |
| Object biInfo; |
| String cacheKey = BREAK_ITERATOR_INFO + key; |
| |
| removeEmptyReferences(); |
| ResourceReference data = cache.get(cacheKey); |
| if (data == null || ((biInfo = data.get()) == null)) { |
| biInfo = localeData.getBreakIteratorInfo(locale).getObject(key); |
| cache.put(cacheKey, new ResourceReference(cacheKey, biInfo, referenceQueue)); |
| } |
| |
| return biInfo; |
| } |
| |
| @SuppressWarnings("unchecked") |
| byte[] getBreakIteratorResources(String key) { |
| return (byte[]) localeData.getBreakIteratorResources(locale).getObject(key); |
| } |
| |
| int getCalendarData(String key) { |
| Integer caldata; |
| String cacheKey = CALENDAR_DATA + key; |
| |
| removeEmptyReferences(); |
| |
| ResourceReference data = cache.get(cacheKey); |
| if (data == null || ((caldata = (Integer) data.get()) == null)) { |
| ResourceBundle rb = localeData.getCalendarData(locale); |
| if (rb.containsKey(key)) { |
| caldata = Integer.parseInt(rb.getString(key)); |
| } else { |
| caldata = 0; |
| } |
| |
| cache.put(cacheKey, |
| new ResourceReference(cacheKey, (Object) caldata, referenceQueue)); |
| } |
| |
| return caldata; |
| } |
| |
| public String getCollationData() { |
| String key = "Rule"; |
| String coldata = ""; |
| |
| removeEmptyReferences(); |
| ResourceReference data = cache.get(COLLATION_DATA_CACHEKEY); |
| if (data == null || ((coldata = (String) data.get()) == null)) { |
| ResourceBundle rb = localeData.getCollationData(locale); |
| if (rb.containsKey(key)) { |
| coldata = rb.getString(key); |
| } |
| cache.put(COLLATION_DATA_CACHEKEY, |
| new ResourceReference(COLLATION_DATA_CACHEKEY, (Object) coldata, referenceQueue)); |
| } |
| |
| return coldata; |
| } |
| |
| public Object[] getDecimalFormatSymbolsData() { |
| Object[] dfsdata; |
| |
| removeEmptyReferences(); |
| ResourceReference data = cache.get(DECIMAL_FORMAT_SYMBOLS_DATA_CACHEKEY); |
| if (data == null || ((dfsdata = (Object[]) data.get()) == null)) { |
| // Note that only dfsdata[0] is prepared here in this method. Other |
| // elements are provided by the caller, yet they are cached here. |
| ResourceBundle rb = localeData.getNumberFormatData(locale); |
| dfsdata = new Object[3]; |
| |
| // NumberElements look up. First, try the Unicode extension |
| String numElemKey; |
| String numberType = locale.getUnicodeLocaleType("nu"); |
| if (numberType != null) { |
| numElemKey = numberType + ".NumberElements"; |
| if (rb.containsKey(numElemKey)) { |
| dfsdata[0] = rb.getStringArray(numElemKey); |
| } |
| } |
| |
| // Next, try DefaultNumberingSystem value |
| if (dfsdata[0] == null && rb.containsKey("DefaultNumberingSystem")) { |
| numElemKey = rb.getString("DefaultNumberingSystem") + ".NumberElements"; |
| if (rb.containsKey(numElemKey)) { |
| dfsdata[0] = rb.getStringArray(numElemKey); |
| } |
| } |
| |
| // Last resort. No need to check the availability. |
| // Just let it throw MissingResourceException when needed. |
| if (dfsdata[0] == null) { |
| dfsdata[0] = rb.getStringArray("NumberElements"); |
| } |
| |
| cache.put(DECIMAL_FORMAT_SYMBOLS_DATA_CACHEKEY, |
| new ResourceReference(DECIMAL_FORMAT_SYMBOLS_DATA_CACHEKEY, (Object) dfsdata, referenceQueue)); |
| } |
| |
| return dfsdata; |
| } |
| |
| public String getCurrencyName(String key) { |
| Object currencyName = null; |
| String cacheKey = CURRENCY_NAMES + key; |
| |
| removeEmptyReferences(); |
| ResourceReference data = cache.get(cacheKey); |
| |
| if (data != null && ((currencyName = data.get()) != null)) { |
| if (currencyName.equals(NULLOBJECT)) { |
| currencyName = null; |
| } |
| |
| return (String) currencyName; |
| } |
| |
| OpenListResourceBundle olrb = localeData.getCurrencyNames(locale); |
| |
| if (olrb.containsKey(key)) { |
| currencyName = olrb.getObject(key); |
| cache.put(cacheKey, |
| new ResourceReference(cacheKey, currencyName, referenceQueue)); |
| } |
| |
| return (String) currencyName; |
| } |
| |
| public String getLocaleName(String key) { |
| Object localeName = null; |
| String cacheKey = LOCALE_NAMES + key; |
| |
| removeEmptyReferences(); |
| ResourceReference data = cache.get(cacheKey); |
| |
| if (data != null && ((localeName = data.get()) != null)) { |
| if (localeName.equals(NULLOBJECT)) { |
| localeName = null; |
| } |
| |
| return (String) localeName; |
| } |
| |
| OpenListResourceBundle olrb = localeData.getLocaleNames(locale); |
| |
| if (olrb.containsKey(key)) { |
| localeName = olrb.getObject(key); |
| cache.put(cacheKey, |
| new ResourceReference(cacheKey, localeName, referenceQueue)); |
| } |
| |
| return (String) localeName; |
| } |
| |
| String[] getTimeZoneNames(String key) { |
| String[] names = null; |
| String cacheKey = TIME_ZONE_NAMES + '.' + key; |
| |
| removeEmptyReferences(); |
| ResourceReference data = cache.get(cacheKey); |
| |
| if (Objects.isNull(data) || Objects.isNull((names = (String[]) data.get()))) { |
| TimeZoneNamesBundle tznb = localeData.getTimeZoneNames(locale); |
| if (tznb.containsKey(key)) { |
| names = tznb.getStringArray(key); |
| cache.put(cacheKey, |
| new ResourceReference(cacheKey, (Object) names, referenceQueue)); |
| } |
| } |
| |
| return names; |
| } |
| |
| @SuppressWarnings("unchecked") |
| Set<String> getZoneIDs() { |
| Set<String> zoneIDs = null; |
| |
| removeEmptyReferences(); |
| ResourceReference data = cache.get(ZONE_IDS_CACHEKEY); |
| if (data == null || ((zoneIDs = (Set<String>) data.get()) == null)) { |
| TimeZoneNamesBundle rb = localeData.getTimeZoneNames(locale); |
| zoneIDs = rb.keySet(); |
| cache.put(ZONE_IDS_CACHEKEY, |
| new ResourceReference(ZONE_IDS_CACHEKEY, (Object) zoneIDs, referenceQueue)); |
| } |
| |
| return zoneIDs; |
| } |
| |
| // zoneStrings are cached separately in TimeZoneNameUtility. |
| String[][] getZoneStrings() { |
| TimeZoneNamesBundle rb = localeData.getTimeZoneNames(locale); |
| Set<String> keyset = getZoneIDs(); |
| // Use a LinkedHashSet to preseve the order |
| Set<String[]> value = new LinkedHashSet<>(); |
| for (String key : keyset) { |
| value.add(rb.getStringArray(key)); |
| } |
| |
| // Add aliases data for CLDR |
| if (type == LocaleProviderAdapter.Type.CLDR) { |
| // Note: TimeZoneNamesBundle creates a String[] on each getStringArray call. |
| Map<String, String> aliases = ZoneInfo.getAliasTable(); |
| for (String alias : aliases.keySet()) { |
| if (!keyset.contains(alias)) { |
| String tzid = aliases.get(alias); |
| if (keyset.contains(tzid)) { |
| String[] val = rb.getStringArray(tzid); |
| val[0] = alias; |
| value.add(val); |
| } |
| } |
| } |
| } |
| return value.toArray(new String[0][]); |
| } |
| |
| String[] getCalendarNames(String key) { |
| String[] names = null; |
| String cacheKey = CALENDAR_NAMES + key; |
| |
| removeEmptyReferences(); |
| ResourceReference data = cache.get(cacheKey); |
| |
| if (data == null || ((names = (String[]) data.get()) == null)) { |
| ResourceBundle rb = localeData.getDateFormatData(locale); |
| if (rb.containsKey(key)) { |
| names = rb.getStringArray(key); |
| cache.put(cacheKey, |
| new ResourceReference(cacheKey, (Object) names, referenceQueue)); |
| } |
| } |
| |
| return names; |
| } |
| |
| String[] getJavaTimeNames(String key) { |
| String[] names = null; |
| String cacheKey = CALENDAR_NAMES + key; |
| |
| removeEmptyReferences(); |
| ResourceReference data = cache.get(cacheKey); |
| |
| if (data == null || ((names = (String[]) data.get()) == null)) { |
| ResourceBundle rb = getJavaTimeFormatData(); |
| if (rb.containsKey(key)) { |
| names = rb.getStringArray(key); |
| cache.put(cacheKey, |
| new ResourceReference(cacheKey, (Object) names, referenceQueue)); |
| } |
| } |
| |
| return names; |
| } |
| |
| public String getDateTimePattern(int timeStyle, int dateStyle, Calendar cal) { |
| if (cal == null) { |
| cal = Calendar.getInstance(locale); |
| } |
| return getDateTimePattern(null, timeStyle, dateStyle, cal.getCalendarType()); |
| } |
| |
| /** |
| * Returns a date-time format pattern |
| * @param timeStyle style of time; one of FULL, LONG, MEDIUM, SHORT in DateFormat, |
| * or -1 if not required |
| * @param dateStyle style of time; one of FULL, LONG, MEDIUM, SHORT in DateFormat, |
| * or -1 if not required |
| * @param calType the calendar type for the pattern |
| * @return the pattern string |
| */ |
| public String getJavaTimeDateTimePattern(int timeStyle, int dateStyle, String calType) { |
| calType = CalendarDataUtility.normalizeCalendarType(calType); |
| String pattern; |
| pattern = getDateTimePattern("java.time.", timeStyle, dateStyle, calType); |
| if (pattern == null) { |
| pattern = getDateTimePattern(null, timeStyle, dateStyle, calType); |
| } |
| return pattern; |
| } |
| |
| private String getDateTimePattern(String prefix, int timeStyle, int dateStyle, String calType) { |
| String pattern; |
| String timePattern = null; |
| String datePattern = null; |
| |
| if (timeStyle >= 0) { |
| if (prefix != null) { |
| timePattern = getDateTimePattern(prefix, "TimePatterns", timeStyle, calType); |
| } |
| if (timePattern == null) { |
| timePattern = getDateTimePattern(null, "TimePatterns", timeStyle, calType); |
| } |
| } |
| if (dateStyle >= 0) { |
| if (prefix != null) { |
| datePattern = getDateTimePattern(prefix, "DatePatterns", dateStyle, calType); |
| } |
| if (datePattern == null) { |
| datePattern = getDateTimePattern(null, "DatePatterns", dateStyle, calType); |
| } |
| } |
| if (timeStyle >= 0) { |
| if (dateStyle >= 0) { |
| String dateTimePattern = null; |
| int dateTimeStyle = Math.max(dateStyle, timeStyle); |
| if (prefix != null) { |
| dateTimePattern = getDateTimePattern(prefix, "DateTimePatterns", dateTimeStyle, calType); |
| } |
| if (dateTimePattern == null) { |
| dateTimePattern = getDateTimePattern(null, "DateTimePatterns", dateTimeStyle, calType); |
| } |
| switch (dateTimePattern) { |
| case "{1} {0}": |
| pattern = datePattern + " " + timePattern; |
| break; |
| case "{0} {1}": |
| pattern = timePattern + " " + datePattern; |
| break; |
| default: |
| pattern = MessageFormat.format(dateTimePattern.replaceAll("'", "''"), timePattern, datePattern); |
| break; |
| } |
| } else { |
| pattern = timePattern; |
| } |
| } else if (dateStyle >= 0) { |
| pattern = datePattern; |
| } else { |
| throw new IllegalArgumentException("No date or time style specified"); |
| } |
| return pattern; |
| } |
| |
| public String[] getNumberPatterns() { |
| String[] numberPatterns = null; |
| |
| removeEmptyReferences(); |
| ResourceReference data = cache.get(NUMBER_PATTERNS_CACHEKEY); |
| |
| if (data == null || ((numberPatterns = (String[]) data.get()) == null)) { |
| ResourceBundle resource = localeData.getNumberFormatData(locale); |
| numberPatterns = resource.getStringArray("NumberPatterns"); |
| cache.put(NUMBER_PATTERNS_CACHEKEY, |
| new ResourceReference(NUMBER_PATTERNS_CACHEKEY, (Object) numberPatterns, referenceQueue)); |
| } |
| |
| return numberPatterns; |
| } |
| |
| /** |
| * Returns the FormatData resource bundle of this LocaleResources. |
| * The FormatData should be used only for accessing extra |
| * resources required by JSR 310. |
| */ |
| public ResourceBundle getJavaTimeFormatData() { |
| ResourceBundle rb = localeData.getDateFormatData(locale); |
| if (rb instanceof ParallelListResourceBundle) { |
| localeData.setSupplementary((ParallelListResourceBundle) rb); |
| } |
| return rb; |
| } |
| |
| private String getDateTimePattern(String prefix, String key, int styleIndex, String calendarType) { |
| StringBuilder sb = new StringBuilder(); |
| if (prefix != null) { |
| sb.append(prefix); |
| } |
| if (!"gregory".equals(calendarType)) { |
| sb.append(calendarType).append('.'); |
| } |
| sb.append(key); |
| String resourceKey = sb.toString(); |
| String cacheKey = sb.insert(0, DATE_TIME_PATTERN).toString(); |
| |
| removeEmptyReferences(); |
| ResourceReference data = cache.get(cacheKey); |
| Object value = NULLOBJECT; |
| |
| if (data == null || ((value = data.get()) == null)) { |
| ResourceBundle r = (prefix != null) ? getJavaTimeFormatData() : localeData.getDateFormatData(locale); |
| if (r.containsKey(resourceKey)) { |
| value = r.getStringArray(resourceKey); |
| } else { |
| assert !resourceKey.equals(key); |
| if (r.containsKey(key)) { |
| value = r.getStringArray(key); |
| } |
| } |
| cache.put(cacheKey, |
| new ResourceReference(cacheKey, value, referenceQueue)); |
| } |
| if (value == NULLOBJECT) { |
| assert prefix != null; |
| return null; |
| } |
| |
| // for DateTimePatterns. CLDR has multiple styles, while JRE has one. |
| String[] styles = (String[])value; |
| return (styles.length > 1 ? styles[styleIndex] : styles[0]); |
| } |
| |
| private static class ResourceReference extends SoftReference<Object> { |
| private final String cacheKey; |
| |
| ResourceReference(String cacheKey, Object o, ReferenceQueue<Object> q) { |
| super(o, q); |
| this.cacheKey = cacheKey; |
| } |
| |
| String getCacheKey() { |
| return cacheKey; |
| } |
| } |
| } |