View Javadoc
1   /*
2    * Copyright (c) 2005, 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  package sun.util.locale.provider;
27  
28  import java.util.ArrayList;
29  import java.util.Arrays;
30  import java.util.Collections;
31  import java.util.HashSet;
32  import java.util.IllformedLocaleException;
33  import java.util.List;
34  import java.util.Locale;
35  import java.util.Locale.Builder;
36  import java.util.ResourceBundle.Control;
37  import java.util.Set;
38  import java.util.concurrent.ConcurrentHashMap;
39  import java.util.concurrent.ConcurrentMap;
40  import java.util.spi.LocaleServiceProvider;
41  import sun.util.logging.PlatformLogger;
42  
43  /**
44   * An instance of this class holds a set of the third party implementations of a particular
45   * locale sensitive service, such as {@link java.util.spi.LocaleNameProvider}.
46   *
47   * @author Naoto Sato
48   * @author Masayoshi Okutsu
49   */
50  public final class LocaleServiceProviderPool {
51  
52      /**
53       * A Map that holds singleton instances of this class.  Each instance holds a
54       * set of provider implementations of a particular locale sensitive service.
55       */
56      private static ConcurrentMap<Class<? extends LocaleServiceProvider>, LocaleServiceProviderPool> poolOfPools =
57          new ConcurrentHashMap<>();
58  
59      /**
60       * A Map containing locale service providers that implement the
61       * specified provider SPI, keyed by a LocaleProviderAdapter.Type
62       */
63      private ConcurrentMap<LocaleProviderAdapter.Type, LocaleServiceProvider> providers =
64          new ConcurrentHashMap<>();
65  
66      /**
67       * A Map that retains Locale->provider mapping
68       */
69      private ConcurrentMap<Locale, List<LocaleProviderAdapter.Type>> providersCache =
70          new ConcurrentHashMap<>();
71  
72      /**
73       * Available locales for this locale sensitive service.  This also contains
74       * JRE's available locales
75       */
76      private Set<Locale> availableLocales = null;
77  
78      /**
79       * Provider class
80       */
81      private Class<? extends LocaleServiceProvider> providerClass;
82  
83      /**
84       * Array of all Locale Sensitive SPI classes.
85       *
86       * We know "spiClasses" contains classes that extends LocaleServiceProvider,
87       * but generic array creation is not allowed, thus the "unchecked" warning
88       * is suppressed here.
89       */
90      @SuppressWarnings("unchecked")
91      static final Class<LocaleServiceProvider>[] spiClasses =
92                  (Class<LocaleServiceProvider>[]) new Class<?>[] {
93          java.text.spi.BreakIteratorProvider.class,
94          java.text.spi.CollatorProvider.class,
95          java.text.spi.DateFormatProvider.class,
96          java.text.spi.DateFormatSymbolsProvider.class,
97          java.text.spi.DecimalFormatSymbolsProvider.class,
98          java.text.spi.NumberFormatProvider.class,
99          java.util.spi.CurrencyNameProvider.class,
100         java.util.spi.LocaleNameProvider.class,
101         java.util.spi.TimeZoneNameProvider.class,
102         java.util.spi.CalendarDataProvider.class
103     };
104 
105     /**
106      * A factory method that returns a singleton instance
107      */
108     public static LocaleServiceProviderPool getPool(Class<? extends LocaleServiceProvider> providerClass) {
109         LocaleServiceProviderPool pool = poolOfPools.get(providerClass);
110         if (pool == null) {
111             LocaleServiceProviderPool newPool =
112                 new LocaleServiceProviderPool(providerClass);
113             pool = poolOfPools.putIfAbsent(providerClass, newPool);
114             if (pool == null) {
115                 pool = newPool;
116             }
117         }
118 
119         return pool;
120     }
121 
122     /**
123      * The sole constructor.
124      *
125      * @param c class of the locale sensitive service
126      */
127     private LocaleServiceProviderPool (final Class<? extends LocaleServiceProvider> c) {
128         providerClass = c;
129 
130         for (LocaleProviderAdapter.Type type : LocaleProviderAdapter.getAdapterPreference()) {
131             LocaleProviderAdapter lda = LocaleProviderAdapter.forType(type);
132             if (lda != null) {
133                 LocaleServiceProvider provider = lda.getLocaleServiceProvider(c);
134                 if (provider != null) {
135                     providers.putIfAbsent(type, provider);
136                 }
137             }
138         }
139     }
140 
141     static void config(Class<? extends Object> caller, String message) {
142         PlatformLogger logger = PlatformLogger.getLogger(caller.getCanonicalName());
143         logger.config(message);
144     }
145 
146     /**
147      * Lazy loaded set of available locales.
148      * Loading all locales is a very long operation.
149      */
150     private static class AllAvailableLocales {
151         /**
152          * Available locales for all locale sensitive services.
153          * This also contains JRE's available locales
154          */
155         static final Locale[] allAvailableLocales;
156 
157         static {
158             Set<Locale> all = new HashSet<>();
159             for (Class<? extends LocaleServiceProvider> c : spiClasses) {
160                 LocaleServiceProviderPool pool =
161                     LocaleServiceProviderPool.getPool(c);
162                 all.addAll(pool.getAvailableLocaleSet());
163             }
164 
165             allAvailableLocales = all.toArray(new Locale[0]);
166         }
167 
168         // No instantiation
169         private AllAvailableLocales() {
170         }
171     }
172 
173     /**
174      * Returns an array of available locales for all the provider classes.
175      * This array is a merged array of all the locales that are provided by each
176      * provider, including the JRE.
177      *
178      * @return an array of the available locales for all provider classes
179      */
180     public static Locale[] getAllAvailableLocales() {
181         return AllAvailableLocales.allAvailableLocales.clone();
182     }
183 
184     /**
185      * Returns an array of available locales.  This array is a
186      * merged array of all the locales that are provided by each
187      * provider, including the JRE.
188      *
189      * @return an array of the available locales
190      */
191     public Locale[] getAvailableLocales() {
192         Set<Locale> locList = new HashSet<>();
193         locList.addAll(getAvailableLocaleSet());
194         // Make sure it all contains JRE's locales for compatibility.
195         locList.addAll(Arrays.asList(LocaleProviderAdapter.forJRE().getAvailableLocales()));
196         Locale[] tmp = new Locale[locList.size()];
197         locList.toArray(tmp);
198         return tmp;
199     }
200 
201     /**
202      * Returns the union of locale sets that are available from
203      * each service provider. This method does NOT return the
204      * defensive copy.
205      *
206      * @return a set of available locales
207      */
208     private synchronized Set<Locale> getAvailableLocaleSet() {
209         if (availableLocales == null) {
210             availableLocales = new HashSet<>();
211             for (LocaleServiceProvider lsp : providers.values()) {
212                 Locale[] locales = lsp.getAvailableLocales();
213                 for (Locale locale: locales) {
214                     availableLocales.add(getLookupLocale(locale));
215                 }
216             }
217         }
218 
219         return availableLocales;
220     }
221 
222     /**
223      * Returns whether any provider for this locale sensitive
224      * service is available or not, excluding JRE's one.
225      *
226      * @return true if any provider (other than JRE) is available
227      */
228     boolean hasProviders() {
229         return providers.size() != 1 ||
230                (providers.get(LocaleProviderAdapter.Type.JRE) == null &&
231                 providers.get(LocaleProviderAdapter.Type.FALLBACK) == null);
232     }
233 
234     /**
235      * Returns the provider's localized object for the specified
236      * locale.
237      *
238      * @param getter an object on which getObject() method
239      *     is called to obtain the provider's instance.
240      * @param locale the given locale that is used as the starting one
241      * @param params provider specific parameters
242      * @return provider's instance, or null.
243      */
244     public <P extends LocaleServiceProvider, S> S getLocalizedObject(LocalizedObjectGetter<P, S> getter,
245                                      Locale locale,
246                                      Object... params) {
247         return getLocalizedObjectImpl(getter, locale, true, null, params);
248     }
249 
250     /**
251      * Returns the provider's localized name for the specified
252      * locale.
253      *
254      * @param getter an object on which getObject() method
255      *     is called to obtain the provider's instance.
256      * @param locale the given locale that is used as the starting one
257      * @param key the key string for name providers
258      * @param params provider specific parameters
259      * @return provider's instance, or null.
260      */
261     public <P extends LocaleServiceProvider, S> S getLocalizedObject(LocalizedObjectGetter<P, S> getter,
262                                      Locale locale,
263                                      String key,
264                                      Object... params) {
265         return getLocalizedObjectImpl(getter, locale, false, key, params);
266     }
267 
268     @SuppressWarnings("unchecked")
269     private <P extends LocaleServiceProvider, S> S getLocalizedObjectImpl(LocalizedObjectGetter<P, S> getter,
270                                      Locale locale,
271                                      boolean isObjectProvider,
272                                      String key,
273                                      Object... params) {
274         if (locale == null) {
275             throw new NullPointerException();
276         }
277 
278         // Check whether JRE is the sole locale data provider or not,
279         // and directly call it if it is.
280         if (!hasProviders()) {
281             return getter.getObject((P)providers.get(LocaleProviderAdapter.defaultLocaleProviderAdapter),
282                                     locale, key, params);
283         }
284 
285         List<Locale> lookupLocales = getLookupLocales(locale);
286 
287         Set<Locale> available = getAvailableLocaleSet();
288         for (Locale current : lookupLocales) {
289             if (available.contains(current)) {
290                 S providersObj;
291 
292                 for (LocaleProviderAdapter.Type type: findProviders(current)) {
293                     LocaleServiceProvider lsp = providers.get(type);
294                     providersObj = getter.getObject((P)lsp, locale, key, params);
295                     if (providersObj != null) {
296                         return providersObj;
297                     } else if (isObjectProvider) {
298                         config(LocaleServiceProviderPool.class,
299                             "A locale sensitive service provider returned null for a localized objects,  which should not happen.  provider: "
300                                 + lsp + " locale: " + locale);
301                     }
302                 }
303             }
304         }
305 
306         // not found.
307         return null;
308     }
309 
310     /**
311      * Returns the list of locale service provider instances that support
312      * the specified locale.
313      *
314      * @param locale the given locale
315      * @return the list of locale data adapter types
316      */
317     private List<LocaleProviderAdapter.Type> findProviders(Locale locale) {
318         List<LocaleProviderAdapter.Type> providersList = providersCache.get(locale);
319         if (providersList == null) {
320             for (LocaleProviderAdapter.Type type : LocaleProviderAdapter.getAdapterPreference()) {
321                 LocaleServiceProvider lsp = providers.get(type);
322                 if (lsp != null) {
323                     if (lsp.isSupportedLocale(locale)) {
324                         if (providersList == null) {
325                             providersList = new ArrayList<>(2);
326                         }
327                         providersList.add(type);
328 
329                     }
330                 }
331             }
332             if (providersList == null) {
333                 providersList = NULL_LIST;
334             }
335             List<LocaleProviderAdapter.Type> val = providersCache.putIfAbsent(locale, providersList);
336             if (val != null) {
337                 providersList = val;
338             }
339         }
340             return providersList;
341         }
342 
343     /**
344      * Returns a list of candidate locales for service look up.
345      * @param locale the input locale
346      * @return the list of candidate locales for the given locale
347      */
348     static List<Locale> getLookupLocales(Locale locale) {
349         // Note: We currently use the default implementation of
350         // ResourceBundle.Control.getCandidateLocales. The result
351         // returned by getCandidateLocales are already normalized
352         // (no extensions) for service look up.
353         List<Locale> lookupLocales = Control.getNoFallbackControl(Control.FORMAT_DEFAULT)
354                                             .getCandidateLocales("", locale);
355         return lookupLocales;
356     }
357 
358     /**
359      * Returns an instance of Locale used for service look up.
360      * The result Locale has no extensions except for ja_JP_JP
361      * and th_TH_TH
362      *
363      * @param locale the locale
364      * @return the locale used for service look up
365      */
366     static Locale getLookupLocale(Locale locale) {
367         Locale lookupLocale = locale;
368         if (locale.hasExtensions()
369                 && !locale.equals(JRELocaleConstants.JA_JP_JP)
370                 && !locale.equals(JRELocaleConstants.TH_TH_TH)) {
371             // remove extensions
372             Builder locbld = new Builder();
373             try {
374                 locbld.setLocale(locale);
375                 locbld.clearExtensions();
376                 lookupLocale = locbld.build();
377             } catch (IllformedLocaleException e) {
378                 // A Locale with non-empty extensions
379                 // should have well-formed fields except
380                 // for ja_JP_JP and th_TH_TH. Therefore,
381                 // it should never enter in this catch clause.
382                 config(LocaleServiceProviderPool.class,
383                        "A locale(" + locale + ") has non-empty extensions, but has illformed fields.");
384 
385                 // Fallback - script field will be lost.
386                 lookupLocale = new Locale(locale.getLanguage(), locale.getCountry(), locale.getVariant());
387             }
388         }
389         return lookupLocale;
390     }
391 
392     /**
393      * A dummy locale service provider list that indicates there is no
394      * provider available
395      */
396     private static List<LocaleProviderAdapter.Type> NULL_LIST =
397         Collections.emptyList();
398 
399     /**
400      * An interface to get a localized object for each locale sensitive
401      * service class.
402      */
403     public interface LocalizedObjectGetter<P extends LocaleServiceProvider, S> {
404         /**
405          * Returns an object from the provider
406          *
407          * @param lsp the provider
408          * @param locale the locale
409          * @param key key string to localize, or null if the provider is not
410          *     a name provider
411          * @param params provider specific params
412          * @return localized object from the provider
413          */
414         public S getObject(P lsp,
415                            Locale locale,
416                            String key,
417                            Object... params);
418     }
419 }