J. Duke | 319a3b9 | 2007-12-01 00:00:00 +0000 | [diff] [blame^] | 1 | /* |
| 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 | |
| 26 | package sun.util; |
| 27 | |
| 28 | import java.security.AccessController; |
| 29 | import java.security.PrivilegedActionException; |
| 30 | import java.security.PrivilegedExceptionAction; |
| 31 | import java.util.Arrays; |
| 32 | import java.util.HashSet; |
| 33 | import java.util.Iterator; |
| 34 | import java.util.LinkedHashSet; |
| 35 | import java.util.List; |
| 36 | import java.util.Locale; |
| 37 | import java.util.Map; |
| 38 | import java.util.ServiceLoader; |
| 39 | import java.util.ServiceConfigurationError; |
| 40 | import java.util.Set; |
| 41 | import java.util.concurrent.ConcurrentHashMap; |
| 42 | import java.util.logging.Logger; |
| 43 | import java.util.spi.LocaleServiceProvider; |
| 44 | import sun.util.resources.LocaleData; |
| 45 | import 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 | */ |
| 52 | public 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 | } |