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