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