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