View Javadoc
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 + size + '.' + 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 getJavaTimeDateTimePattern(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 }