1 /* 2 * Copyright (c) 2012, 2018, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 /* 27 * (C) Copyright Taligent, Inc. 1996, 1997 - All Rights Reserved 28 * (C) Copyright IBM Corp. 1996 - 1998 - All Rights Reserved 29 * 30 * The original version of this source code and documentation 31 * is copyrighted and owned by Taligent, Inc., a wholly-owned 32 * subsidiary of IBM. These materials are provided under terms 33 * of a License Agreement between Taligent and Sun. This technology 34 * is protected by multiple US and International patents. 35 * 36 * This notice and attribution to Taligent may not be removed. 37 * Taligent is a registered trademark of Taligent, Inc. 38 * 39 */ 40 41 package sun.util.locale.provider; 42 43 import java.lang.ref.ReferenceQueue; 44 import java.lang.ref.SoftReference; 45 import java.text.MessageFormat; 46 import java.util.Calendar; 47 import java.util.HashSet; 48 import java.util.LinkedHashSet; 49 import java.util.Locale; 50 import java.util.Map; 51 import java.util.Objects; 52 import java.util.ResourceBundle; 53 import java.util.Set; 54 import java.util.TimeZone; 55 import java.util.concurrent.ConcurrentHashMap; 56 import java.util.concurrent.ConcurrentMap; 57 import sun.security.action.GetPropertyAction; 58 import sun.util.calendar.ZoneInfo; 59 import sun.util.resources.LocaleData; 60 import sun.util.resources.OpenListResourceBundle; 61 import sun.util.resources.ParallelListResourceBundle; 62 import sun.util.resources.TimeZoneNamesBundle; 63 64 /** 65 * Central accessor to locale-dependent resources for JRE/CLDR provider adapters. 66 * 67 * @author Masayoshi Okutsu 68 * @author Naoto Sato 69 */ 70 public class LocaleResources { 71 72 private final Locale locale; 73 private final LocaleData localeData; 74 private final LocaleProviderAdapter.Type type; 75 76 // Resource cache 77 private final ConcurrentMap<String, ResourceReference> cache = new ConcurrentHashMap<>(); 78 private final ReferenceQueue<Object> referenceQueue = new ReferenceQueue<>(); 79 80 // cache key prefixes 81 private static final String BREAK_ITERATOR_INFO = "BII."; 82 private static final String CALENDAR_DATA = "CALD."; 83 private static final String COLLATION_DATA_CACHEKEY = "COLD"; 84 private static final String DECIMAL_FORMAT_SYMBOLS_DATA_CACHEKEY = "DFSD"; 85 private static final String CURRENCY_NAMES = "CN."; 86 private static final String LOCALE_NAMES = "LN."; 87 private static final String TIME_ZONE_NAMES = "TZN."; 88 private static final String ZONE_IDS_CACHEKEY = "ZID"; 89 private static final String CALENDAR_NAMES = "CALN."; 90 private static final String NUMBER_PATTERNS_CACHEKEY = "NP"; 91 private static final String DATE_TIME_PATTERN = "DTP."; 92 93 // TimeZoneNamesBundle exemplar city prefix 94 private static final String TZNB_EXCITY_PREFIX = "timezone.excity."; 95 96 // null singleton cache value 97 private static final Object NULLOBJECT = new Object(); 98 99 LocaleResources(ResourceBundleBasedAdapter adapter, Locale locale) { 100 this.locale = locale; 101 this.localeData = adapter.getLocaleData(); 102 type = ((LocaleProviderAdapter)adapter).getAdapterType(); 103 } 104 105 private void removeEmptyReferences() { 106 Object ref; 107 while ((ref = referenceQueue.poll()) != null) { 108 cache.remove(((ResourceReference)ref).getCacheKey()); 109 } 110 } 111 112 Object getBreakIteratorInfo(String key) { 113 Object biInfo; 114 String cacheKey = BREAK_ITERATOR_INFO + key; 115 116 removeEmptyReferences(); 117 ResourceReference data = cache.get(cacheKey); 118 if (data == null || ((biInfo = data.get()) == null)) { 119 biInfo = localeData.getBreakIteratorInfo(locale).getObject(key); 120 cache.put(cacheKey, new ResourceReference(cacheKey, biInfo, referenceQueue)); 121 } 122 123 return biInfo; 124 } 125 126 @SuppressWarnings("unchecked") 127 byte[] getBreakIteratorResources(String key) { 128 return (byte[]) localeData.getBreakIteratorResources(locale).getObject(key); 129 } 130 131 public String getCalendarData(String key) { 132 String caldata = ""; 133 String cacheKey = CALENDAR_DATA + key; 134 135 removeEmptyReferences(); 136 137 ResourceReference data = cache.get(cacheKey); 138 if (data == null || ((caldata = (String) data.get()) == null)) { 139 ResourceBundle rb = localeData.getCalendarData(locale); 140 if (rb.containsKey(key)) { 141 caldata = rb.getString(key); 142 } 143 144 cache.put(cacheKey, 145 new ResourceReference(cacheKey, caldata, referenceQueue)); 146 } 147 148 return caldata; 149 } 150 151 public String getCollationData() { 152 String key = "Rule"; 153 String coldata = ""; 154 155 removeEmptyReferences(); 156 ResourceReference data = cache.get(COLLATION_DATA_CACHEKEY); 157 if (data == null || ((coldata = (String) data.get()) == null)) { 158 ResourceBundle rb = localeData.getCollationData(locale); 159 if (rb.containsKey(key)) { 160 coldata = rb.getString(key); 161 } 162 cache.put(COLLATION_DATA_CACHEKEY, 163 new ResourceReference(COLLATION_DATA_CACHEKEY, (Object) coldata, referenceQueue)); 164 } 165 166 return coldata; 167 } 168 169 public Object[] getDecimalFormatSymbolsData() { 170 Object[] dfsdata; 171 172 removeEmptyReferences(); 173 ResourceReference data = cache.get(DECIMAL_FORMAT_SYMBOLS_DATA_CACHEKEY); 174 if (data == null || ((dfsdata = (Object[]) data.get()) == null)) { 175 // Note that only dfsdata[0] is prepared here in this method. Other 176 // elements are provided by the caller, yet they are cached here. 177 ResourceBundle rb = localeData.getNumberFormatData(locale); 178 dfsdata = new Object[3]; 179 180 // NumberElements look up. First, try the Unicode extension 181 String numElemKey; 182 String numberType = locale.getUnicodeLocaleType("nu"); 183 if (numberType != null) { 184 numElemKey = numberType + ".NumberElements"; 185 if (rb.containsKey(numElemKey)) { 186 dfsdata[0] = rb.getStringArray(numElemKey); 187 } 188 } 189 190 // Next, try DefaultNumberingSystem value 191 if (dfsdata[0] == null && rb.containsKey("DefaultNumberingSystem")) { 192 numElemKey = rb.getString("DefaultNumberingSystem") + ".NumberElements"; 193 if (rb.containsKey(numElemKey)) { 194 dfsdata[0] = rb.getStringArray(numElemKey); 195 } 196 } 197 198 // Last resort. No need to check the availability. 199 // Just let it throw MissingResourceException when needed. 200 if (dfsdata[0] == null) { 201 dfsdata[0] = rb.getStringArray("NumberElements"); 202 } 203 204 cache.put(DECIMAL_FORMAT_SYMBOLS_DATA_CACHEKEY, 205 new ResourceReference(DECIMAL_FORMAT_SYMBOLS_DATA_CACHEKEY, (Object) dfsdata, referenceQueue)); 206 } 207 208 return dfsdata; 209 } 210 211 public String getCurrencyName(String key) { 212 Object currencyName = null; 213 String cacheKey = CURRENCY_NAMES + key; 214 215 removeEmptyReferences(); 216 ResourceReference data = cache.get(cacheKey); 217 218 if (data != null && ((currencyName = data.get()) != null)) { 219 if (currencyName.equals(NULLOBJECT)) { 220 currencyName = null; 221 } 222 223 return (String) currencyName; 224 } 225 226 OpenListResourceBundle olrb = localeData.getCurrencyNames(locale); 227 228 if (olrb.containsKey(key)) { 229 currencyName = olrb.getObject(key); 230 cache.put(cacheKey, 231 new ResourceReference(cacheKey, currencyName, referenceQueue)); 232 } 233 234 return (String) currencyName; 235 } 236 237 public String getLocaleName(String key) { 238 Object localeName = null; 239 String cacheKey = LOCALE_NAMES + key; 240 241 removeEmptyReferences(); 242 ResourceReference data = cache.get(cacheKey); 243 244 if (data != null && ((localeName = data.get()) != null)) { 245 if (localeName.equals(NULLOBJECT)) { 246 localeName = null; 247 } 248 249 return (String) localeName; 250 } 251 252 OpenListResourceBundle olrb = localeData.getLocaleNames(locale); 253 254 if (olrb.containsKey(key)) { 255 localeName = olrb.getObject(key); 256 cache.put(cacheKey, 257 new ResourceReference(cacheKey, localeName, referenceQueue)); 258 } 259 260 return (String) localeName; 261 } 262 263 public Object getTimeZoneNames(String key) { 264 Object val = null; 265 String cacheKey = TIME_ZONE_NAMES + key; 266 267 removeEmptyReferences(); 268 ResourceReference data = cache.get(cacheKey); 269 270 if (Objects.isNull(data) || Objects.isNull(val = data.get())) { 271 TimeZoneNamesBundle tznb = localeData.getTimeZoneNames(locale); 272 if (tznb.containsKey(key)) { 273 if (key.startsWith(TZNB_EXCITY_PREFIX)) { 274 val = tznb.getString(key); 275 assert val instanceof String; 276 trace("tznb: %s key: %s, val: %s\n", tznb, key, val); 277 } else { 278 String[] names = tznb.getStringArray(key); 279 trace("tznb: %s key: %s, names: %s, %s, %s, %s, %s, %s, %s\n", tznb, key, 280 names[0], names[1], names[2], names[3], names[4], names[5], names[6]); 281 val = names; 282 } 283 cache.put(cacheKey, 284 new ResourceReference(cacheKey, val, referenceQueue)); 285 } 286 } 287 288 return val; 289 } 290 291 @SuppressWarnings("unchecked") 292 Set<String> getZoneIDs() { 293 Set<String> zoneIDs = null; 294 295 removeEmptyReferences(); 296 ResourceReference data = cache.get(ZONE_IDS_CACHEKEY); 297 if (data == null || ((zoneIDs = (Set<String>) data.get()) == null)) { 298 TimeZoneNamesBundle rb = localeData.getTimeZoneNames(locale); 299 zoneIDs = rb.keySet(); 300 cache.put(ZONE_IDS_CACHEKEY, 301 new ResourceReference(ZONE_IDS_CACHEKEY, (Object) zoneIDs, referenceQueue)); 302 } 303 304 return zoneIDs; 305 } 306 307 // zoneStrings are cached separately in TimeZoneNameUtility. 308 String[][] getZoneStrings() { 309 TimeZoneNamesBundle rb = localeData.getTimeZoneNames(locale); 310 Set<String> keyset = getZoneIDs(); 311 // Use a LinkedHashSet to preseve the order 312 Set<String[]> value = new LinkedHashSet<>(); 313 Set<String> tzIds = new HashSet<>(Set.of(TimeZone.getAvailableIDs())); 314 for (String key : keyset) { 315 if (!key.startsWith(TZNB_EXCITY_PREFIX)) { 316 value.add(rb.getStringArray(key)); 317 tzIds.remove(key); 318 } 319 } 320 321 if (type == LocaleProviderAdapter.Type.CLDR) { 322 // Add aliases data for CLDR 323 Map<String, String> aliases = ZoneInfo.getAliasTable(); 324 // Note: TimeZoneNamesBundle creates a String[] on each getStringArray call. 325 326 // Add timezones which are not present in this keyset, 327 // so that their fallback names will be generated at runtime. 328 tzIds.stream().filter(i -> (!i.startsWith("Etc/GMT") 329 && !i.startsWith("GMT") 330 && !i.startsWith("SystemV"))) 331 .forEach(tzid -> { 332 String[] val = new String[7]; 333 if (keyset.contains(tzid)) { 334 val = rb.getStringArray(tzid); 335 } else { 336 String tz = aliases.get(tzid); 337 if (keyset.contains(tz)) { 338 val = rb.getStringArray(tz); 339 } 340 } 341 val[0] = tzid; 342 value.add(val); 343 }); 344 } 345 return value.toArray(new String[0][]); 346 } 347 348 String[] getCalendarNames(String key) { 349 String[] names = null; 350 String cacheKey = CALENDAR_NAMES + key; 351 352 removeEmptyReferences(); 353 ResourceReference data = cache.get(cacheKey); 354 355 if (data == null || ((names = (String[]) data.get()) == null)) { 356 ResourceBundle rb = localeData.getDateFormatData(locale); 357 if (rb.containsKey(key)) { 358 names = rb.getStringArray(key); 359 cache.put(cacheKey, 360 new ResourceReference(cacheKey, (Object) names, referenceQueue)); 361 } 362 } 363 364 return names; 365 } 366 367 String[] getJavaTimeNames(String key) { 368 String[] names = null; 369 String cacheKey = CALENDAR_NAMES + key; 370 371 removeEmptyReferences(); 372 ResourceReference data = cache.get(cacheKey); 373 374 if (data == null || ((names = (String[]) data.get()) == null)) { 375 ResourceBundle rb = getJavaTimeFormatData(); 376 if (rb.containsKey(key)) { 377 names = rb.getStringArray(key); 378 cache.put(cacheKey, 379 new ResourceReference(cacheKey, (Object) names, referenceQueue)); 380 } 381 } 382 383 return names; 384 } 385 386 public String getDateTimePattern(int timeStyle, int dateStyle, Calendar cal) { 387 if (cal == null) { 388 cal = Calendar.getInstance(locale); 389 } 390 return getDateTimePattern(null, timeStyle, dateStyle, cal.getCalendarType()); 391 } 392 393 /** 394 * Returns a date-time format pattern 395 * @param timeStyle style of time; one of FULL, LONG, MEDIUM, SHORT in DateFormat, 396 * or -1 if not required 397 * @param dateStyle style of time; one of FULL, LONG, MEDIUM, SHORT in DateFormat, 398 * or -1 if not required 399 * @param calType the calendar type for the pattern 400 * @return the pattern string 401 */ 402 public String getJavaTimeDateTimePattern(int timeStyle, int dateStyle, String calType) { 403 calType = CalendarDataUtility.normalizeCalendarType(calType); 404 String pattern; 405 pattern = getDateTimePattern("java.time.", timeStyle, dateStyle, calType); 406 if (pattern == null) { 407 pattern = getDateTimePattern(null, timeStyle, dateStyle, calType); 408 } 409 return pattern; 410 } 411 412 private String getDateTimePattern(String prefix, int timeStyle, int dateStyle, String calType) { 413 String pattern; 414 String timePattern = null; 415 String datePattern = null; 416 417 if (timeStyle >= 0) { 418 if (prefix != null) { 419 timePattern = getDateTimePattern(prefix, "TimePatterns", timeStyle, calType); 420 } 421 if (timePattern == null) { 422 timePattern = getDateTimePattern(null, "TimePatterns", timeStyle, calType); 423 } 424 } 425 if (dateStyle >= 0) { 426 if (prefix != null) { 427 datePattern = getDateTimePattern(prefix, "DatePatterns", dateStyle, calType); 428 } 429 if (datePattern == null) { 430 datePattern = getDateTimePattern(null, "DatePatterns", dateStyle, calType); 431 } 432 } 433 if (timeStyle >= 0) { 434 if (dateStyle >= 0) { 435 String dateTimePattern = null; 436 int dateTimeStyle = Math.max(dateStyle, timeStyle); 437 if (prefix != null) { 438 dateTimePattern = getDateTimePattern(prefix, "DateTimePatterns", dateTimeStyle, calType); 439 } 440 if (dateTimePattern == null) { 441 dateTimePattern = getDateTimePattern(null, "DateTimePatterns", dateTimeStyle, calType); 442 } 443 switch (dateTimePattern) { 444 case "{1} {0}": 445 pattern = datePattern + " " + timePattern; 446 break; 447 case "{0} {1}": 448 pattern = timePattern + " " + datePattern; 449 break; 450 default: 451 pattern = MessageFormat.format(dateTimePattern.replaceAll("'", "''"), timePattern, datePattern); 452 break; 453 } 454 } else { 455 pattern = timePattern; 456 } 457 } else if (dateStyle >= 0) { 458 pattern = datePattern; 459 } else { 460 throw new IllegalArgumentException("No date or time style specified"); 461 } 462 return pattern; 463 } 464 465 public String[] getNumberPatterns() { 466 String[] numberPatterns = null; 467 468 removeEmptyReferences(); 469 ResourceReference data = cache.get(NUMBER_PATTERNS_CACHEKEY); 470 471 if (data == null || ((numberPatterns = (String[]) data.get()) == null)) { 472 ResourceBundle resource = localeData.getNumberFormatData(locale); 473 numberPatterns = resource.getStringArray("NumberPatterns"); 474 cache.put(NUMBER_PATTERNS_CACHEKEY, 475 new ResourceReference(NUMBER_PATTERNS_CACHEKEY, (Object) numberPatterns, referenceQueue)); 476 } 477 478 return numberPatterns; 479 } 480 481 /** 482 * Returns the FormatData resource bundle of this LocaleResources. 483 * The FormatData should be used only for accessing extra 484 * resources required by JSR 310. 485 */ 486 public ResourceBundle getJavaTimeFormatData() { 487 ResourceBundle rb = localeData.getDateFormatData(locale); 488 if (rb instanceof ParallelListResourceBundle) { 489 localeData.setSupplementary((ParallelListResourceBundle) rb); 490 } 491 return rb; 492 } 493 494 private String getDateTimePattern(String prefix, String key, int styleIndex, String calendarType) { 495 StringBuilder sb = new StringBuilder(); 496 if (prefix != null) { 497 sb.append(prefix); 498 } 499 if (!"gregory".equals(calendarType)) { 500 sb.append(calendarType).append('.'); 501 } 502 sb.append(key); 503 String resourceKey = sb.toString(); 504 String cacheKey = sb.insert(0, DATE_TIME_PATTERN).toString(); 505 506 removeEmptyReferences(); 507 ResourceReference data = cache.get(cacheKey); 508 Object value = NULLOBJECT; 509 510 if (data == null || ((value = data.get()) == null)) { 511 ResourceBundle r = (prefix != null) ? getJavaTimeFormatData() : localeData.getDateFormatData(locale); 512 if (r.containsKey(resourceKey)) { 513 value = r.getStringArray(resourceKey); 514 } else { 515 assert !resourceKey.equals(key); 516 if (r.containsKey(key)) { 517 value = r.getStringArray(key); 518 } 519 } 520 cache.put(cacheKey, 521 new ResourceReference(cacheKey, value, referenceQueue)); 522 } 523 if (value == NULLOBJECT) { 524 assert prefix != null; 525 return null; 526 } 527 528 // for DateTimePatterns. CLDR has multiple styles, while JRE has one. 529 String[] styles = (String[])value; 530 return (styles.length > 1 ? styles[styleIndex] : styles[0]); 531 } 532 533 private static class ResourceReference extends SoftReference<Object> { 534 private final String cacheKey; 535 536 ResourceReference(String cacheKey, Object o, ReferenceQueue<Object> q) { 537 super(o, q); 538 this.cacheKey = cacheKey; 539 } 540 541 String getCacheKey() { 542 return cacheKey; 543 } 544 } 545 546 private static final boolean TRACE_ON = Boolean.valueOf( 547 GetPropertyAction.privilegedGetProperty("locale.resources.debug", "false")); 548 549 public static void trace(String format, Object... params) { 550 if (TRACE_ON) { 551 System.out.format(format, params); 552 } 553 } 554 }