1 /*
2  * Copyright (c) 2012, 2018, 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 package sun.util.locale.provider;
27 
28 import java.security.AccessController;
29 import java.security.AccessControlException;
30 import java.security.PrivilegedAction;
31 import java.security.PrivilegedActionException;
32 import java.security.PrivilegedExceptionAction;
33 import java.text.spi.BreakIteratorProvider;
34 import java.text.spi.CollatorProvider;
35 import java.text.spi.DateFormatProvider;
36 import java.text.spi.DateFormatSymbolsProvider;
37 import java.text.spi.DecimalFormatSymbolsProvider;
38 import java.text.spi.NumberFormatProvider;
39 import java.util.Collections;
40 import java.util.HashSet;
41 import java.util.List;
42 import java.util.Locale;
43 import java.util.ResourceBundle;
44 import java.util.ServiceLoader;
45 import java.util.ServiceConfigurationError;
46 import java.util.Set;
47 import java.util.StringTokenizer;
48 import java.util.concurrent.ConcurrentHashMap;
49 import java.util.concurrent.ConcurrentMap;
50 import java.util.spi.CalendarDataProvider;
51 import java.util.spi.CalendarNameProvider;
52 import java.util.spi.CurrencyNameProvider;
53 import java.util.spi.LocaleNameProvider;
54 import java.util.spi.LocaleServiceProvider;
55 import java.util.spi.TimeZoneNameProvider;
56 import sun.text.spi.JavaTimeDateTimePatternProvider;
57 import sun.util.resources.LocaleData;
58 import sun.util.spi.CalendarProvider;
59 
60 /**
61  * LocaleProviderAdapter implementation for the legacy JRE locale data.
62  *
63  * @author Naoto Sato
64  * @author Masayoshi Okutsu
65  */
66 public class JRELocaleProviderAdapter extends LocaleProviderAdapter implements ResourceBundleBasedAdapter {
67 
68     private final ConcurrentMap<String, Set<String>> langtagSets
69         = new ConcurrentHashMap<>();
70 
71     private final ConcurrentMap<Locale, LocaleResources> localeResourcesMap
72         = new ConcurrentHashMap<>();
73 
74     // LocaleData specific to this LocaleProviderAdapter.
75     private volatile LocaleData localeData;
76 
77     /**
78      * Returns the type of this LocaleProviderAdapter
79      */
80     @Override
getAdapterType()81     public LocaleProviderAdapter.Type getAdapterType() {
82         return Type.JRE;
83     }
84 
85     /**
86      * Getter method for Locale Service Providers
87      */
88     @Override
89     @SuppressWarnings("unchecked")
getLocaleServiceProvider(Class<P> c)90     public <P extends LocaleServiceProvider> P getLocaleServiceProvider(Class<P> c) {
91         switch (c.getSimpleName()) {
92         case "BreakIteratorProvider":
93             return (P) getBreakIteratorProvider();
94         case "CollatorProvider":
95             return (P) getCollatorProvider();
96         case "DateFormatProvider":
97             return (P) getDateFormatProvider();
98         case "DateFormatSymbolsProvider":
99             return (P) getDateFormatSymbolsProvider();
100         case "DecimalFormatSymbolsProvider":
101             return (P) getDecimalFormatSymbolsProvider();
102         case "NumberFormatProvider":
103             return (P) getNumberFormatProvider();
104         case "CurrencyNameProvider":
105             return (P) getCurrencyNameProvider();
106         case "LocaleNameProvider":
107             return (P) getLocaleNameProvider();
108         case "TimeZoneNameProvider":
109             return (P) getTimeZoneNameProvider();
110         case "CalendarDataProvider":
111             return (P) getCalendarDataProvider();
112         case "CalendarNameProvider":
113             return (P) getCalendarNameProvider();
114         case "CalendarProvider":
115             return (P) getCalendarProvider();
116         case "JavaTimeDateTimePatternProvider":
117             return (P) getJavaTimeDateTimePatternProvider();
118         default:
119             throw new InternalError("should not come down here");
120         }
121     }
122 
123     private volatile BreakIteratorProvider breakIteratorProvider;
124     private volatile CollatorProvider collatorProvider;
125     private volatile DateFormatProvider dateFormatProvider;
126     private volatile DateFormatSymbolsProvider dateFormatSymbolsProvider;
127     private volatile DecimalFormatSymbolsProvider decimalFormatSymbolsProvider;
128     private volatile NumberFormatProvider numberFormatProvider;
129 
130     private volatile CurrencyNameProvider currencyNameProvider;
131     private volatile LocaleNameProvider localeNameProvider;
132     protected volatile TimeZoneNameProvider timeZoneNameProvider;
133     protected volatile CalendarDataProvider calendarDataProvider;
134     protected volatile CalendarNameProvider calendarNameProvider;
135 
136     private volatile CalendarProvider calendarProvider;
137     private volatile JavaTimeDateTimePatternProvider javaTimeDateTimePatternProvider;
138 
139     /*
140      * Getter methods for java.text.spi.* providers
141      */
142     @Override
getBreakIteratorProvider()143     public BreakIteratorProvider getBreakIteratorProvider() {
144         if (breakIteratorProvider == null) {
145             BreakIteratorProvider provider = AccessController.doPrivileged(
146                 (PrivilegedAction<BreakIteratorProvider>) () ->
147                     new BreakIteratorProviderImpl(
148                         getAdapterType(),
149                         getLanguageTagSet("FormatData")));
150 
151             synchronized (this) {
152                 if (breakIteratorProvider == null) {
153                     breakIteratorProvider = provider;
154                 }
155             }
156         }
157         return breakIteratorProvider;
158     }
159 
160     @Override
getCollatorProvider()161     public CollatorProvider getCollatorProvider() {
162         if (collatorProvider == null) {
163             CollatorProvider provider = AccessController.doPrivileged(
164                 (PrivilegedAction<CollatorProvider>) () ->
165                     new CollatorProviderImpl(
166                         getAdapterType(),
167                         getLanguageTagSet("CollationData")));
168 
169             synchronized (this) {
170                 if (collatorProvider == null) {
171                     collatorProvider = provider;
172                 }
173             }
174         }
175         return collatorProvider;
176     }
177 
178     @Override
getDateFormatProvider()179     public DateFormatProvider getDateFormatProvider() {
180         if (dateFormatProvider == null) {
181             DateFormatProvider provider = AccessController.doPrivileged(
182                 (PrivilegedAction<DateFormatProvider>) () ->
183                     new DateFormatProviderImpl(
184                         getAdapterType(),
185                         getLanguageTagSet("FormatData")));
186 
187             synchronized (this) {
188                 if (dateFormatProvider == null) {
189                     dateFormatProvider = provider;
190                 }
191             }
192         }
193         return dateFormatProvider;
194     }
195 
196     @Override
getDateFormatSymbolsProvider()197     public DateFormatSymbolsProvider getDateFormatSymbolsProvider() {
198         if (dateFormatSymbolsProvider == null) {
199             DateFormatSymbolsProvider provider = AccessController.doPrivileged(
200                 (PrivilegedAction<DateFormatSymbolsProvider>) () ->
201                     new DateFormatSymbolsProviderImpl(
202                         getAdapterType(),
203                         getLanguageTagSet("FormatData")));
204 
205             synchronized (this) {
206                 if (dateFormatSymbolsProvider == null) {
207                     dateFormatSymbolsProvider = provider;
208                 }
209             }
210         }
211         return dateFormatSymbolsProvider;
212     }
213 
214     @Override
getDecimalFormatSymbolsProvider()215     public DecimalFormatSymbolsProvider getDecimalFormatSymbolsProvider() {
216         if (decimalFormatSymbolsProvider == null) {
217             DecimalFormatSymbolsProvider provider = AccessController.doPrivileged(
218                 (PrivilegedAction<DecimalFormatSymbolsProvider>) () ->
219                     new DecimalFormatSymbolsProviderImpl(
220                         getAdapterType(),
221                         getLanguageTagSet("FormatData")));
222 
223             synchronized (this) {
224                 if (decimalFormatSymbolsProvider == null) {
225                     decimalFormatSymbolsProvider = provider;
226                 }
227             }
228         }
229         return decimalFormatSymbolsProvider;
230     }
231 
232     @Override
getNumberFormatProvider()233     public NumberFormatProvider getNumberFormatProvider() {
234         if (numberFormatProvider == null) {
235             NumberFormatProvider provider = AccessController.doPrivileged(
236                 (PrivilegedAction<NumberFormatProvider>) () ->
237                     new NumberFormatProviderImpl(
238                         getAdapterType(),
239                         getLanguageTagSet("FormatData")));
240 
241             synchronized (this) {
242                 if (numberFormatProvider == null) {
243                     numberFormatProvider = provider;
244                 }
245             }
246         }
247         return numberFormatProvider;
248     }
249 
250     /**
251      * Getter methods for java.util.spi.* providers
252      */
253     @Override
getCurrencyNameProvider()254     public CurrencyNameProvider getCurrencyNameProvider() {
255         if (currencyNameProvider == null) {
256             CurrencyNameProvider provider = AccessController.doPrivileged(
257                 (PrivilegedAction<CurrencyNameProvider>) () ->
258                     new CurrencyNameProviderImpl(
259                         getAdapterType(),
260                         getLanguageTagSet("CurrencyNames")));
261 
262             synchronized (this) {
263                 if (currencyNameProvider == null) {
264                     currencyNameProvider = provider;
265                 }
266             }
267         }
268         return currencyNameProvider;
269     }
270 
271     @Override
getLocaleNameProvider()272     public LocaleNameProvider getLocaleNameProvider() {
273         if (localeNameProvider == null) {
274             LocaleNameProvider provider = AccessController.doPrivileged(
275                 (PrivilegedAction<LocaleNameProvider>) () ->
276                     new LocaleNameProviderImpl(
277                         getAdapterType(),
278                         getLanguageTagSet("LocaleNames")));
279 
280             synchronized (this) {
281                 if (localeNameProvider == null) {
282                     localeNameProvider = provider;
283                 }
284             }
285         }
286         return localeNameProvider;
287     }
288 
289     @Override
getTimeZoneNameProvider()290     public TimeZoneNameProvider getTimeZoneNameProvider() {
291         if (timeZoneNameProvider == null) {
292             TimeZoneNameProvider provider = AccessController.doPrivileged(
293                 (PrivilegedAction<TimeZoneNameProvider>) () ->
294                     new TimeZoneNameProviderImpl(
295                         getAdapterType(),
296                         getLanguageTagSet("TimeZoneNames")));
297 
298             synchronized (this) {
299                 if (timeZoneNameProvider == null) {
300                     timeZoneNameProvider = provider;
301                 }
302             }
303         }
304         return timeZoneNameProvider;
305     }
306 
307     @Override
getCalendarDataProvider()308     public CalendarDataProvider getCalendarDataProvider() {
309         if (calendarDataProvider == null) {
310             CalendarDataProvider provider = AccessController.doPrivileged(
311                 (PrivilegedAction<CalendarDataProvider>) () ->
312                     new CalendarDataProviderImpl(
313                         getAdapterType(),
314                         getLanguageTagSet("CalendarData")));
315 
316             synchronized (this) {
317                 if (calendarDataProvider == null) {
318                     calendarDataProvider = provider;
319                 }
320             }
321         }
322         return calendarDataProvider;
323     }
324 
325     @Override
getCalendarNameProvider()326     public CalendarNameProvider getCalendarNameProvider() {
327         if (calendarNameProvider == null) {
328             CalendarNameProvider provider = AccessController.doPrivileged(
329                 (PrivilegedAction<CalendarNameProvider>) () ->
330                     new CalendarNameProviderImpl(
331                         getAdapterType(),
332                         getLanguageTagSet("FormatData")));
333 
334             synchronized (this) {
335                 if (calendarNameProvider == null) {
336                     calendarNameProvider = provider;
337                 }
338             }
339         }
340         return calendarNameProvider;
341     }
342 
343     /**
344      * Getter methods for sun.util.spi.* providers
345      */
346     @Override
getCalendarProvider()347     public CalendarProvider getCalendarProvider() {
348         if (calendarProvider == null) {
349             CalendarProvider provider = AccessController.doPrivileged(
350                 (PrivilegedAction<CalendarProvider>) () ->
351                     new CalendarProviderImpl(
352                         getAdapterType(),
353                         getLanguageTagSet("CalendarData")));
354 
355             synchronized (this) {
356                 if (calendarProvider == null) {
357                     calendarProvider = provider;
358                 }
359             }
360         }
361         return calendarProvider;
362     }
363 
364     /**
365      * Getter methods for sun.text.spi.JavaTimeDateTimePatternProvider provider
366      */
367     @Override
getJavaTimeDateTimePatternProvider()368     public JavaTimeDateTimePatternProvider getJavaTimeDateTimePatternProvider() {
369         if (javaTimeDateTimePatternProvider == null) {
370             JavaTimeDateTimePatternProvider provider = AccessController.doPrivileged(
371                     (PrivilegedAction<JavaTimeDateTimePatternProvider>) ()
372                     -> new JavaTimeDateTimePatternImpl(
373                             getAdapterType(),
374                             getLanguageTagSet("FormatData")));
375 
376             synchronized (this) {
377                 if (javaTimeDateTimePatternProvider == null) {
378                     javaTimeDateTimePatternProvider = provider;
379                 }
380             }
381         }
382         return javaTimeDateTimePatternProvider;
383     }
384 
385     @Override
getLocaleResources(Locale locale)386     public LocaleResources getLocaleResources(Locale locale) {
387         LocaleResources lr = localeResourcesMap.get(locale);
388         if (lr == null) {
389             lr = new LocaleResources(this, locale);
390             LocaleResources lrc = localeResourcesMap.putIfAbsent(locale, lr);
391             if (lrc != null) {
392                 lr = lrc;
393             }
394         }
395         return lr;
396     }
397 
398     // ResourceBundleBasedAdapter method implementation
399 
400     @Override
getLocaleData()401     public LocaleData getLocaleData() {
402         if (localeData == null) {
403             synchronized (this) {
404                 if (localeData == null) {
405                     localeData = new LocaleData(getAdapterType());
406                 }
407             }
408         }
409         return localeData;
410     }
411 
412     @Override
getCandidateLocales(String baseName, Locale locale)413     public List<Locale> getCandidateLocales(String baseName, Locale locale) {
414         return ResourceBundle.Control
415             .getNoFallbackControl(ResourceBundle.Control.FORMAT_DEFAULT)
416             .getCandidateLocales(baseName, locale);
417     }
418 
419     /**
420      * Returns a list of the installed locales. Currently, this simply returns
421      * the list of locales for which a sun.text.resources.FormatData bundle
422      * exists. This bundle family happens to be the one with the broadest
423      * locale coverage in the JRE.
424      */
425     @Override
getAvailableLocales()426     public Locale[] getAvailableLocales() {
427         return AvailableJRELocales.localeList.clone();
428     }
429 
getLanguageTagSet(String category)430     public Set<String> getLanguageTagSet(String category) {
431         Set<String> tagset = langtagSets.get(category);
432         if (tagset == null) {
433             tagset = createLanguageTagSet(category);
434             Set<String> ts = langtagSets.putIfAbsent(category, tagset);
435             if (ts != null) {
436                 tagset = ts;
437             }
438         }
439         return tagset;
440     }
441 
createLanguageTagSet(String category)442     protected Set<String> createLanguageTagSet(String category) {
443         String supportedLocaleString = createSupportedLocaleString(category);
444         if (supportedLocaleString == null) {
445             return Collections.emptySet();
446         }
447         Set<String> tagset = new HashSet<>();
448         StringTokenizer tokens = new StringTokenizer(supportedLocaleString);
449         while (tokens.hasMoreTokens()) {
450             tagset.add(tokens.nextToken());
451         }
452 
453         return tagset;
454     }
455 
createSupportedLocaleString(String category)456     private static String createSupportedLocaleString(String category) {
457         // Directly call Base tags, as we know it's in the base module.
458         String supportedLocaleString = BaseLocaleDataMetaInfo.getSupportedLocaleString(category);
459 
460         // Use ServiceLoader to dynamically acquire installed locales' tags.
461         try {
462             String nonBaseTags = AccessController.doPrivileged((PrivilegedExceptionAction<String>) () -> {
463                 StringBuilder tags = new StringBuilder();
464                 for (LocaleDataMetaInfo ldmi :
465                         ServiceLoader.loadInstalled(LocaleDataMetaInfo.class)) {
466                     if (ldmi.getType() == LocaleProviderAdapter.Type.JRE) {
467                         String t = ldmi.availableLanguageTags(category);
468                         if (t != null) {
469                             if (tags.length() > 0) {
470                                 tags.append(' ');
471                             }
472                             tags.append(t);
473                         }
474                     }
475                 }
476                 return tags.toString();
477             });
478 
479             if (nonBaseTags != null) {
480                 supportedLocaleString += " " + nonBaseTags;
481             }
482         } catch (PrivilegedActionException pae) {
483             throw new InternalError(pae.getCause());
484         }
485 
486         return supportedLocaleString;
487     }
488 
489     /**
490      * Lazy load available locales.
491      */
492     private static class AvailableJRELocales {
493         private static final Locale[] localeList = createAvailableLocales();
AvailableJRELocales()494         private AvailableJRELocales() {}
495     }
496 
createAvailableLocales()497     private static Locale[] createAvailableLocales() {
498         /*
499          * Gets the locale string list from LocaleDataMetaInfo classes and then
500          * contructs the Locale array and a set of language tags based on the
501          * locale string returned above.
502          */
503         String supportedLocaleString = createSupportedLocaleString("AvailableLocales");
504 
505         if (supportedLocaleString.isEmpty()) {
506             throw new InternalError("No available locales for JRE");
507         }
508 
509         StringTokenizer localeStringTokenizer = new StringTokenizer(supportedLocaleString);
510 
511         int length = localeStringTokenizer.countTokens();
512         Locale[] locales = new Locale[length + 1];
513         locales[0] = Locale.ROOT;
514         for (int i = 1; i <= length; i++) {
515             String currentToken = localeStringTokenizer.nextToken();
516             switch (currentToken) {
517                 case "ja-JP-JP":
518                     locales[i] = JRELocaleConstants.JA_JP_JP;
519                     break;
520                 case "no-NO-NY":
521                     locales[i] = JRELocaleConstants.NO_NO_NY;
522                     break;
523                 case "th-TH-TH":
524                     locales[i] = JRELocaleConstants.TH_TH_TH;
525                     break;
526                 default:
527                     locales[i] = Locale.forLanguageTag(currentToken);
528             }
529         }
530         return locales;
531     }
532 
533     @Override
isSupportedProviderLocale(Locale locale, Set<String> langtags)534     public boolean isSupportedProviderLocale(Locale locale,  Set<String> langtags) {
535         if (Locale.ROOT.equals(locale)) {
536             return true;
537         }
538 
539         locale = locale.stripExtensions();
540         if (langtags.contains(locale.toLanguageTag())) {
541             return true;
542         }
543 
544         String oldname = locale.toString().replace('_', '-');
545         return langtags.contains(oldname) ||
546                    "ja-JP-JP".equals(oldname) ||
547                    "th-TH-TH".equals(oldname) ||
548                    "no-NO-NY".equals(oldname);
549     }
550 }
551