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 cal = locale.getUnicodeLocaleType("ca"); 317 if (cal != null) { 318 String[] names = getCalendarNamesImpl(cal + "." + key); 319 if (names != null) { 320 return names; 321 } 322 } 323 324 return getCalendarNamesImpl(key); 325 } 326 327 private String[] getCalendarNamesImpl(String key) { 328 String[] names = null; 329 String cacheKey = CALENDAR_NAMES + key; 330 331 removeEmptyReferences(); 332 ResourceReference data = cache.get(cacheKey); 333 334 if (data == null || ((names = (String[]) data.get()) == null)) { 335 ResourceBundle rb = localeData.getDateFormatData(locale); 336 if (rb.containsKey(key)) { 337 names = rb.getStringArray(key); 338 cache.put(cacheKey, 339 new ResourceReference(cacheKey, (Object) names, referenceQueue)); 340 } 341 } 342 343 return names; 344 } 345 346 public String getDateTimePattern(int timeStyle, int dateStyle, Calendar cal) { 347 String pattern; 348 349 if (cal == null) { 350 cal = Calendar.getInstance(locale); 351 } 352 String calType = cal.getCalendarType(); 353 String timePattern = null; 354 String datePattern = null; 355 if (timeStyle >= 0) { 356 timePattern = getDateTimePattern("TimePatterns", timeStyle, calType); 357 } 358 if (dateStyle >= 0) { 359 datePattern = getDateTimePattern("DatePatterns", dateStyle, calType); 360 } 361 if (timeStyle >= 0) { 362 if (dateStyle >= 0) { 363 String dateTimePattern = getDateTimePattern("DateTimePatterns", 0, calType); 364 switch (dateTimePattern) { 365 case "{1} {0}": 366 pattern = datePattern + " " + timePattern; 367 break; 368 case "{0} {1}": 369 pattern = timePattern + " " + datePattern; 370 break; 371 default: 372 pattern = MessageFormat.format(dateTimePattern, timePattern, datePattern); 373 break; 374 } 375 } else { 376 pattern = timePattern; 377 } 378 } else if (dateStyle >= 0) { 379 pattern = datePattern; 380 } else { 381 throw new IllegalArgumentException("No date or time style specified"); 382 } 383 return pattern; 384 } 385 386 public String[] getNumberPatterns() { 387 String[] numberPatterns = null; 388 389 removeEmptyReferences(); 390 ResourceReference data = cache.get(NUMBER_PATTERNS_CACHEKEY); 391 392 if (data == null || ((numberPatterns = (String[]) data.get()) == null)) { 393 ResourceBundle resource = localeData.getNumberFormatData(locale); 394 numberPatterns = resource.getStringArray("NumberPatterns"); 395 cache.put(NUMBER_PATTERNS_CACHEKEY, 396 new ResourceReference(NUMBER_PATTERNS_CACHEKEY, (Object) numberPatterns, referenceQueue)); 397 } 398 399 return numberPatterns; 400 } 401 402 /** 403 * Returns the FormatData resource bundle of this LocaleResources. 404 * The FormatData should be used only for accessing extra 405 * resources required by JSR 310. 406 */ 407 public ResourceBundle getFormatData() { 408 return localeData.getDateFormatData(locale); 409 } 410 411 private String getDateTimePattern(String key, int styleIndex, String calendarType) { 412 String resourceKey = "gregory".equals(calendarType) ? key : calendarType + "." + key; 413 String cacheKey = DATE_TIME_PATTERN + resourceKey; 414 String[] patterns = null; 415 416 removeEmptyReferences(); 417 ResourceReference data = cache.get(cacheKey); 418 419 if (data == null || ((patterns = (String[]) data.get()) == null)) { 420 ResourceBundle r = localeData.getDateFormatData(locale); 421 if (r.containsKey(resourceKey)) { 422 patterns = r.getStringArray(resourceKey); 423 } else { 424 assert !resourceKey.equals(key); 425 patterns = r.getStringArray(key); 426 } 427 cache.put(cacheKey, 428 new ResourceReference(cacheKey, (Object) patterns, referenceQueue)); 429 } 430 431 return patterns[styleIndex]; 432 } 433 434 private static class ResourceReference extends SoftReference<Object> { 435 private final String cacheKey; 436 437 ResourceReference(String cacheKey, Object o, ReferenceQueue<Object> q) { 438 super(o, q); 439 this.cacheKey = cacheKey; 440 } 441 442 String getCacheKey() { 443 return cacheKey; 444 } 445 } 446 }