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