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