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.text.spi.BreakIteratorProvider;
29import java.text.spi.CollatorProvider;
30import java.text.spi.DateFormatProvider;
31import java.text.spi.DateFormatSymbolsProvider;
32import java.text.spi.DecimalFormatSymbolsProvider;
33import java.text.spi.NumberFormatProvider;
34import java.util.ArrayList;
35import java.util.Collections;
36import java.util.List;
37import java.util.Locale;
38import java.util.Map;
39import java.util.ResourceBundle;
40import java.util.Set;
41import java.util.concurrent.ConcurrentHashMap;
42import java.util.concurrent.ConcurrentMap;
43import java.util.spi.CalendarDataProvider;
44import java.util.spi.CalendarNameProvider;
45import java.util.spi.CurrencyNameProvider;
46import java.util.spi.LocaleNameProvider;
47import java.util.spi.LocaleServiceProvider;
48import java.util.spi.TimeZoneNameProvider;
49import sun.security.action.GetPropertyAction;
50import sun.text.spi.JavaTimeDateTimePatternProvider;
51import sun.util.spi.CalendarProvider;
52
53/**
54 * The LocaleProviderAdapter abstract class.
55 *
56 * @author Naoto Sato
57 * @author Masayoshi Okutsu
58 */
59public abstract class LocaleProviderAdapter {
60    /**
61     * Adapter type.
62     */
63    public static enum Type {
64        JRE("sun.util.locale.provider.JRELocaleProviderAdapter", "sun.util.resources", "sun.text.resources"),
65        CLDR("sun.util.cldr.CLDRLocaleProviderAdapter", "sun.util.resources.cldr", "sun.text.resources.cldr"),
66        SPI("sun.util.locale.provider.SPILocaleProviderAdapter"),
67        HOST("sun.util.locale.provider.HostLocaleProviderAdapter"),
68        FALLBACK("sun.util.locale.provider.FallbackLocaleProviderAdapter", "sun.util.resources", "sun.text.resources");
69
70        private final String CLASSNAME;
71        private final String UTIL_RESOURCES_PACKAGE;
72        private final String TEXT_RESOURCES_PACKAGE;
73
74        private Type(String className) {
75            this(className, null, null);
76        }
77
78        private Type(String className, String util, String text) {
79            CLASSNAME = className;
80            UTIL_RESOURCES_PACKAGE = util;
81            TEXT_RESOURCES_PACKAGE = text;
82        }
83
84        public String getAdapterClassName() {
85            return CLASSNAME;
86        }
87
88        public String getUtilResourcesPackage() {
89            return UTIL_RESOURCES_PACKAGE;
90        }
91
92        public String getTextResourcesPackage() {
93            return TEXT_RESOURCES_PACKAGE;
94        }
95    }
96
97    /**
98     * LocaleProviderAdapter preference list.
99     */
100    private static final List<Type> adapterPreference;
101
102    /**
103     * LocaleProviderAdapter instances
104     */
105    private static final Map<Type, LocaleProviderAdapter> adapterInstances = new ConcurrentHashMap<>();
106
107    /**
108     * Default fallback adapter type, which should return something meaningful in any case.
109     * This is either CLDR or FALLBACK.
110     */
111    static volatile LocaleProviderAdapter.Type defaultLocaleProviderAdapter;
112
113    /**
114     * Adapter lookup cache.
115     */
116    private static ConcurrentMap<Class<? extends LocaleServiceProvider>, ConcurrentMap<Locale, LocaleProviderAdapter>>
117        adapterCache = new ConcurrentHashMap<>();
118
119    static {
120        String order = GetPropertyAction.privilegedGetProperty("java.locale.providers");
121        List<Type> typeList = new ArrayList<>();
122
123        // Check user specified adapter preference
124        if (order != null && order.length() != 0) {
125            String[] types = order.split(",");
126            for (String type : types) {
127                type = type.trim().toUpperCase(Locale.ROOT);
128                if (type.equals("COMPAT")) {
129                    type = "JRE";
130                }
131                try {
132                    Type aType = Type.valueOf(type.trim().toUpperCase(Locale.ROOT));
133                    if (!typeList.contains(aType)) {
134                        typeList.add(aType);
135                    }
136                } catch (IllegalArgumentException | UnsupportedOperationException e) {
137                    // could be caused by the user specifying wrong
138                    // provider name or format in the system property
139                    LocaleServiceProviderPool.config(LocaleProviderAdapter.class, e.toString());
140                }
141            }
142        }
143
144        defaultLocaleProviderAdapter = Type.CLDR;
145        if (!typeList.isEmpty()) {
146            // bona fide preference exists
147            if (!(typeList.contains(Type.CLDR) || (typeList.contains(Type.JRE)))) {
148                // Append FALLBACK as the last resort when no ResourceBundleBasedAdapter is available.
149                typeList.add(Type.FALLBACK);
150                defaultLocaleProviderAdapter = Type.FALLBACK;
151            }
152        } else {
153            // Default preference list.
154            typeList.add(Type.CLDR);
155            typeList.add(Type.JRE);
156        }
157        adapterPreference = Collections.unmodifiableList(typeList);
158    }
159
160    /**
161     * Returns the singleton instance for each adapter type
162     */
163    public static LocaleProviderAdapter forType(Type type) {
164        switch (type) {
165        case JRE:
166        case CLDR:
167        case SPI:
168        case HOST:
169        case FALLBACK:
170            LocaleProviderAdapter adapter = null;
171            LocaleProviderAdapter cached = adapterInstances.get(type);
172            if (cached == null) {
173                try {
174                    // lazily load adapters here
175                    @SuppressWarnings("deprecation")
176                    Object tmp = Class.forName(type.getAdapterClassName()).newInstance();
177                    adapter = (LocaleProviderAdapter)tmp;
178                    cached = adapterInstances.putIfAbsent(type, adapter);
179                    if (cached != null) {
180                        adapter = cached;
181                    }
182                } catch (ClassNotFoundException |
183                         IllegalAccessException |
184                         InstantiationException |
185                         UnsupportedOperationException e) {
186                    LocaleServiceProviderPool.config(LocaleProviderAdapter.class, e.toString());
187                    adapterInstances.putIfAbsent(type, NONEXISTENT_ADAPTER);
188                    if (defaultLocaleProviderAdapter == type) {
189                        defaultLocaleProviderAdapter = Type.FALLBACK;
190                    }
191                }
192            } else if (cached != NONEXISTENT_ADAPTER) {
193                adapter = cached;
194            }
195            return adapter;
196        default:
197            throw new InternalError("unknown locale data adapter type");
198        }
199    }
200
201    public static LocaleProviderAdapter forJRE() {
202        return forType(Type.JRE);
203    }
204
205    public static LocaleProviderAdapter getResourceBundleBased() {
206        for (Type type : getAdapterPreference()) {
207            if (type == Type.JRE || type == Type.CLDR || type == Type.FALLBACK) {
208                LocaleProviderAdapter adapter = forType(type);
209                if (adapter != null) {
210                    return adapter;
211                }
212            }
213        }
214        // Shouldn't happen.
215        throw new InternalError();
216    }
217
218    /**
219     * Returns the preference order of LocaleProviderAdapter.Type
220     */
221    public static List<Type> getAdapterPreference() {
222        return adapterPreference;
223    }
224
225    /**
226     * Returns a LocaleProviderAdapter for the given locale service provider that
227     * best matches the given locale. This method returns the LocaleProviderAdapter
228     * for JRE if none is found for the given locale.
229     *
230     * @param providerClass the class for the locale service provider
231     * @param locale the desired locale.
232     * @return a LocaleProviderAdapter
233     */
234    public static LocaleProviderAdapter getAdapter(Class<? extends LocaleServiceProvider> providerClass,
235                                               Locale locale) {
236        LocaleProviderAdapter adapter;
237
238        // cache lookup
239        ConcurrentMap<Locale, LocaleProviderAdapter> adapterMap = adapterCache.get(providerClass);
240        if (adapterMap != null) {
241            if ((adapter = adapterMap.get(locale)) != null) {
242                return adapter;
243            }
244        } else {
245            adapterMap = new ConcurrentHashMap<>();
246            adapterCache.putIfAbsent(providerClass, adapterMap);
247        }
248
249        // Fast look-up for the given locale
250        adapter = findAdapter(providerClass, locale);
251        if (adapter != null) {
252            adapterMap.putIfAbsent(locale, adapter);
253            return adapter;
254        }
255
256        // Try finding an adapter in the normal candidate locales path of the given locale.
257        List<Locale> lookupLocales = ResourceBundle.Control.getControl(ResourceBundle.Control.FORMAT_DEFAULT)
258                                        .getCandidateLocales("", locale);
259        for (Locale loc : lookupLocales) {
260            if (loc.equals(locale)) {
261                // We've already done with this loc.
262                continue;
263            }
264            adapter = findAdapter(providerClass, loc);
265            if (adapter != null) {
266                adapterMap.putIfAbsent(locale, adapter);
267                return adapter;
268            }
269        }
270
271        // returns the adapter for FALLBACK as the last resort
272        adapterMap.putIfAbsent(locale, forType(Type.FALLBACK));
273        return forType(Type.FALLBACK);
274    }
275
276    private static LocaleProviderAdapter findAdapter(Class<? extends LocaleServiceProvider> providerClass,
277                                                 Locale locale) {
278        for (Type type : getAdapterPreference()) {
279            LocaleProviderAdapter adapter = forType(type);
280            if (adapter != null) {
281                LocaleServiceProvider provider = adapter.getLocaleServiceProvider(providerClass);
282                if (provider != null) {
283                    if (provider.isSupportedLocale(locale)) {
284                        return adapter;
285                    }
286                }
287            }
288        }
289        return null;
290    }
291
292    /**
293     * A utility method for implementing the default LocaleServiceProvider.isSupportedLocale
294     * for the JRE, CLDR, and FALLBACK adapters.
295     */
296    public boolean isSupportedProviderLocale(Locale locale,  Set<String> langtags) {
297        LocaleProviderAdapter.Type type = getAdapterType();
298        assert type == Type.JRE || type == Type.CLDR || type == Type.FALLBACK;
299        return false;
300    }
301
302    public static Locale[] toLocaleArray(Set<String> tags) {
303        Locale[] locs = new Locale[tags.size() + 1];
304        int index = 0;
305        locs[index++] = Locale.ROOT;
306        for (String tag : tags) {
307            switch (tag) {
308            case "ja-JP-JP":
309                locs[index++] = JRELocaleConstants.JA_JP_JP;
310                break;
311            case "th-TH-TH":
312                locs[index++] = JRELocaleConstants.TH_TH_TH;
313                break;
314            default:
315                locs[index++] = Locale.forLanguageTag(tag);
316                break;
317            }
318        }
319        return locs;
320    }
321
322    /**
323     * Returns the type of this LocaleProviderAdapter
324     */
325    public abstract LocaleProviderAdapter.Type getAdapterType();
326
327    /**
328     * Getter method for Locale Service Providers.
329     */
330    public abstract <P extends LocaleServiceProvider> P getLocaleServiceProvider(Class<P> c);
331
332    /**
333     * Returns a BreakIteratorProvider for this LocaleProviderAdapter, or null if no
334     * BreakIteratorProvider is available.
335     *
336     * @return a BreakIteratorProvider
337     */
338    public abstract BreakIteratorProvider getBreakIteratorProvider();
339
340    /**
341     * Returns a ollatorProvider for this LocaleProviderAdapter, or null if no
342     * ollatorProvider is available.
343     *
344     * @return a ollatorProvider
345     */
346    public abstract CollatorProvider getCollatorProvider();
347
348    /**
349     * Returns a DateFormatProvider for this LocaleProviderAdapter, or null if no
350     * DateFormatProvider is available.
351     *
352     * @return a DateFormatProvider
353     */
354    public abstract DateFormatProvider getDateFormatProvider();
355
356    /**
357     * Returns a DateFormatSymbolsProvider for this LocaleProviderAdapter, or null if no
358     * DateFormatSymbolsProvider is available.
359     *
360     * @return a DateFormatSymbolsProvider
361     */
362    public abstract DateFormatSymbolsProvider getDateFormatSymbolsProvider();
363
364    /**
365     * Returns a DecimalFormatSymbolsProvider for this LocaleProviderAdapter, or null if no
366     * DecimalFormatSymbolsProvider is available.
367     *
368     * @return a DecimalFormatSymbolsProvider
369     */
370    public abstract DecimalFormatSymbolsProvider getDecimalFormatSymbolsProvider();
371
372    /**
373     * Returns a NumberFormatProvider for this LocaleProviderAdapter, or null if no
374     * NumberFormatProvider is available.
375     *
376     * @return a NumberFormatProvider
377     */
378    public abstract NumberFormatProvider getNumberFormatProvider();
379
380    /*
381     * Getter methods for java.util.spi.* providers
382     */
383
384    /**
385     * Returns a CurrencyNameProvider for this LocaleProviderAdapter, or null if no
386     * CurrencyNameProvider is available.
387     *
388     * @return a CurrencyNameProvider
389     */
390    public abstract CurrencyNameProvider getCurrencyNameProvider();
391
392    /**
393     * Returns a LocaleNameProvider for this LocaleProviderAdapter, or null if no
394     * LocaleNameProvider is available.
395     *
396     * @return a LocaleNameProvider
397     */
398    public abstract LocaleNameProvider getLocaleNameProvider();
399
400    /**
401     * Returns a TimeZoneNameProvider for this LocaleProviderAdapter, or null if no
402     * TimeZoneNameProvider is available.
403     *
404     * @return a TimeZoneNameProvider
405     */
406    public abstract TimeZoneNameProvider getTimeZoneNameProvider();
407
408    /**
409     * Returns a CalendarDataProvider for this LocaleProviderAdapter, or null if no
410     * CalendarDataProvider is available.
411     *
412     * @return a CalendarDataProvider
413     */
414    public abstract CalendarDataProvider getCalendarDataProvider();
415
416    /**
417     * Returns a CalendarNameProvider for this LocaleProviderAdapter, or null if no
418     * CalendarNameProvider is available.
419     *
420     * @return a CalendarNameProvider
421     */
422    public abstract CalendarNameProvider getCalendarNameProvider();
423
424    /**
425     * Returns a CalendarProvider for this LocaleProviderAdapter, or null if no
426     * CalendarProvider is available.
427     *
428     * @return a CalendarProvider
429     */
430    public abstract CalendarProvider getCalendarProvider();
431
432    /**
433     * Returns a JavaTimeDateTimePatternProvider for this LocaleProviderAdapter,
434     * or null if no JavaTimeDateTimePatternProvider is available.
435     *
436     * @return a JavaTimeDateTimePatternProvider
437     */
438    public abstract JavaTimeDateTimePatternProvider getJavaTimeDateTimePatternProvider();
439
440    public abstract LocaleResources getLocaleResources(Locale locale);
441
442    public abstract Locale[] getAvailableLocales();
443
444    private static final LocaleProviderAdapter NONEXISTENT_ADAPTER = new NonExistentAdapter();
445    private static final class NonExistentAdapter extends FallbackLocaleProviderAdapter {
446        @Override
447        public LocaleProviderAdapter.Type getAdapterType() {
448            return null;
449        }
450
451        private NonExistentAdapter() {};
452    }
453}
454