LocaleServiceProviderPool.java revision 1672
342N/A/*
2018N/A * Copyright 2005-2006 Sun Microsystems, Inc. All Rights Reserved.
342N/A * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
342N/A *
342N/A * This code is free software; you can redistribute it and/or modify it
342N/A * under the terms of the GNU General Public License version 2 only, as
342N/A * published by the Free Software Foundation. Sun designates this
342N/A * particular file as subject to the "Classpath" exception as provided
342N/A * by Sun in the LICENSE file that accompanied this code.
342N/A *
342N/A * This code is distributed in the hope that it will be useful, but WITHOUT
342N/A * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
342N/A * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
342N/A * version 2 for more details (a copy is included in the LICENSE file that
342N/A * accompanied this code).
342N/A *
342N/A * You should have received a copy of the GNU General Public License version
342N/A * 2 along with this work; if not, write to the Free Software Foundation,
1472N/A * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
1472N/A *
1472N/A * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
342N/A * CA 95054 USA or visit www.sun.com if you need additional information or
342N/A * have any questions.
342N/A */
1879N/A
1879N/Apackage sun.util;
1879N/A
1879N/Aimport java.security.AccessController;
1879N/Aimport java.security.PrivilegedActionException;
1879N/Aimport java.security.PrivilegedExceptionAction;
1879N/Aimport java.util.Arrays;
1879N/Aimport java.util.HashSet;
1879N/Aimport java.util.Iterator;
1879N/Aimport java.util.LinkedHashSet;
1879N/Aimport java.util.List;
342N/Aimport java.util.Locale;
342N/Aimport java.util.Map;
342N/Aimport java.util.ServiceLoader;
342N/Aimport java.util.ServiceConfigurationError;
342N/Aimport java.util.Set;
342N/Aimport java.util.concurrent.ConcurrentHashMap;
342N/Aimport java.util.spi.LocaleServiceProvider;
342N/Aimport sun.util.logging.PlatformLogger;
342N/Aimport sun.util.resources.LocaleData;
342N/Aimport sun.util.resources.OpenListResourceBundle;
342N/A
342N/A/**
342N/A * An instance of this class holds a set of the third party implementations of a particular
342N/A * locale sensitive service, such as {@link java.util.spi.LocaleNameProvider}.
342N/A *
342N/A */
342N/Apublic final class LocaleServiceProviderPool {
2037N/A
2037N/A /**
2591N/A * A Map that holds singleton instances of this class. Each instance holds a
2591N/A * set of provider implementations of a particular locale sensitive service.
2591N/A */
2591N/A private static Map<Class, LocaleServiceProviderPool> poolOfPools =
2591N/A new ConcurrentHashMap<Class, LocaleServiceProviderPool>();
342N/A
342N/A /**
342N/A * A Set containing locale service providers that implement the
342N/A * specified provider SPI
342N/A */
342N/A private Set<LocaleServiceProvider> providers =
342N/A new LinkedHashSet<LocaleServiceProvider>();
342N/A
342N/A /**
342N/A * A Map that retains Locale->provider mapping
342N/A */
342N/A private Map<Locale, LocaleServiceProvider> providersCache =
342N/A new ConcurrentHashMap<Locale, LocaleServiceProvider>();
342N/A
342N/A /**
342N/A * Available locales for this locale sensitive service. This also contains
342N/A * JRE's available locales
342N/A */
342N/A private Set<Locale> availableLocales = null;
342N/A
342N/A /**
342N/A * Available locales within this JRE. Currently this is declared as
342N/A * static. This could be non-static later, so that they could have
342N/A * different sets for each locale sensitive services.
342N/A */
342N/A private static List<Locale> availableJRELocales = null;
342N/A
342N/A /**
342N/A * Provider locales for this locale sensitive service.
342N/A */
342N/A private Set<Locale> providerLocales = null;
342N/A
342N/A /**
342N/A * A factory method that returns a singleton instance
342N/A */
342N/A public static LocaleServiceProviderPool getPool(Class<? extends LocaleServiceProvider> providerClass) {
342N/A LocaleServiceProviderPool pool = poolOfPools.get(providerClass);
342N/A if (pool == null) {
342N/A LocaleServiceProviderPool newPool =
342N/A new LocaleServiceProviderPool(providerClass);
342N/A pool = poolOfPools.put(providerClass, newPool);
342N/A if (pool == null) {
342N/A pool = newPool;
342N/A }
342N/A }
342N/A
342N/A return pool;
342N/A }
342N/A
342N/A /**
342N/A * The sole constructor.
342N/A *
342N/A * @param c class of the locale sensitive service
342N/A */
342N/A private LocaleServiceProviderPool (final Class<? extends LocaleServiceProvider> c) {
342N/A try {
342N/A AccessController.doPrivileged(new PrivilegedExceptionAction<Object>() {
342N/A public Object run() {
342N/A for (LocaleServiceProvider provider : ServiceLoader.loadInstalled(c)) {
342N/A providers.add(provider);
342N/A }
342N/A return null;
342N/A }
342N/A });
342N/A } catch (PrivilegedActionException e) {
342N/A config(e.toString());
342N/A }
342N/A }
342N/A
342N/A private static void config(String message) {
342N/A PlatformLogger logger = PlatformLogger.getLogger("sun.util.LocaleServiceProviderPool");
342N/A logger.config(message);
342N/A }
342N/A
342N/A /**
342N/A * Lazy loaded set of available locales.
342N/A * Loading all locales is a very long operation.
342N/A */
342N/A private static class AllAvailableLocales {
342N/A /**
342N/A * Available locales for all locale sensitive services.
342N/A * This also contains JRE's available locales
342N/A */
342N/A static final Locale[] allAvailableLocales;
342N/A
342N/A static {
342N/A Class[] providerClasses = {
342N/A java.text.spi.BreakIteratorProvider.class,
342N/A java.text.spi.CollatorProvider.class,
342N/A java.text.spi.DateFormatProvider.class,
342N/A java.text.spi.DateFormatSymbolsProvider.class,
342N/A java.text.spi.DecimalFormatSymbolsProvider.class,
342N/A java.text.spi.NumberFormatProvider.class,
2280N/A java.util.spi.CurrencyNameProvider.class,
2280N/A java.util.spi.LocaleNameProvider.class,
2280N/A java.util.spi.TimeZoneNameProvider.class };
2280N/A Set<Locale> all = new HashSet<Locale>(Arrays.asList(
2280N/A LocaleData.getAvailableLocales())
2280N/A );
2280N/A for (Class providerClass : providerClasses) {
342N/A LocaleServiceProviderPool pool =
342N/A LocaleServiceProviderPool.getPool(providerClass);
342N/A all.addAll(pool.getProviderLocales());
342N/A }
342N/A allAvailableLocales = all.toArray(new Locale[0]);
342N/A }
342N/A }
342N/A
342N/A /**
342N/A * Returns an array of available locales for all the provider classes.
342N/A * This array is a merged array of all the locales that are provided by each
342N/A * provider, including the JRE.
342N/A *
342N/A * @return an array of the available locales for all provider classes
2280N/A */
2280N/A public static Locale[] getAllAvailableLocales() {
2280N/A return AllAvailableLocales.allAvailableLocales.clone();
2280N/A }
2280N/A
2280N/A /**
2280N/A * Returns an array of available locales. This array is a
2280N/A * merged array of all the locales that are provided by each
2280N/A * provider, including the JRE.
2280N/A *
2280N/A * @return an array of the available locales
356N/A */
356N/A public synchronized Locale[] getAvailableLocales() {
342N/A if (availableLocales == null) {
342N/A availableLocales = new HashSet<Locale>(getJRELocales());
342N/A if (hasProviders()) {
342N/A availableLocales.addAll(getProviderLocales());
342N/A }
342N/A }
342N/A Locale[] tmp = new Locale[availableLocales.size()];
342N/A availableLocales.toArray(tmp);
342N/A return tmp;
342N/A }
342N/A
342N/A /**
342N/A * Returns an array of available locales from providers.
2018N/A * Note that this method does not return a defensive copy.
2018N/A *
2018N/A * @return list of the provider locales
2018N/A */
2018N/A private synchronized Set<Locale> getProviderLocales() {
2018N/A if (providerLocales == null) {
2018N/A providerLocales = new HashSet<Locale>();
2018N/A if (hasProviders()) {
2018N/A for (LocaleServiceProvider lsp : providers) {
2018N/A Locale[] locales = lsp.getAvailableLocales();
2018N/A for (Locale locale: locales) {
2018N/A providerLocales.add(locale);
2018N/A }
342N/A }
342N/A }
342N/A }
342N/A return providerLocales;
342N/A }
342N/A
355N/A /**
355N/A * Returns whether any provider for this locale sensitive
355N/A * service is available or not.
355N/A *
355N/A * @return true if any provider is available
355N/A */
342N/A public boolean hasProviders() {
342N/A return !providers.isEmpty();
342N/A }
342N/A
342N/A /**
342N/A * Returns an array of available locales supported by the JRE.
342N/A * Note that this method does not return a defensive copy.
342N/A *
342N/A * @return list of the available JRE locales
342N/A */
342N/A private synchronized List<Locale> getJRELocales() {
342N/A if (availableJRELocales == null) {
2591N/A availableJRELocales =
2591N/A Arrays.asList(LocaleData.getAvailableLocales());
342N/A }
355N/A return availableJRELocales;
342N/A }
342N/A
342N/A /**
342N/A * Returns whether the given locale is supported by the JRE.
342N/A *
342N/A * @param locale the locale to test.
342N/A * @return true, if the locale is supported by the JRE. false
342N/A * otherwise.
342N/A */
342N/A private boolean isJRESupported(Locale locale) {
342N/A List<Locale> locales = getJRELocales();
342N/A return locales.contains(locale);
342N/A }
342N/A
342N/A /**
342N/A * Returns the provider's localized object for the specified
342N/A * locale.
342N/A *
342N/A * @param getter an object on which getObject() method
342N/A * is called to obtain the provider's instance.
342N/A * @param locale the given locale that is used as the starting one
342N/A * @param params provider specific parameters
796N/A * @return provider's instance, or null.
796N/A */
796N/A public <P, S> S getLocalizedObject(LocalizedObjectGetter<P, S> getter,
2037N/A Locale locale,
2037N/A Object... params) {
2037N/A return getLocalizedObjectImpl(getter, locale, true, null, null, null, params);
2037N/A }
2037N/A
2037N/A /**
2037N/A * Returns the provider's localized name for the specified
342N/A * locale.
342N/A *
342N/A * @param getter an object on which getObject() method
342N/A * is called to obtain the provider's instance.
342N/A * @param locale the given locale that is used as the starting one
342N/A * @param bundle JRE resource bundle that contains
342N/A * the localized names, or null for localized objects.
342N/A * @param key the key string if bundle is supplied, otherwise null.
342N/A * @param params provider specific parameters
342N/A * @return provider's instance, or null.
342N/A */
342N/A public <P, S> S getLocalizedObject(LocalizedObjectGetter<P, S> getter,
342N/A Locale locale,
342N/A OpenListResourceBundle bundle,
342N/A String key,
342N/A Object... params) {
342N/A return getLocalizedObjectImpl(getter, locale, false, null, bundle, key, params);
342N/A }
2591N/A
342N/A /**
342N/A * Returns the provider's localized name for the specified
1586N/A * locale.
342N/A *
342N/A * @param getter an object on which getObject() method
342N/A * is called to obtain the provider's instance.
342N/A * @param locale the given locale that is used as the starting one
342N/A * @param bundleKey JRE specific bundle key. e.g., "USD" is for currency
342N/A symbol and "usd" is for currency display name in the JRE bundle.
342N/A * @param bundle JRE resource bundle that contains
342N/A * the localized names, or null for localized objects.
342N/A * @param key the key string if bundle is supplied, otherwise null.
342N/A * @param params provider specific parameters
342N/A * @return provider's instance, or null.
342N/A */
342N/A public <P, S> S getLocalizedObject(LocalizedObjectGetter<P, S> getter,
342N/A Locale locale,
342N/A String bundleKey,
342N/A OpenListResourceBundle bundle,
342N/A String key,
342N/A Object... params) {
342N/A return getLocalizedObjectImpl(getter, locale, false, bundleKey, bundle, key, params);
342N/A }
342N/A
342N/A private <P, S> S getLocalizedObjectImpl(LocalizedObjectGetter<P, S> getter,
342N/A Locale locale,
342N/A boolean isObjectProvider,
342N/A String bundleKey,
342N/A OpenListResourceBundle bundle,
342N/A String key,
342N/A Object... params) {
342N/A if (hasProviders()) {
342N/A if (bundleKey == null) {
342N/A bundleKey = key;
342N/A }
342N/A Locale bundleLocale = (bundle != null ? bundle.getLocale() : null);
342N/A Locale requested = locale;
1394N/A P lsp;
1394N/A S providersObj = null;
1394N/A
1394N/A // check whether a provider has an implementation that's closer
1394N/A // to the requested locale than the bundle we've found (for
1394N/A // localized names), or Java runtime's supported locale
1394N/A // (for localized objects)
1394N/A while ((locale = findProviderLocale(locale, bundleLocale)) != null) {
1394N/A
1394N/A lsp = (P)findProvider(locale);
1394N/A
1394N/A if (lsp != null) {
1394N/A providersObj = getter.getObject(lsp, requested, key, params);
1394N/A if (providersObj != null) {
342N/A return providersObj;
342N/A } else if (isObjectProvider) {
2591N/A config(
2591N/A "A locale sensitive service provider returned null for a localized objects, which should not happen. provider: " + lsp + " locale: " + requested);
342N/A }
342N/A }
2822N/A
2822N/A locale = getParentLocale(locale);
2822N/A }
2822N/A
2822N/A // look up the JRE bundle and its parent chain. Only
2822N/A // providers for localized names are checked hereafter.
942N/A while (bundle != null) {
2816N/A bundleLocale = bundle.getLocale();
2816N/A
2816N/A if (bundle.handleGetKeys().contains(bundleKey)) {
2816N/A // JRE has it.
2816N/A return null;
942N/A } else {
942N/A lsp = (P)findProvider(bundleLocale);
942N/A if (lsp != null) {
942N/A providersObj = getter.getObject(lsp, requested, key, params);
942N/A if (providersObj != null) {
942N/A return providersObj;
942N/A }
342N/A }
355N/A }
2943N/A
2943N/A // try parent bundle
2943N/A bundle = bundle.getParent();
2943N/A }
2943N/A }
2943N/A
2943N/A // not found.
355N/A return null;
355N/A }
2019N/A
2019N/A /**
2019N/A * Returns a locale service provider instance that supports
2019N/A * the specified locale.
2019N/A *
2019N/A * @param locale the given locale
2019N/A * @return the provider, or null if there is
2019N/A * no provider available.
2019N/A */
342N/A private LocaleServiceProvider findProvider(Locale locale) {
342N/A if (!hasProviders()) {
2591N/A return null;
342N/A }
342N/A
342N/A if (providersCache.containsKey(locale)) {
2282N/A LocaleServiceProvider provider = providersCache.get(locale);
2282N/A if (provider != NullProvider.INSTANCE) {
2282N/A return provider;
2282N/A }
342N/A } else {
342N/A for (LocaleServiceProvider lsp : providers) {
342N/A Locale[] locales = lsp.getAvailableLocales();
342N/A for (Locale available: locales) {
2282N/A if (locale.equals(available)) {
2282N/A LocaleServiceProvider providerInCache =
342N/A providersCache.put(locale, lsp);
342N/A return (providerInCache != null ?
342N/A providerInCache :
342N/A lsp);
342N/A }
342N/A }
342N/A }
342N/A providersCache.put(locale, NullProvider.INSTANCE);
342N/A }
342N/A return null;
342N/A }
342N/A
342N/A /**
342N/A * Returns the provider's locale that is the most appropriate
342N/A * within the range
342N/A *
2939N/A * @param start the given locale that is used as the starting one
342N/A * @param end the given locale that is used as the end one (exclusive),
342N/A * or null if it reaching any of the JRE supported locale should
342N/A * terminate the look up.
342N/A * @return the most specific locale within the range, or null
342N/A * if no provider locale found in that range.
342N/A */
355N/A private Locale findProviderLocale(Locale start, Locale end) {
355N/A Set<Locale> provLoc = getProviderLocales();
355N/A Locale current = start;
342N/A
342N/A while (current != null) {
342N/A if (end != null) {
342N/A if (current.equals(end)) {
342N/A current = null;
2858N/A break;
2858N/A }
2858N/A } else {
2858N/A if (isJRESupported(current)) {
2858N/A current = null;
2858N/A break;
2858N/A }
2858N/A }
2018N/A
2018N/A if (provLoc.contains(current)) {
2018N/A break;
2018N/A }
2018N/A
2018N/A current = getParentLocale(current);
2018N/A }
2018N/A
2018N/A return current;
2018N/A }
2018N/A
2018N/A /**
2018N/A * Returns the parent locale.
2018N/A *
2018N/A * @param locale the locale
2018N/A * @return the parent locale
2018N/A */
2018N/A private static Locale getParentLocale(Locale locale) {
2018N/A String variant = locale.getVariant();
2018N/A if (variant != "") {
2018N/A int underscoreIndex = variant.lastIndexOf('_');
2018N/A if (underscoreIndex != (-1)) {
2018N/A return new Locale(locale.getLanguage(), locale.getCountry(),
2018N/A variant.substring(0, underscoreIndex));
342N/A } else {
2018N/A return new Locale(locale.getLanguage(), locale.getCountry());
2018N/A }
2018N/A } else if (locale.getCountry() != "") {
2018N/A return new Locale(locale.getLanguage());
342N/A } else if (locale.getLanguage() != "") {
2037N/A return Locale.ROOT;
2037N/A } else {
2037N/A return null;
342N/A }
342N/A }
342N/A
342N/A /**
342N/A * A dummy locale service provider that indicates there is no
342N/A * provider available
342N/A */
342N/A private static class NullProvider extends LocaleServiceProvider {
342N/A private static final NullProvider INSTANCE = new NullProvider();
342N/A
342N/A public Locale[] getAvailableLocales() {
342N/A throw new RuntimeException("Should not get called.");
342N/A }
342N/A }
342N/A
342N/A /**
342N/A * An interface to get a localized object for each locale sensitve
342N/A * service class.
342N/A */
342N/A public interface LocalizedObjectGetter<P, S> {
342N/A /**
342N/A * Returns an object from the provider
342N/A *
342N/A * @param lsp the provider
342N/A * @param locale the locale
2037N/A * @param key key string to localize, or null if the provider is not
342N/A * a name provider
2037N/A * @param params provider specific params
2037N/A * @return localized object from the provider
2037N/A */
2037N/A public S getObject(P lsp,
2037N/A Locale locale,
342N/A String key,
2037N/A Object... params);
2037N/A }
2037N/A}
2037N/A