|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
|
|
/* |
|
* (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.HashSet; |
|
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.TimeZone; |
|
import java.util.concurrent.ConcurrentHashMap; |
|
import java.util.concurrent.ConcurrentMap; |
|
import sun.security.action.GetPropertyAction; |
|
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; |
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
public class LocaleResources { |
|
|
|
private final Locale locale; |
|
private final LocaleData localeData; |
|
private final LocaleProviderAdapter.Type type; |
|
|
|
|
|
private final ConcurrentMap<String, ResourceReference> cache = new ConcurrentHashMap<>(); |
|
private final ReferenceQueue<Object> referenceQueue = new ReferenceQueue<>(); |
|
|
|
|
|
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."; |
|
|
|
|
|
private static final String TZNB_EXCITY_PREFIX = "timezone.excity."; |
|
|
|
|
|
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); |
|
} |
|
|
|
public String getCalendarData(String key) { |
|
String caldata = ""; |
|
String cacheKey = CALENDAR_DATA + key; |
|
|
|
removeEmptyReferences(); |
|
|
|
ResourceReference data = cache.get(cacheKey); |
|
if (data == null || ((caldata = (String) data.get()) == null)) { |
|
ResourceBundle rb = localeData.getCalendarData(locale); |
|
if (rb.containsKey(key)) { |
|
caldata = rb.getString(key); |
|
} |
|
|
|
cache.put(cacheKey, |
|
new ResourceReference(cacheKey, 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 |
|
|
|
ResourceBundle rb = localeData.getNumberFormatData(locale); |
|
dfsdata = new Object[3]; |
|
|
|
|
|
String numElemKey; |
|
String numberType = locale.getUnicodeLocaleType("nu"); |
|
if (numberType != null) { |
|
numElemKey = numberType + ".NumberElements"; |
|
if (rb.containsKey(numElemKey)) { |
|
dfsdata[0] = rb.getStringArray(numElemKey); |
|
} |
|
} |
|
|
|
|
|
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. |
|
|
|
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; |
|
} |
|
|
|
public Object getTimeZoneNames(String key) { |
|
Object val = null; |
|
String cacheKey = TIME_ZONE_NAMES + key; |
|
|
|
removeEmptyReferences(); |
|
ResourceReference data = cache.get(cacheKey); |
|
|
|
if (Objects.isNull(data) || Objects.isNull(val = data.get())) { |
|
TimeZoneNamesBundle tznb = localeData.getTimeZoneNames(locale); |
|
if (tznb.containsKey(key)) { |
|
if (key.startsWith(TZNB_EXCITY_PREFIX)) { |
|
val = tznb.getString(key); |
|
assert val instanceof String; |
|
trace("tznb: %s key: %s, val: %s\n", tznb, key, val); |
|
} else { |
|
String[] names = tznb.getStringArray(key); |
|
trace("tznb: %s key: %s, names: %s, %s, %s, %s, %s, %s, %s\n", tznb, key, |
|
names[0], names[1], names[2], names[3], names[4], names[5], names[6]); |
|
val = names; |
|
} |
|
cache.put(cacheKey, |
|
new ResourceReference(cacheKey, val, referenceQueue)); |
|
} |
|
} |
|
|
|
return val; |
|
} |
|
|
|
@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; |
|
} |
|
|
|
|
|
String[][] getZoneStrings() { |
|
TimeZoneNamesBundle rb = localeData.getTimeZoneNames(locale); |
|
Set<String> keyset = getZoneIDs(); |
|
|
|
Set<String[]> value = new LinkedHashSet<>(); |
|
Set<String> tzIds = new HashSet<>(Set.of(TimeZone.getAvailableIDs())); |
|
for (String key : keyset) { |
|
if (!key.startsWith(TZNB_EXCITY_PREFIX)) { |
|
value.add(rb.getStringArray(key)); |
|
tzIds.remove(key); |
|
} |
|
} |
|
|
|
if (type == LocaleProviderAdapter.Type.CLDR) { |
|
|
|
Map<String, String> aliases = ZoneInfo.getAliasTable(); |
|
// Note: TimeZoneNamesBundle creates a String[] on each getStringArray call. |
|
|
|
// Add timezones which are not present in this keyset, |
|
|
|
tzIds.stream().filter(i -> (!i.startsWith("Etc/GMT") |
|
&& !i.startsWith("GMT") |
|
&& !i.startsWith("SystemV"))) |
|
.forEach(tzid -> { |
|
String[] val = new String[7]; |
|
if (keyset.contains(tzid)) { |
|
val = rb.getStringArray(tzid); |
|
} else { |
|
String tz = aliases.get(tzid); |
|
if (keyset.contains(tz)) { |
|
val = rb.getStringArray(tz); |
|
} |
|
} |
|
val[0] = tzid; |
|
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()); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
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; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
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; |
|
} |
|
|
|
|
|
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; |
|
} |
|
} |
|
|
|
private static final boolean TRACE_ON = Boolean.valueOf( |
|
GetPropertyAction.privilegedGetProperty("locale.resources.debug", "false")); |
|
|
|
public static void trace(String format, Object... params) { |
|
if (TRACE_ON) { |
|
System.out.format(format, params); |
|
} |
|
} |
|
} |