blob: 8fd21c9ce0a003ee4654a7edae946397a0575e51 [file] [log] [blame]
J. Duke319a3b92007-12-01 00:00:00 +00001/*
2 * Copyright 2005-2006 Sun Microsystems, Inc. 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. Sun designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Sun 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 Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
22 * CA 95054 USA or visit www.sun.com if you need additional information or
23 * have any questions.
24 */
25
26package sun.util;
27
28import java.security.AccessController;
29import java.security.PrivilegedActionException;
30import java.security.PrivilegedExceptionAction;
31import java.util.Arrays;
32import java.util.HashSet;
33import java.util.Iterator;
34import java.util.LinkedHashSet;
35import java.util.List;
36import java.util.Locale;
37import java.util.Map;
38import java.util.ServiceLoader;
39import java.util.ServiceConfigurationError;
40import java.util.Set;
41import java.util.concurrent.ConcurrentHashMap;
42import java.util.logging.Logger;
43import java.util.spi.LocaleServiceProvider;
44import sun.util.resources.LocaleData;
45import sun.util.resources.OpenListResourceBundle;
46
47/**
48 * An instance of this class holds a set of the third party implementations of a particular
49 * locale sensitive service, such as {@link java.util.spi.LocaleNameProvider}.
50 *
51 */
52public final class LocaleServiceProviderPool {
53
54 /**
55 * A Map that holds singleton instances of this class. Each instance holds a
56 * set of provider implementations of a particular locale sensitive service.
57 */
58 private static Map<Class, LocaleServiceProviderPool> poolOfPools =
59 new ConcurrentHashMap<Class, LocaleServiceProviderPool>();
60
61 /**
62 * A Set containing locale service providers that implement the
63 * specified provider SPI
64 */
65 private Set<LocaleServiceProvider> providers =
66 new LinkedHashSet<LocaleServiceProvider>();
67
68 /**
69 * A Map that retains Locale->provider mapping
70 */
71 private Map<Locale, LocaleServiceProvider> providersCache =
72 new ConcurrentHashMap<Locale, LocaleServiceProvider>();
73
74 /**
75 * Available locales for this locale sensitive service. This also contains
76 * JRE's available locales
77 */
78 private Set<Locale> availableLocales = null;
79
80 /**
81 * Available locales within this JRE. Currently this is declared as
82 * static. This could be non-static later, so that they could have
83 * different sets for each locale sensitive services.
84 */
85 private static List<Locale> availableJRELocales = null;
86
87 /**
88 * Provider locales for this locale sensitive service.
89 */
90 private Set<Locale> providerLocales = null;
91
92 /**
93 * A factory method that returns a singleton instance
94 */
95 public static LocaleServiceProviderPool getPool(Class<? extends LocaleServiceProvider> providerClass) {
96 LocaleServiceProviderPool pool = poolOfPools.get(providerClass);
97 if (pool == null) {
98 LocaleServiceProviderPool newPool =
99 new LocaleServiceProviderPool(providerClass);
100 pool = poolOfPools.put(providerClass, newPool);
101 if (pool == null) {
102 pool = newPool;
103 }
104 }
105
106 return pool;
107 }
108
109 /**
110 * The sole constructor.
111 *
112 * @param c class of the locale sensitive service
113 */
114 private LocaleServiceProviderPool (final Class<? extends LocaleServiceProvider> c) {
115 try {
116 AccessController.doPrivileged(new PrivilegedExceptionAction<Object>() {
117 public Object run() {
118 for (LocaleServiceProvider provider : ServiceLoader.loadInstalled(c)) {
119 providers.add(provider);
120 }
121 return null;
122 }
123 });
124 } catch (PrivilegedActionException e) {
125 Logger.getLogger("sun.util.LocaleServiceProviderPool").config(e.toString());
126 }
127 }
128
129 /**
130 * Lazy loaded set of available locales.
131 * Loading all locales is a very long operation.
132 */
133 private static class AllAvailableLocales {
134 /**
135 * Available locales for all locale sensitive services.
136 * This also contains JRE's available locales
137 */
138 static final Locale[] allAvailableLocales;
139
140 static {
141 Class[] providerClasses = {
142 java.text.spi.BreakIteratorProvider.class,
143 java.text.spi.CollatorProvider.class,
144 java.text.spi.DateFormatProvider.class,
145 java.text.spi.DateFormatSymbolsProvider.class,
146 java.text.spi.DecimalFormatSymbolsProvider.class,
147 java.text.spi.NumberFormatProvider.class,
148 java.util.spi.CurrencyNameProvider.class,
149 java.util.spi.LocaleNameProvider.class,
150 java.util.spi.TimeZoneNameProvider.class };
151 Set<Locale> all = new HashSet<Locale>(Arrays.asList(
152 LocaleData.getAvailableLocales())
153 );
154 for (Class providerClass : providerClasses) {
155 LocaleServiceProviderPool pool =
156 LocaleServiceProviderPool.getPool(providerClass);
157 all.addAll(pool.getProviderLocales());
158 }
159 allAvailableLocales = all.toArray(new Locale[0]);
160 }
161 }
162
163 /**
164 * Returns an array of available locales for all the provider classes.
165 * This array is a merged array of all the locales that are provided by each
166 * provider, including the JRE.
167 *
168 * @return an array of the available locales for all provider classes
169 */
170 public static Locale[] getAllAvailableLocales() {
171 return AllAvailableLocales.allAvailableLocales.clone();
172 }
173
174 /**
175 * Returns an array of available locales. This array is a
176 * merged array of all the locales that are provided by each
177 * provider, including the JRE.
178 *
179 * @return an array of the available locales
180 */
181 public synchronized Locale[] getAvailableLocales() {
182 if (availableLocales == null) {
183 availableLocales = new HashSet<Locale>(getJRELocales());
184 if (hasProviders()) {
185 availableLocales.addAll(getProviderLocales());
186 }
187 }
188 Locale[] tmp = new Locale[availableLocales.size()];
189 availableLocales.toArray(tmp);
190 return tmp;
191 }
192
193 /**
194 * Returns an array of available locales from providers.
195 * Note that this method does not return a defensive copy.
196 *
197 * @return list of the provider locales
198 */
199 private synchronized Set<Locale> getProviderLocales() {
200 if (providerLocales == null) {
201 providerLocales = new HashSet<Locale>();
202 if (hasProviders()) {
203 for (LocaleServiceProvider lsp : providers) {
204 Locale[] locales = lsp.getAvailableLocales();
205 for (Locale locale: locales) {
206 providerLocales.add(locale);
207 }
208 }
209 }
210 }
211 return providerLocales;
212 }
213
214 /**
215 * Returns whether any provider for this locale sensitive
216 * service is available or not.
217 *
218 * @return true if any provider is available
219 */
220 public boolean hasProviders() {
221 return !providers.isEmpty();
222 }
223
224 /**
225 * Returns an array of available locales supported by the JRE.
226 * Note that this method does not return a defensive copy.
227 *
228 * @return list of the available JRE locales
229 */
230 private synchronized List<Locale> getJRELocales() {
231 if (availableJRELocales == null) {
232 availableJRELocales =
233 Arrays.asList(LocaleData.getAvailableLocales());
234 }
235 return availableJRELocales;
236 }
237
238 /**
239 * Returns whether the given locale is supported by the JRE.
240 *
241 * @param locale the locale to test.
242 * @return true, if the locale is supported by the JRE. false
243 * otherwise.
244 */
245 private boolean isJRESupported(Locale locale) {
246 List<Locale> locales = getJRELocales();
247 return locales.contains(locale);
248 }
249
250 /**
251 * Returns the provider's localized object 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 params provider specific parameters
258 * @return provider's instance, or null.
259 */
260 public <P, S> S getLocalizedObject(LocalizedObjectGetter<P, S> getter,
261 Locale locale,
262 Object... params) {
263 return getLocalizedObjectImpl(getter, locale, true, null, null, null, params);
264 }
265
266 /**
267 * Returns the provider's localized name for the specified
268 * locale.
269 *
270 * @param getter an object on which getObject() method
271 * is called to obtain the provider's instance.
272 * @param locale the given locale that is used as the starting one
273 * @param bundle JRE resource bundle that contains
274 * the localized names, or null for localized objects.
275 * @param key the key string if bundle is supplied, otherwise null.
276 * @param params provider specific parameters
277 * @return provider's instance, or null.
278 */
279 public <P, S> S getLocalizedObject(LocalizedObjectGetter<P, S> getter,
280 Locale locale,
281 OpenListResourceBundle bundle,
282 String key,
283 Object... params) {
284 return getLocalizedObjectImpl(getter, locale, false, null, bundle, key, params);
285 }
286
287 /**
288 * Returns the provider's localized name for the specified
289 * locale.
290 *
291 * @param getter an object on which getObject() method
292 * is called to obtain the provider's instance.
293 * @param locale the given locale that is used as the starting one
294 * @param bundleKey JRE specific bundle key. e.g., "USD" is for currency
295 symbol and "usd" is for currency display name in the JRE bundle.
296 * @param bundle JRE resource bundle that contains
297 * the localized names, or null for localized objects.
298 * @param key the key string if bundle is supplied, otherwise null.
299 * @param params provider specific parameters
300 * @return provider's instance, or null.
301 */
302 public <P, S> S getLocalizedObject(LocalizedObjectGetter<P, S> getter,
303 Locale locale,
304 String bundleKey,
305 OpenListResourceBundle bundle,
306 String key,
307 Object... params) {
308 return getLocalizedObjectImpl(getter, locale, false, bundleKey, bundle, key, params);
309 }
310
311 private <P, S> S getLocalizedObjectImpl(LocalizedObjectGetter<P, S> getter,
312 Locale locale,
313 boolean isObjectProvider,
314 String bundleKey,
315 OpenListResourceBundle bundle,
316 String key,
317 Object... params) {
318 if (hasProviders()) {
319 if (bundleKey == null) {
320 bundleKey = key;
321 }
322 Locale bundleLocale = (bundle != null ? bundle.getLocale() : null);
323 Locale requested = locale;
324 P lsp;
325 S providersObj = null;
326
327 // check whether a provider has an implementation that's closer
328 // to the requested locale than the bundle we've found (for
329 // localized names), or Java runtime's supported locale
330 // (for localized objects)
331 while ((locale = findProviderLocale(locale, bundleLocale)) != null) {
332
333 lsp = (P)findProvider(locale);
334
335 if (lsp != null) {
336 providersObj = getter.getObject(lsp, requested, key, params);
337 if (providersObj != null) {
338 return providersObj;
339 } else if (isObjectProvider) {
340 Logger.getLogger("sun.util.LocaleServiceProviderPool").config(
341 "A locale sensitive service provider returned null for a localized objects, which should not happen. provider: " + lsp + " locale: " + requested);
342 }
343 }
344
345 locale = getParentLocale(locale);
346 }
347
348 // look up the JRE bundle and its parent chain. Only
349 // providers for localized names are checked hereafter.
350 while (bundle != null) {
351 bundleLocale = bundle.getLocale();
352
353 if (bundle.handleGetKeys().contains(bundleKey)) {
354 // JRE has it.
355 return null;
356 } else {
357 lsp = (P)findProvider(bundleLocale);
358 if (lsp != null) {
359 providersObj = getter.getObject(lsp, requested, key, params);
360 if (providersObj != null) {
361 return providersObj;
362 }
363 }
364 }
365
366 // try parent bundle
367 bundle = bundle.getParent();
368 }
369 }
370
371 // not found.
372 return null;
373 }
374
375 /**
376 * Returns a locale service provider instance that supports
377 * the specified locale.
378 *
379 * @param locale the given locale
380 * @return the provider, or null if there is
381 * no provider available.
382 */
383 private LocaleServiceProvider findProvider(Locale locale) {
384 if (!hasProviders()) {
385 return null;
386 }
387
388 if (providersCache.containsKey(locale)) {
389 LocaleServiceProvider provider = providersCache.get(locale);
390 if (provider != NullProvider.INSTANCE) {
391 return provider;
392 }
393 } else {
394 for (LocaleServiceProvider lsp : providers) {
395 Locale[] locales = lsp.getAvailableLocales();
396 for (Locale available: locales) {
397 if (locale.equals(available)) {
398 LocaleServiceProvider providerInCache =
399 providersCache.put(locale, lsp);
400 return (providerInCache != null ?
401 providerInCache :
402 lsp);
403 }
404 }
405 }
406 providersCache.put(locale, NullProvider.INSTANCE);
407 }
408 return null;
409 }
410
411 /**
412 * Returns the provider's locale that is the most appropriate
413 * within the range
414 *
415 * @param start the given locale that is used as the starting one
416 * @param end the given locale that is used as the end one (exclusive),
417 * or null if it reaching any of the JRE supported locale should
418 * terminate the look up.
419 * @return the most specific locale within the range, or null
420 * if no provider locale found in that range.
421 */
422 private Locale findProviderLocale(Locale start, Locale end) {
423 Set<Locale> provLoc = getProviderLocales();
424 Locale current = start;
425
426 while (current != null) {
427 if (end != null) {
428 if (current.equals(end)) {
429 current = null;
430 break;
431 }
432 } else {
433 if (isJRESupported(current)) {
434 current = null;
435 break;
436 }
437 }
438
439 if (provLoc.contains(current)) {
440 break;
441 }
442
443 current = getParentLocale(current);
444 }
445
446 return current;
447 }
448
449 /**
450 * Returns the parent locale.
451 *
452 * @param locale the locale
453 * @return the parent locale
454 */
455 private static Locale getParentLocale(Locale locale) {
456 String variant = locale.getVariant();
457 if (variant != "") {
458 int underscoreIndex = variant.lastIndexOf('_');
459 if (underscoreIndex != (-1)) {
460 return new Locale(locale.getLanguage(), locale.getCountry(),
461 variant.substring(0, underscoreIndex));
462 } else {
463 return new Locale(locale.getLanguage(), locale.getCountry());
464 }
465 } else if (locale.getCountry() != "") {
466 return new Locale(locale.getLanguage());
467 } else if (locale.getLanguage() != "") {
468 return Locale.ROOT;
469 } else {
470 return null;
471 }
472 }
473
474 /**
475 * A dummy locale service provider that indicates there is no
476 * provider available
477 */
478 private static class NullProvider extends LocaleServiceProvider {
479 private static final NullProvider INSTANCE = new NullProvider();
480
481 public Locale[] getAvailableLocales() {
482 throw new RuntimeException("Should not get called.");
483 }
484 }
485
486 /**
487 * An interface to get a localized object for each locale sensitve
488 * service class.
489 */
490 public interface LocalizedObjectGetter<P, S> {
491 /**
492 * Returns an object from the provider
493 *
494 * @param lsp the provider
495 * @param locale the locale
496 * @param key key string to localize, or null if the provider is not
497 * a name provider
498 * @param params provider specific params
499 * @return localized object from the provider
500 */
501 public S getObject(P lsp,
502 Locale locale,
503 String key,
504 Object... params);
505 }
506}