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.LinkedHashSet; 48 import java.util.Locale; 49 import java.util.Map; 50 import java.util.Objects; 51 import java.util.ResourceBundle; 52 import java.util.Set; 53 import java.util.concurrent.ConcurrentHashMap; 54 import java.util.concurrent.ConcurrentMap; 55 import sun.security.action.GetPropertyAction; 56 import sun.util.calendar.ZoneInfo; 57 import sun.util.resources.LocaleData; 58 import sun.util.resources.OpenListResourceBundle; 59 import sun.util.resources.ParallelListResourceBundle; 60 import sun.util.resources.TimeZoneNamesBundle; 61 62 /** 63 * Central accessor to locale-dependent resources for JRE/CLDR provider adapters. 64 * 65 * @author Masayoshi Okutsu 66 * @author Naoto Sato 67 */ 68 public class LocaleResources { 69 70 private final Locale locale; 71 private final LocaleData localeData; 72 private final LocaleProviderAdapter.Type type; 73 74 // Resource cache 75 private final ConcurrentMap<String, ResourceReference> cache = new ConcurrentHashMap<>(); 76 private final ReferenceQueue<Object> referenceQueue = new ReferenceQueue<>(); 77 78 // cache key prefixes 79 private static final String BREAK_ITERATOR_INFO = "BII."; 80 private static final String CALENDAR_DATA = "CALD."; 81 private static final String COLLATION_DATA_CACHEKEY = "COLD"; 82 private static final String DECIMAL_FORMAT_SYMBOLS_DATA_CACHEKEY = "DFSD"; 83 private static final String CURRENCY_NAMES = "CN."; 84 private static final String LOCALE_NAMES = "LN."; 85 private static final String TIME_ZONE_NAMES = "TZN."; 86 private static final String ZONE_IDS_CACHEKEY = "ZID"; 87 private static final String CALENDAR_NAMES = "CALN."; 88 private static final String NUMBER_PATTERNS_CACHEKEY = "NP"; 89 private static final String DATE_TIME_PATTERN = "DTP."; 90 91 // TimeZoneNamesBundle exemplar city prefix 92 private static final String TZNB_EXCITY_PREFIX = "timezone.excity."; 93 94 // null singleton cache value 95 private static final Object NULLOBJECT = new Object(); 96 97 LocaleResources(ResourceBundleBasedAdapter adapter, Locale locale) { 98 this.locale = locale; 99 this.localeData = adapter.getLocaleData(); 100 type = ((LocaleProviderAdapter)adapter).getAdapterType(); 101 } 102 103 private void removeEmptyReferences() { 104 Object ref; 105 while ((ref = referenceQueue.poll()) != null) { 106 cache.remove(((ResourceReference)ref).getCacheKey()); 107 } 108 } 109 110 Object getBreakIteratorInfo(String key) { 111 Object biInfo; 112 String cacheKey = BREAK_ITERATOR_INFO + key; 113 114 removeEmptyReferences(); 115 ResourceReference data = cache.get(cacheKey); 116 if (data == null || ((biInfo = data.get()) == null)) { 117 biInfo = localeData.getBreakIteratorInfo(locale).getObject(key); 118 cache.put(cacheKey, new ResourceReference(cacheKey, biInfo, referenceQueue)); 119 } 120 121 return biInfo; 122 } 123 124 @SuppressWarnings("unchecked") 125 byte[] getBreakIteratorResources(String key) { 126 return (byte[]) localeData.getBreakIteratorResources(locale).getObject(key); 127 } 128 129 public String getCalendarData(String key) { 130 String caldata = ""; 131 String cacheKey = CALENDAR_DATA + key; 132 133 removeEmptyReferences(); 134 135 ResourceReference data = cache.get(cacheKey); 136 if (data == null || ((caldata = (String) data.get()) == null)) { 137 ResourceBundle rb = localeData.getCalendarData(locale); 138 if (rb.containsKey(key)) { 139 caldata = rb.getString(key); 140 } 141 142 cache.put(cacheKey, 143 new ResourceReference(cacheKey, caldata, referenceQueue)); 144 } 145 146 return caldata; 147 } 148 149 public String getCollationData() { 150 String key = "Rule"; 151 String coldata = ""; 152 153 removeEmptyReferences(); 154 ResourceReference data = cache.get(COLLATION_DATA_CACHEKEY); 155 if (data == null || ((coldata = (String) data.get()) == null)) { 156 ResourceBundle rb = localeData.getCollationData(locale); 157 if (rb.containsKey(key)) { 158 coldata = rb.getString(key); 159 } 160 cache.put(COLLATION_DATA_CACHEKEY, 161 new ResourceReference(COLLATION_DATA_CACHEKEY, (Object) coldata, referenceQueue)); 162 } 163 164 return coldata; 165 } 166 167 public Object[] getDecimalFormatSymbolsData() { 168 Object[] dfsdata; 169 170 removeEmptyReferences(); 171 ResourceReference data = cache.get(DECIMAL_FORMAT_SYMBOLS_DATA_CACHEKEY); 172 if (data == null || ((dfsdata = (Object[]) data.get()) == null)) { 173 // Note that only dfsdata[0] is prepared here in this method. Other 174 // elements are provided by the caller, yet they are cached here. 175 ResourceBundle rb = localeData.getNumberFormatData(locale); 176 dfsdata = new Object[3]; 177 178 // NumberElements look up. First, try the Unicode extension 179 String numElemKey; 180 String numberType = locale.getUnicodeLocaleType("nu"); 181 if (numberType != null) { 182 numElemKey = numberType + ".NumberElements"; 183 if (rb.containsKey(numElemKey)) { 184 dfsdata[0] = rb.getStringArray(numElemKey); 185 } 186 } 187 188 // Next, try DefaultNumberingSystem value 189 if (dfsdata[0] == null && rb.containsKey("DefaultNumberingSystem")) { 190 numElemKey = rb.getString("DefaultNumberingSystem") + ".NumberElements"; 191 if (rb.containsKey(numElemKey)) { 192 dfsdata[0] = rb.getStringArray(numElemKey); 193 } 194 } 195 196 // Last resort. No need to check the availability. 197 // Just let it throw MissingResourceException when needed. 198 if (dfsdata[0] == null) { 199 dfsdata[0] = rb.getStringArray("NumberElements"); 200 } 201 202 cache.put(DECIMAL_FORMAT_SYMBOLS_DATA_CACHEKEY, 203 new ResourceReference(DECIMAL_FORMAT_SYMBOLS_DATA_CACHEKEY, (Object) dfsdata, referenceQueue)); 204 } 205 206 return dfsdata; 207 } 208 209 public String getCurrencyName(String key) { 210 Object currencyName = null; 211 String cacheKey = CURRENCY_NAMES + key; 212 213 removeEmptyReferences(); 214 ResourceReference data = cache.get(cacheKey); 215 216 if (data != null && ((currencyName = data.get()) != null)) { 217 if (currencyName.equals(NULLOBJECT)) { 218 currencyName = null; 219 } 220 221 return (String) currencyName; 222 } 223 224 OpenListResourceBundle olrb = localeData.getCurrencyNames(locale); 225 226 if (olrb.containsKey(key)) { 227 currencyName = olrb.getObject(key); 228 cache.put(cacheKey, 229 new ResourceReference(cacheKey, currencyName, referenceQueue)); 230 } 231 232 return (String) currencyName; 233 } 234 235 public String getLocaleName(String key) { 236 Object localeName = null; 237 String cacheKey = LOCALE_NAMES + key; 238 239 removeEmptyReferences(); 240 ResourceReference data = cache.get(cacheKey); 241 242 if (data != null && ((localeName = data.get()) != null)) { 243 if (localeName.equals(NULLOBJECT)) { 244 localeName = null; 245 } 246 247 return (String) localeName; 248 } 249 250 OpenListResourceBundle olrb = localeData.getLocaleNames(locale); 251 252 if (olrb.containsKey(key)) { 253 localeName = olrb.getObject(key); 254 cache.put(cacheKey, 255 new ResourceReference(cacheKey, localeName, referenceQueue)); 256 } 257 258 return (String) localeName; 259 } 260 261 public Object getTimeZoneNames(String key) { 262 Object val = null; 263 String cacheKey = TIME_ZONE_NAMES + key; 264 265 removeEmptyReferences(); 266 ResourceReference data = cache.get(cacheKey); 267 268 if (Objects.isNull(data) || Objects.isNull(val = data.get())) { 269 TimeZoneNamesBundle tznb = localeData.getTimeZoneNames(locale); 270 if (tznb.containsKey(key)) { 271 if (key.startsWith(TZNB_EXCITY_PREFIX)) { 272 val = tznb.getString(key); 273 assert val instanceof String; 274 trace("tznb: %s key: %s, val: %s\n", tznb, key, val); 275 } else { 276 String[] names = tznb.getStringArray(key); 277 trace("tznb: %s key: %s, names: %s, %s, %s, %s, %s, %s, %s\n", tznb, key, 278 names[0], names[1], names[2], names[3], names[4], names[5], names[6]); 279 val = names; 280 } 281 cache.put(cacheKey, 282 new ResourceReference(cacheKey, val, referenceQueue)); 283 } 284 } 285 286 return val; 287 } 288 289 @SuppressWarnings("unchecked") 290 Set<String> getZoneIDs() { 291 Set<String> zoneIDs = null; 292 293 removeEmptyReferences(); 294 ResourceReference data = cache.get(ZONE_IDS_CACHEKEY); 295 if (data == null || ((zoneIDs = (Set<String>) data.get()) == null)) { 296 TimeZoneNamesBundle rb = localeData.getTimeZoneNames(locale); 297 zoneIDs = rb.keySet(); 298 cache.put(ZONE_IDS_CACHEKEY, 299 new ResourceReference(ZONE_IDS_CACHEKEY, (Object) zoneIDs, referenceQueue)); 300 } 301 302 return zoneIDs; 303 } 304 305 // zoneStrings are cached separately in TimeZoneNameUtility. 306 String[][] getZoneStrings() { 307 TimeZoneNamesBundle rb = localeData.getTimeZoneNames(locale); 308 Set<String> keyset = getZoneIDs(); 309 // Use a LinkedHashSet to preseve the order 310 Set<String[]> value = new LinkedHashSet<>(); 311 for (String key : keyset) { 312 if (!key.startsWith(TZNB_EXCITY_PREFIX)) { 313 value.add(rb.getStringArray(key)); 314 } 315 } 316 317 // Add aliases data for CLDR 318 if (type == LocaleProviderAdapter.Type.CLDR) { 319 // Note: TimeZoneNamesBundle creates a String[] on each getStringArray call. 320 Map<String, String> aliases = ZoneInfo.getAliasTable(); 321 for (String alias : aliases.keySet()) { 322 if (!keyset.contains(alias)) { 323 String tzid = aliases.get(alias); 324 if (keyset.contains(tzid)) { 325 String[] val = rb.getStringArray(tzid); 326 val[0] = alias; 327 value.add(val); 328 } 329 } 330 } 331 } 332 return value.toArray(new String[0][]); 333 } 334 335 String[] getCalendarNames(String key) { 336 String[] names = null; 337 String cacheKey = CALENDAR_NAMES + key; 338 339 removeEmptyReferences(); 340 ResourceReference data = cache.get(cacheKey); 341 342 if (data == null || ((names = (String[]) data.get()) == null)) { 343 ResourceBundle rb = localeData.getDateFormatData(locale); 344 if (rb.containsKey(key)) { 345 names = rb.getStringArray(key); 346 cache.put(cacheKey, 347 new ResourceReference(cacheKey, (Object) names, referenceQueue)); 348 } 349 } 350 351 return names; 352 } 353 354 String[] getJavaTimeNames(String key) { 355 String[] names = null; 356 String cacheKey = CALENDAR_NAMES + key; 357 358 removeEmptyReferences(); 359 ResourceReference data = cache.get(cacheKey); 360 361 if (data == null || ((names = (String[]) data.get()) == null)) { 362 ResourceBundle rb = getJavaTimeFormatData(); 363 if (rb.containsKey(key)) { 364 names = rb.getStringArray(key); 365 cache.put(cacheKey, 366 new ResourceReference(cacheKey, (Object) names, referenceQueue)); 367 } 368 } 369 370 return names; 371 } 372 373 public String getDateTimePattern(int timeStyle, int dateStyle, Calendar cal) { 374 if (cal == null) { 375 cal = Calendar.getInstance(locale); 376 } 377 return getDateTimePattern(null, timeStyle, dateStyle, cal.getCalendarType()); 378 } 379 380 /** 381 * Returns a date-time format pattern 382 * @param timeStyle style of time; one of FULL, LONG, MEDIUM, SHORT in DateFormat, 383 * or -1 if not required 384 * @param dateStyle style of time; one of FULL, LONG, MEDIUM, SHORT in DateFormat, 385 * or -1 if not required 386 * @param calType the calendar type for the pattern 387 * @return the pattern string 388 */ 389 public String getJavaTimeDateTimePattern(int timeStyle, int dateStyle, String calType) { 390 calType = CalendarDataUtility.normalizeCalendarType(calType); 391 String pattern; 392 pattern = getDateTimePattern("java.time.", timeStyle, dateStyle, calType); 393 if (pattern == null) { 394 pattern = getDateTimePattern(null, timeStyle, dateStyle, calType); 395 } 396 return pattern; 397 } 398 399 private String getDateTimePattern(String prefix, int timeStyle, int dateStyle, String calType) { 400 String pattern; 401 String timePattern = null; 402 String datePattern = null; 403 404 if (timeStyle >= 0) { 405 if (prefix != null) { 406 timePattern = getDateTimePattern(prefix, "TimePatterns", timeStyle, calType); 407 } 408 if (timePattern == null) { 409 timePattern = getDateTimePattern(null, "TimePatterns", timeStyle, calType); 410 } 411 } 412 if (dateStyle >= 0) { 413 if (prefix != null) { 414 datePattern = getDateTimePattern(prefix, "DatePatterns", dateStyle, calType); 415 } 416 if (datePattern == null) { 417 datePattern = getDateTimePattern(null, "DatePatterns", dateStyle, calType); 418 } 419 } 420 if (timeStyle >= 0) { 421 if (dateStyle >= 0) { 422 String dateTimePattern = null; 423 int dateTimeStyle = Math.max(dateStyle, timeStyle); 424 if (prefix != null) { 425 dateTimePattern = getDateTimePattern(prefix, "DateTimePatterns", dateTimeStyle, calType); 426 } 427 if (dateTimePattern == null) { 428 dateTimePattern = getDateTimePattern(null, "DateTimePatterns", dateTimeStyle, calType); 429 } 430 switch (dateTimePattern) { 431 case "{1} {0}": 432 pattern = datePattern + " " + timePattern; 433 break; 434 case "{0} {1}": 435 pattern = timePattern + " " + datePattern; 436 break; 437 default: 438 pattern = MessageFormat.format(dateTimePattern.replaceAll("'", "''"), timePattern, datePattern); 439 break; 440 } 441 } else { 442 pattern = timePattern; 443 } 444 } else if (dateStyle >= 0) { 445 pattern = datePattern; 446 } else { 447 throw new IllegalArgumentException("No date or time style specified"); 448 } 449 return pattern; 450 } 451 452 public String[] getNumberPatterns() { 453 String[] numberPatterns = null; 454 455 removeEmptyReferences(); 456 ResourceReference data = cache.get(NUMBER_PATTERNS_CACHEKEY); 457 458 if (data == null || ((numberPatterns = (String[]) data.get()) == null)) { 459 ResourceBundle resource = localeData.getNumberFormatData(locale); 460 numberPatterns = resource.getStringArray("NumberPatterns"); 461 cache.put(NUMBER_PATTERNS_CACHEKEY, 462 new ResourceReference(NUMBER_PATTERNS_CACHEKEY, (Object) numberPatterns, referenceQueue)); 463 } 464 465 return numberPatterns; 466 } 467 468 /** 469 * Returns the FormatData resource bundle of this LocaleResources. 470 * The FormatData should be used only for accessing extra 471 * resources required by JSR 310. 472 */ 473 public ResourceBundle getJavaTimeFormatData() { 474 ResourceBundle rb = localeData.getDateFormatData(locale); 475 if (rb instanceof ParallelListResourceBundle) { 476 localeData.setSupplementary((ParallelListResourceBundle) rb); 477 } 478 return rb; 479 } 480 481 private String getDateTimePattern(String prefix, String key, int styleIndex, String calendarType) { 482 StringBuilder sb = new StringBuilder(); 483 if (prefix != null) { 484 sb.append(prefix); 485 } 486 if (!"gregory".equals(calendarType)) { 487 sb.append(calendarType).append('.'); 488 } 489 sb.append(key); 490 String resourceKey = sb.toString(); 491 String cacheKey = sb.insert(0, DATE_TIME_PATTERN).toString(); 492 493 removeEmptyReferences(); 494 ResourceReference data = cache.get(cacheKey); 495 Object value = NULLOBJECT; 496 497 if (data == null || ((value = data.get()) == null)) { 498 ResourceBundle r = (prefix != null) ? getJavaTimeFormatData() : localeData.getDateFormatData(locale); 499 if (r.containsKey(resourceKey)) { 500 value = r.getStringArray(resourceKey); 501 } else { 502 assert !resourceKey.equals(key); 503 if (r.containsKey(key)) { 504 value = r.getStringArray(key); 505 } 506 } 507 cache.put(cacheKey, 508 new ResourceReference(cacheKey, value, referenceQueue)); 509 } 510 if (value == NULLOBJECT) { 511 assert prefix != null; 512 return null; 513 } 514 515 // for DateTimePatterns. CLDR has multiple styles, while JRE has one. 516 String[] styles = (String[])value; 517 return (styles.length > 1 ? styles[styleIndex] : styles[0]); 518 } 519 520 private static class ResourceReference extends SoftReference<Object> { 521 private final String cacheKey; 522 523 ResourceReference(String cacheKey, Object o, ReferenceQueue<Object> q) { 524 super(o, q); 525 this.cacheKey = cacheKey; 526 } 527 528 String getCacheKey() { 529 return cacheKey; 530 } 531 } 532 533 private static final boolean TRACE_ON = Boolean.valueOf( 534 GetPropertyAction.privilegedGetProperty("locale.resources.debug", "false")); 535 536 public static void trace(String format, Object... params) { 537 if (TRACE_ON) { 538 System.out.format(format, params); 539 } 540 } 541 }