• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2014 The Android Open Source Project
3  * Copyright (c) 2005, 2010, Oracle and/or its affiliates. All rights reserved.
4  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
5  *
6  * This code is free software; you can redistribute it and/or modify it
7  * under the terms of the GNU General Public License version 2 only, as
8  * published by the Free Software Foundation.  Oracle designates this
9  * particular file as subject to the "Classpath" exception as provided
10  * by Oracle in the LICENSE file that accompanied this code.
11  *
12  * This code is distributed in the hope that it will be useful, but WITHOUT
13  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
14  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
15  * version 2 for more details (a copy is included in the LICENSE file that
16  * accompanied this code).
17  *
18  * You should have received a copy of the GNU General Public License version
19  * 2 along with this work; if not, write to the Free Software Foundation,
20  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
21  *
22  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
23  * or visit www.oracle.com if you need additional information or have any
24  * questions.
25  */
26 
27 package sun.util;
28 
29 import java.security.AccessController;
30 import java.security.PrivilegedActionException;
31 import java.security.PrivilegedExceptionAction;
32 import java.util.ArrayList;
33 import java.util.HashSet;
34 import java.util.IllformedLocaleException;
35 import java.util.LinkedHashSet;
36 import java.util.List;
37 import java.util.Locale;
38 import java.util.Locale.Builder;
39 import java.util.Map;
40 import java.util.ResourceBundle.Control;
41 import java.util.ServiceLoader;
42 import java.util.Set;
43 import java.util.concurrent.ConcurrentHashMap;
44 import java.util.concurrent.ConcurrentMap;
45 import java.util.spi.LocaleServiceProvider;
46 import libcore.icu.ICU;
47 
48 import sun.util.logging.PlatformLogger;
49 import sun.util.resources.OpenListResourceBundle;
50 
51 /**
52  * An instance of this class holds a set of the third party implementations of a particular
53  * locale sensitive service, such as {@link java.util.spi.LocaleNameProvider}.
54  *
55  */
56 public final class LocaleServiceProviderPool {
57 
58     /**
59      * A Map that holds singleton instances of this class.  Each instance holds a
60      * set of provider implementations of a particular locale sensitive service.
61      */
62     private static ConcurrentMap<Class<? extends LocaleServiceProvider>, LocaleServiceProviderPool> poolOfPools =
63         new ConcurrentHashMap<>();
64 
65     /**
66      * A Set containing locale service providers that implement the
67      * specified provider SPI
68      */
69     private Set<LocaleServiceProvider> providers =
70         new LinkedHashSet<LocaleServiceProvider>();
71 
72     /**
73      * A Map that retains Locale->provider mapping
74      */
75     private Map<Locale, LocaleServiceProvider> providersCache =
76         new ConcurrentHashMap<Locale, LocaleServiceProvider>();
77 
78     /**
79      * Available locales for this locale sensitive service.  This also contains
80      * JRE's available locales
81      */
82     private Set<Locale> availableLocales = null;
83 
84     /**
85      * Available locales within this JRE.  Currently this is declared as
86      * static.  This could be non-static later, so that they could have
87      * different sets for each locale sensitive services.
88      */
89     private static volatile List<Locale> availableJRELocales = null;
90 
91     /**
92      * Provider locales for this locale sensitive service.
93      */
94     private Set<Locale> providerLocales = null;
95 
96     /**
97      * Special locale for ja_JP with Japanese calendar
98      */
99     private static Locale locale_ja_JP_JP = new Locale("ja", "JP", "JP");
100 
101     /**
102      * Special locale for th_TH with Thai numbering system
103      */
104     private static Locale locale_th_TH_TH = new Locale("th", "TH", "TH");
105 
106     /**
107      * A factory method that returns a singleton instance
108      */
getPool(Class<? extends LocaleServiceProvider> providerClass)109     public static LocaleServiceProviderPool getPool(Class<? extends LocaleServiceProvider> providerClass) {
110         LocaleServiceProviderPool pool = poolOfPools.get(providerClass);
111         if (pool == null) {
112             LocaleServiceProviderPool newPool =
113                 new LocaleServiceProviderPool(providerClass);
114             pool = poolOfPools.putIfAbsent(providerClass, newPool);
115             if (pool == null) {
116                 pool = newPool;
117             }
118         }
119 
120         return pool;
121     }
122 
123     /**
124      * The sole constructor.
125      *
126      * @param c class of the locale sensitive service
127      */
LocaleServiceProviderPool(final Class<? extends LocaleServiceProvider> c)128     private LocaleServiceProviderPool (final Class<? extends LocaleServiceProvider> c) {
129         try {
130             AccessController.doPrivileged(new PrivilegedExceptionAction<Object>() {
131                 public Object run() {
132                     for (LocaleServiceProvider provider : ServiceLoader.loadInstalled(c)) {
133                         providers.add(provider);
134                     }
135                     return null;
136                 }
137             });
138         }  catch (PrivilegedActionException e) {
139             config(e.toString());
140         }
141     }
142 
config(String message)143     private static void config(String message) {
144         PlatformLogger logger = PlatformLogger.getLogger("sun.util.LocaleServiceProviderPool");
145         logger.config(message);
146     }
147 
148     /**
149      * Lazy loaded set of available locales.
150      * Loading all locales is a very long operation.
151      *
152      * We know "providerClasses" contains classes that extends LocaleServiceProvider,
153      * but generic array creation is not allowed, thus the "unchecked" warning
154      * is suppressed here.
155      */
156     private static class AllAvailableLocales {
157         /**
158          * Available locales for all locale sensitive services.
159          * This also contains JRE's available locales
160          */
161         static final Locale[] allAvailableLocales;
162 
163         static {
164             @SuppressWarnings("unchecked")
165             Class<LocaleServiceProvider>[] providerClasses =
166                         (Class<LocaleServiceProvider>[]) new Class<?>[] {
167                 java.text.spi.BreakIteratorProvider.class,
168                 java.text.spi.CollatorProvider.class,
169                 java.text.spi.DateFormatProvider.class,
170                 java.text.spi.DateFormatSymbolsProvider.class,
171                 java.text.spi.DecimalFormatSymbolsProvider.class,
172                 java.text.spi.NumberFormatProvider.class,
173                 java.util.spi.CurrencyNameProvider.class,
174                 java.util.spi.LocaleNameProvider.class,
175                 java.util.spi.TimeZoneNameProvider.class,
176             };
177 
178             // Normalize locales for look up
179             Locale[] allLocales = ICU.getAvailableLocales();
180             Set<Locale> all = new HashSet<Locale>(allLocales.length);
181             for (Locale locale : allLocales) {
getLookupLocale(locale)182                 all.add(getLookupLocale(locale));
183             }
184 
185             for (Class<LocaleServiceProvider> providerClass : providerClasses) {
186                 LocaleServiceProviderPool pool =
187                     LocaleServiceProviderPool.getPool(providerClass);
pool.getProviderLocales()188                 all.addAll(pool.getProviderLocales());
189             }
190 
191             allAvailableLocales = all.toArray(new Locale[0]);
192         }
193     }
194 
195     /**
196      * Returns an array of available locales for all the provider classes.
197      * This array is a merged array of all the locales that are provided by each
198      * provider, including the JRE.
199      *
200      * @return an array of the available locales for all provider classes
201      */
getAllAvailableLocales()202     public static Locale[] getAllAvailableLocales() {
203         return AllAvailableLocales.allAvailableLocales.clone();
204     }
205 
206     /**
207      * Returns an array of available locales.  This array is a
208      * merged array of all the locales that are provided by each
209      * provider, including the JRE.
210      *
211      * @return an array of the available locales
212      */
getAvailableLocales()213     public synchronized Locale[] getAvailableLocales() {
214         if (availableLocales == null) {
215             availableLocales = new HashSet<Locale>(getJRELocales());
216             if (hasProviders()) {
217                 availableLocales.addAll(getProviderLocales());
218             }
219         }
220         Locale[] tmp = new Locale[availableLocales.size()];
221         availableLocales.toArray(tmp);
222         return tmp;
223     }
224 
225     /**
226      * Returns an array of available locales (already normalized
227      * for service lookup) from providers.
228      * Note that this method does not return a defensive copy.
229      *
230      * @return list of the provider locales
231      */
getProviderLocales()232     private synchronized Set<Locale> getProviderLocales() {
233         if (providerLocales == null) {
234             providerLocales = new HashSet<Locale>();
235             if (hasProviders()) {
236                 for (LocaleServiceProvider lsp : providers) {
237                     Locale[] locales = lsp.getAvailableLocales();
238                     for (Locale locale: locales) {
239                         providerLocales.add(getLookupLocale(locale));
240                     }
241                 }
242             }
243         }
244         return providerLocales;
245     }
246 
247     /**
248      * Returns whether any provider for this locale sensitive
249      * service is available or not.
250      *
251      * @return true if any provider is available
252      */
hasProviders()253     public boolean hasProviders() {
254         return !providers.isEmpty();
255     }
256 
257     /**
258      * Returns an array of available locales (already normalized for
259      * service lookup) supported by the JRE.
260      * Note that this method does not return a defensive copy.
261      *
262      * @return list of the available JRE locales
263      */
getJRELocales()264     private List<Locale> getJRELocales() {
265         if (availableJRELocales == null) {
266             synchronized (LocaleServiceProviderPool.class) {
267                 if (availableJRELocales == null) {
268                     Locale[] allLocales = ICU.getAvailableLocales();
269                     List<Locale> tmpList = new ArrayList<>(allLocales.length);
270                     for (Locale locale : allLocales) {
271                         tmpList.add(getLookupLocale(locale));
272                     }
273                     availableJRELocales = tmpList;
274                 }
275             }
276         }
277         return availableJRELocales;
278     }
279 
280     /**
281      * Returns whether the given locale is supported by the JRE.
282      *
283      * @param locale the locale to test.
284      * @return true, if the locale is supported by the JRE. false
285      *     otherwise.
286      */
isJRESupported(Locale locale)287     private boolean isJRESupported(Locale locale) {
288         List<Locale> locales = getJRELocales();
289         return locales.contains(getLookupLocale(locale));
290     }
291 
292     /**
293      * Returns the provider's localized object for the specified
294      * locale.
295      *
296      * @param getter an object on which getObject() method
297      *     is called to obtain the provider's instance.
298      * @param locale the given locale that is used as the starting one
299      * @param params provider specific parameters
300      * @return provider's instance, or null.
301      */
getLocalizedObject(LocalizedObjectGetter<P, S> getter, Locale locale, Object... params)302     public <P, S> S getLocalizedObject(LocalizedObjectGetter<P, S> getter,
303                                      Locale locale,
304                                      Object... params) {
305         return getLocalizedObjectImpl(getter, locale, true, null, null, null, params);
306     }
307 
308     /**
309      * Returns the provider's localized name for the specified
310      * locale.
311      *
312      * @param getter an object on which getObject() method
313      *     is called to obtain the provider's instance.
314      * @param locale the given locale that is used as the starting one
315      * @param bundle JRE resource bundle that contains
316      *     the localized names, or null for localized objects.
317      * @param key the key string if bundle is supplied, otherwise null.
318      * @param params provider specific parameters
319      * @return provider's instance, or null.
320      */
getLocalizedObject(LocalizedObjectGetter<P, S> getter, Locale locale, OpenListResourceBundle bundle, String key, Object... params)321     public <P, S> S getLocalizedObject(LocalizedObjectGetter<P, S> getter,
322                                      Locale locale,
323                                      OpenListResourceBundle bundle,
324                                      String key,
325                                      Object... params) {
326         return getLocalizedObjectImpl(getter, locale, false, null, bundle, key, params);
327     }
328 
329     /**
330      * Returns the provider's localized name for the specified
331      * locale.
332      *
333      * @param getter an object on which getObject() method
334      *     is called to obtain the provider's instance.
335      * @param locale the given locale that is used as the starting one
336      * @param bundleKey JRE specific bundle key. e.g., "USD" is for currency
337            symbol and "usd" is for currency display name in the JRE bundle.
338      * @param bundle JRE resource bundle that contains
339      *     the localized names, or null for localized objects.
340      * @param key the key string if bundle is supplied, otherwise null.
341      * @param params provider specific parameters
342      * @return provider's instance, or null.
343      */
getLocalizedObject(LocalizedObjectGetter<P, S> getter, Locale locale, String bundleKey, OpenListResourceBundle bundle, String key, Object... params)344     public <P, S> S getLocalizedObject(LocalizedObjectGetter<P, S> getter,
345                                      Locale locale,
346                                      String bundleKey,
347                                      OpenListResourceBundle bundle,
348                                      String key,
349                                      Object... params) {
350         return getLocalizedObjectImpl(getter, locale, false, bundleKey, bundle, key, params);
351     }
352 
getLocalizedObjectImpl(LocalizedObjectGetter<P, S> getter, Locale locale, boolean isObjectProvider, String bundleKey, OpenListResourceBundle bundle, String key, Object... params)353     private <P, S> S getLocalizedObjectImpl(LocalizedObjectGetter<P, S> getter,
354                                      Locale locale,
355                                      boolean isObjectProvider,
356                                      String bundleKey,
357                                      OpenListResourceBundle bundle,
358                                      String key,
359                                      Object... params) {
360         if (hasProviders()) {
361             if (bundleKey == null) {
362                 bundleKey = key;
363             }
364             Locale bundleLocale = (bundle != null ? bundle.getLocale() : null);
365             List<Locale> lookupLocales = getLookupLocales(locale);
366             S providersObj = null;
367 
368             // check whether a provider has an implementation that's closer
369             // to the requested locale than the bundle we've found (for
370             // localized names), or Java runtime's supported locale
371             // (for localized objects)
372             Set<Locale> provLoc = getProviderLocales();
373             for (int i = 0; i < lookupLocales.size(); i++) {
374                 Locale current = lookupLocales.get(i);
375                 if (bundleLocale != null) {
376                     if (current.equals(bundleLocale)) {
377                         break;
378                     }
379                 } else {
380                     if (isJRESupported(current)) {
381                         break;
382                     }
383                 }
384                 if (provLoc.contains(current)) {
385                     // It is safe to assume that findProvider() returns the instance of type P.
386                     @SuppressWarnings("unchecked")
387                     P lsp = (P)findProvider(current);
388                     if (lsp != null) {
389                         providersObj = getter.getObject(lsp, locale, key, params);
390                         if (providersObj != null) {
391                             return providersObj;
392                         } else if (isObjectProvider) {
393                             config(
394                                 "A locale sensitive service provider returned null for a localized objects,  which should not happen.  provider: " + lsp + " locale: " + locale);
395                         }
396                     }
397                 }
398             }
399 
400             // look up the JRE bundle and its parent chain.  Only
401             // providers for localized names are checked hereafter.
402             while (bundle != null) {
403                 bundleLocale = bundle.getLocale();
404 
405                 if (bundle.handleGetKeys().contains(bundleKey)) {
406                     // JRE has it.
407                     return null;
408                 } else {
409                     // It is safe to assume that findProvider() returns the instance of type P.
410                     @SuppressWarnings("unchecked")
411                     P lsp = (P)findProvider(bundleLocale);
412                     if (lsp != null) {
413                         providersObj = getter.getObject(lsp, locale, key, params);
414                         if (providersObj != null) {
415                             return providersObj;
416                         }
417                     }
418                 }
419 
420                 // try parent bundle
421                 bundle = bundle.getParent();
422             }
423         }
424 
425         // not found.
426         return null;
427     }
428 
429     /**
430      * Returns a locale service provider instance that supports
431      * the specified locale.
432      *
433      * @param locale the given locale
434      * @return the provider, or null if there is
435      *     no provider available.
436      */
findProvider(Locale locale)437     private LocaleServiceProvider findProvider(Locale locale) {
438         if (!hasProviders()) {
439             return null;
440         }
441 
442         if (providersCache.containsKey(locale)) {
443             LocaleServiceProvider provider = providersCache.get(locale);
444             if (provider != NullProvider.INSTANCE) {
445                 return provider;
446             }
447         } else {
448             for (LocaleServiceProvider lsp : providers) {
449                 Locale[] locales = lsp.getAvailableLocales();
450                 for (Locale available: locales) {
451                     // normalize
452                     available = getLookupLocale(available);
453                     if (locale.equals(available)) {
454                         LocaleServiceProvider providerInCache =
455                             providersCache.put(locale, lsp);
456                         return (providerInCache != null ?
457                                 providerInCache :
458                                 lsp);
459                     }
460                 }
461             }
462             providersCache.put(locale, NullProvider.INSTANCE);
463         }
464         return null;
465     }
466 
467     /**
468      * Returns a list of candidate locales for service look up.
469      * @param locale the input locale
470      * @return the list of candiate locales for the given locale
471      */
getLookupLocales(Locale locale)472     private static List<Locale> getLookupLocales(Locale locale) {
473         // Note: We currently use the default implementation of
474         // ResourceBundle.Control.getCandidateLocales. The result
475         // returned by getCandidateLocales are already normalized
476         // (no extensions) for service look up.
477         List<Locale> lookupLocales = new Control(){}.getCandidateLocales("", locale);
478         return lookupLocales;
479     }
480 
481     /**
482      * Returns an instance of Locale used for service look up.
483      * The result Locale has no extensions except for ja_JP_JP
484      * and th_TH_TH
485      *
486      * @param locale the locale
487      * @return the locale used for service look up
488      */
getLookupLocale(Locale locale)489     private static Locale getLookupLocale(Locale locale) {
490         Locale lookupLocale = locale;
491         Set<Character> extensions = locale.getExtensionKeys();
492         if (!extensions.isEmpty()
493                 && !locale.equals(locale_ja_JP_JP)
494                 && !locale.equals(locale_th_TH_TH)) {
495             // remove extensions
496             Builder locbld = new Builder();
497             try {
498                 locbld.setLocale(locale);
499                 locbld.clearExtensions();
500                 lookupLocale = locbld.build();
501             } catch (IllformedLocaleException e) {
502                 // A Locale with non-empty extensions
503                 // should have well-formed fields except
504                 // for ja_JP_JP and th_TH_TH. Therefore,
505                 // it should never enter in this catch clause.
506                 config("A locale(" + locale + ") has non-empty extensions, but has illformed fields.");
507 
508                 // Fallback - script field will be lost.
509                 lookupLocale = new Locale(locale.getLanguage(), locale.getCountry(), locale.getVariant());
510             }
511         }
512         return lookupLocale;
513     }
514 
515     /**
516      * A dummy locale service provider that indicates there is no
517      * provider available
518      */
519     private static class NullProvider extends LocaleServiceProvider {
520         private static final NullProvider INSTANCE = new NullProvider();
521 
getAvailableLocales()522         public Locale[] getAvailableLocales() {
523             throw new RuntimeException("Should not get called.");
524         }
525     }
526 
527     /**
528      * An interface to get a localized object for each locale sensitve
529      * service class.
530      */
531     public interface LocalizedObjectGetter<P, S> {
532         /**
533          * Returns an object from the provider
534          *
535          * @param lsp the provider
536          * @param locale the locale
537          * @param key key string to localize, or null if the provider is not
538          *     a name provider
539          * @param params provider specific params
540          * @return localized object from the provider
541          */
getObject(P lsp, Locale locale, String key, Object... params)542         public S getObject(P lsp,
543                                 Locale locale,
544                                 String key,
545                                 Object... params);
546     }
547 }
548