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