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