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