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