0N/A/*
2712N/A * Copyright (c) 2005, 2010, Oracle and/or its affiliates. All rights reserved.
0N/A * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
0N/A *
0N/A * This code is free software; you can redistribute it and/or modify it
0N/A * under the terms of the GNU General Public License version 2 only, as
2362N/A * published by the Free Software Foundation. Oracle designates this
0N/A * particular file as subject to the "Classpath" exception as provided
2362N/A * by Oracle in the LICENSE file that accompanied this code.
0N/A *
0N/A * This code is distributed in the hope that it will be useful, but WITHOUT
0N/A * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
0N/A * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
0N/A * version 2 for more details (a copy is included in the LICENSE file that
0N/A * accompanied this code).
0N/A *
0N/A * You should have received a copy of the GNU General Public License version
0N/A * 2 along with this work; if not, write to the Free Software Foundation,
0N/A * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
0N/A *
2362N/A * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
2362N/A * or visit www.oracle.com if you need additional information or have any
2362N/A * questions.
0N/A */
0N/A
0N/Apackage sun.util;
0N/A
0N/Aimport java.security.AccessController;
0N/Aimport java.security.PrivilegedActionException;
0N/Aimport java.security.PrivilegedExceptionAction;
2712N/Aimport java.util.ArrayList;
0N/Aimport java.util.HashSet;
2712N/Aimport java.util.IllformedLocaleException;
0N/Aimport java.util.LinkedHashSet;
0N/Aimport java.util.List;
0N/Aimport java.util.Locale;
2712N/Aimport java.util.Locale.Builder;
0N/Aimport java.util.Map;
2712N/Aimport java.util.ResourceBundle.Control;
0N/Aimport java.util.ServiceLoader;
0N/Aimport java.util.Set;
0N/Aimport java.util.concurrent.ConcurrentHashMap;
4564N/Aimport java.util.concurrent.ConcurrentMap;
0N/Aimport java.util.spi.LocaleServiceProvider;
2712N/A
1672N/Aimport sun.util.logging.PlatformLogger;
0N/Aimport sun.util.resources.LocaleData;
0N/Aimport sun.util.resources.OpenListResourceBundle;
0N/A
0N/A/**
0N/A * An instance of this class holds a set of the third party implementations of a particular
0N/A * locale sensitive service, such as {@link java.util.spi.LocaleNameProvider}.
0N/A *
0N/A */
0N/Apublic final class LocaleServiceProviderPool {
0N/A
0N/A /**
0N/A * A Map that holds singleton instances of this class. Each instance holds a
0N/A * set of provider implementations of a particular locale sensitive service.
0N/A */
4565N/A private static ConcurrentMap<Class<? extends LocaleServiceProvider>, LocaleServiceProviderPool> poolOfPools =
4564N/A new ConcurrentHashMap<>();
0N/A
0N/A /**
0N/A * A Set containing locale service providers that implement the
0N/A * specified provider SPI
0N/A */
0N/A private Set<LocaleServiceProvider> providers =
0N/A new LinkedHashSet<LocaleServiceProvider>();
0N/A
0N/A /**
0N/A * A Map that retains Locale->provider mapping
0N/A */
0N/A private Map<Locale, LocaleServiceProvider> providersCache =
0N/A new ConcurrentHashMap<Locale, LocaleServiceProvider>();
0N/A
0N/A /**
0N/A * Available locales for this locale sensitive service. This also contains
0N/A * JRE's available locales
0N/A */
0N/A private Set<Locale> availableLocales = null;
0N/A
0N/A /**
0N/A * Available locales within this JRE. Currently this is declared as
0N/A * static. This could be non-static later, so that they could have
0N/A * different sets for each locale sensitive services.
0N/A */
2869N/A private static volatile List<Locale> availableJRELocales = null;
0N/A
0N/A /**
0N/A * Provider locales for this locale sensitive service.
0N/A */
0N/A private Set<Locale> providerLocales = null;
0N/A
0N/A /**
2712N/A * Special locale for ja_JP with Japanese calendar
2712N/A */
2712N/A private static Locale locale_ja_JP_JP = new Locale("ja", "JP", "JP");
2712N/A
2712N/A /**
2712N/A * Special locale for th_TH with Thai numbering system
2712N/A */
2712N/A private static Locale locale_th_TH_TH = new Locale("th", "TH", "TH");
2712N/A
2712N/A /**
0N/A * A factory method that returns a singleton instance
0N/A */
0N/A public static LocaleServiceProviderPool getPool(Class<? extends LocaleServiceProvider> providerClass) {
0N/A LocaleServiceProviderPool pool = poolOfPools.get(providerClass);
0N/A if (pool == null) {
0N/A LocaleServiceProviderPool newPool =
0N/A new LocaleServiceProviderPool(providerClass);
4564N/A pool = poolOfPools.putIfAbsent(providerClass, newPool);
0N/A if (pool == null) {
0N/A pool = newPool;
0N/A }
0N/A }
0N/A
0N/A return pool;
0N/A }
0N/A
0N/A /**
0N/A * The sole constructor.
0N/A *
0N/A * @param c class of the locale sensitive service
0N/A */
0N/A private LocaleServiceProviderPool (final Class<? extends LocaleServiceProvider> c) {
0N/A try {
0N/A AccessController.doPrivileged(new PrivilegedExceptionAction<Object>() {
0N/A public Object run() {
0N/A for (LocaleServiceProvider provider : ServiceLoader.loadInstalled(c)) {
0N/A providers.add(provider);
0N/A }
0N/A return null;
0N/A }
0N/A });
0N/A } catch (PrivilegedActionException e) {
1672N/A config(e.toString());
0N/A }
0N/A }
0N/A
1672N/A private static void config(String message) {
1672N/A PlatformLogger logger = PlatformLogger.getLogger("sun.util.LocaleServiceProviderPool");
1672N/A logger.config(message);
1672N/A }
1672N/A
0N/A /**
0N/A * Lazy loaded set of available locales.
0N/A * Loading all locales is a very long operation.
4565N/A *
4565N/A * We know "providerClasses" contains classes that extends LocaleServiceProvider,
4565N/A * but generic array creation is not allowed, thus the "unchecked" warning
4565N/A * is suppressed here.
0N/A */
0N/A private static class AllAvailableLocales {
0N/A /**
0N/A * Available locales for all locale sensitive services.
0N/A * This also contains JRE's available locales
0N/A */
0N/A static final Locale[] allAvailableLocales;
0N/A
0N/A static {
4565N/A @SuppressWarnings("unchecked")
4565N/A Class<LocaleServiceProvider>[] providerClasses =
4565N/A (Class<LocaleServiceProvider>[]) new Class<?>[] {
0N/A java.text.spi.BreakIteratorProvider.class,
0N/A java.text.spi.CollatorProvider.class,
0N/A java.text.spi.DateFormatProvider.class,
0N/A java.text.spi.DateFormatSymbolsProvider.class,
0N/A java.text.spi.DecimalFormatSymbolsProvider.class,
0N/A java.text.spi.NumberFormatProvider.class,
0N/A java.util.spi.CurrencyNameProvider.class,
0N/A java.util.spi.LocaleNameProvider.class,
0N/A java.util.spi.TimeZoneNameProvider.class };
2712N/A
2712N/A // Normalize locales for look up
2712N/A Locale[] allLocales = LocaleData.getAvailableLocales();
2712N/A Set<Locale> all = new HashSet<Locale>(allLocales.length);
2712N/A for (Locale locale : allLocales) {
2712N/A all.add(getLookupLocale(locale));
2712N/A }
2712N/A
4565N/A for (Class<LocaleServiceProvider> providerClass : providerClasses) {
0N/A LocaleServiceProviderPool pool =
0N/A LocaleServiceProviderPool.getPool(providerClass);
0N/A all.addAll(pool.getProviderLocales());
0N/A }
2712N/A
0N/A allAvailableLocales = all.toArray(new Locale[0]);
0N/A }
0N/A }
0N/A
0N/A /**
0N/A * Returns an array of available locales for all the provider classes.
0N/A * This array is a merged array of all the locales that are provided by each
0N/A * provider, including the JRE.
0N/A *
0N/A * @return an array of the available locales for all provider classes
0N/A */
0N/A public static Locale[] getAllAvailableLocales() {
0N/A return AllAvailableLocales.allAvailableLocales.clone();
0N/A }
0N/A
0N/A /**
0N/A * Returns an array of available locales. This array is a
0N/A * merged array of all the locales that are provided by each
0N/A * provider, including the JRE.
0N/A *
0N/A * @return an array of the available locales
0N/A */
0N/A public synchronized Locale[] getAvailableLocales() {
0N/A if (availableLocales == null) {
0N/A availableLocales = new HashSet<Locale>(getJRELocales());
0N/A if (hasProviders()) {
0N/A availableLocales.addAll(getProviderLocales());
0N/A }
0N/A }
0N/A Locale[] tmp = new Locale[availableLocales.size()];
0N/A availableLocales.toArray(tmp);
0N/A return tmp;
0N/A }
0N/A
0N/A /**
2712N/A * Returns an array of available locales (already normalized
2712N/A * for service lookup) from providers.
0N/A * Note that this method does not return a defensive copy.
0N/A *
0N/A * @return list of the provider locales
0N/A */
0N/A private synchronized Set<Locale> getProviderLocales() {
0N/A if (providerLocales == null) {
0N/A providerLocales = new HashSet<Locale>();
0N/A if (hasProviders()) {
0N/A for (LocaleServiceProvider lsp : providers) {
0N/A Locale[] locales = lsp.getAvailableLocales();
0N/A for (Locale locale: locales) {
2712N/A providerLocales.add(getLookupLocale(locale));
0N/A }
0N/A }
0N/A }
0N/A }
0N/A return providerLocales;
0N/A }
0N/A
0N/A /**
0N/A * Returns whether any provider for this locale sensitive
0N/A * service is available or not.
0N/A *
0N/A * @return true if any provider is available
0N/A */
0N/A public boolean hasProviders() {
0N/A return !providers.isEmpty();
0N/A }
0N/A
0N/A /**
2712N/A * Returns an array of available locales (already normalized for
2712N/A * service lookup) supported by the JRE.
0N/A * Note that this method does not return a defensive copy.
0N/A *
0N/A * @return list of the available JRE locales
0N/A */
2869N/A private List<Locale> getJRELocales() {
0N/A if (availableJRELocales == null) {
2869N/A synchronized (LocaleServiceProviderPool.class) {
2869N/A if (availableJRELocales == null) {
2869N/A Locale[] allLocales = LocaleData.getAvailableLocales();
4564N/A List<Locale> tmpList = new ArrayList<>(allLocales.length);
2869N/A for (Locale locale : allLocales) {
4564N/A tmpList.add(getLookupLocale(locale));
2869N/A }
4564N/A availableJRELocales = tmpList;
2869N/A }
2712N/A }
0N/A }
0N/A return availableJRELocales;
0N/A }
0N/A
0N/A /**
0N/A * Returns whether the given locale is supported by the JRE.
0N/A *
0N/A * @param locale the locale to test.
0N/A * @return true, if the locale is supported by the JRE. false
0N/A * otherwise.
0N/A */
0N/A private boolean isJRESupported(Locale locale) {
0N/A List<Locale> locales = getJRELocales();
2712N/A return locales.contains(getLookupLocale(locale));
0N/A }
0N/A
0N/A /**
0N/A * Returns the provider's localized object for the specified
0N/A * locale.
0N/A *
0N/A * @param getter an object on which getObject() method
0N/A * is called to obtain the provider's instance.
0N/A * @param locale the given locale that is used as the starting one
0N/A * @param params provider specific parameters
0N/A * @return provider's instance, or null.
0N/A */
0N/A public <P, S> S getLocalizedObject(LocalizedObjectGetter<P, S> getter,
0N/A Locale locale,
0N/A Object... params) {
0N/A return getLocalizedObjectImpl(getter, locale, true, null, null, null, params);
0N/A }
0N/A
0N/A /**
0N/A * Returns the provider's localized name for the specified
0N/A * locale.
0N/A *
0N/A * @param getter an object on which getObject() method
0N/A * is called to obtain the provider's instance.
0N/A * @param locale the given locale that is used as the starting one
0N/A * @param bundle JRE resource bundle that contains
0N/A * the localized names, or null for localized objects.
0N/A * @param key the key string if bundle is supplied, otherwise null.
0N/A * @param params provider specific parameters
0N/A * @return provider's instance, or null.
0N/A */
0N/A public <P, S> S getLocalizedObject(LocalizedObjectGetter<P, S> getter,
0N/A Locale locale,
0N/A OpenListResourceBundle bundle,
0N/A String key,
0N/A Object... params) {
0N/A return getLocalizedObjectImpl(getter, locale, false, null, bundle, key, params);
0N/A }
0N/A
0N/A /**
0N/A * Returns the provider's localized name for the specified
0N/A * locale.
0N/A *
0N/A * @param getter an object on which getObject() method
0N/A * is called to obtain the provider's instance.
0N/A * @param locale the given locale that is used as the starting one
0N/A * @param bundleKey JRE specific bundle key. e.g., "USD" is for currency
0N/A symbol and "usd" is for currency display name in the JRE bundle.
0N/A * @param bundle JRE resource bundle that contains
0N/A * the localized names, or null for localized objects.
0N/A * @param key the key string if bundle is supplied, otherwise null.
0N/A * @param params provider specific parameters
0N/A * @return provider's instance, or null.
0N/A */
0N/A public <P, S> S getLocalizedObject(LocalizedObjectGetter<P, S> getter,
0N/A Locale locale,
0N/A String bundleKey,
0N/A OpenListResourceBundle bundle,
0N/A String key,
0N/A Object... params) {
0N/A return getLocalizedObjectImpl(getter, locale, false, bundleKey, bundle, key, params);
0N/A }
0N/A
0N/A private <P, S> S getLocalizedObjectImpl(LocalizedObjectGetter<P, S> getter,
0N/A Locale locale,
0N/A boolean isObjectProvider,
0N/A String bundleKey,
0N/A OpenListResourceBundle bundle,
0N/A String key,
0N/A Object... params) {
0N/A if (hasProviders()) {
0N/A if (bundleKey == null) {
0N/A bundleKey = key;
0N/A }
0N/A Locale bundleLocale = (bundle != null ? bundle.getLocale() : null);
2712N/A List<Locale> lookupLocales = getLookupLocales(locale);
0N/A S providersObj = null;
0N/A
0N/A // check whether a provider has an implementation that's closer
0N/A // to the requested locale than the bundle we've found (for
0N/A // localized names), or Java runtime's supported locale
0N/A // (for localized objects)
2712N/A Set<Locale> provLoc = getProviderLocales();
2712N/A for (int i = 0; i < lookupLocales.size(); i++) {
2712N/A Locale current = lookupLocales.get(i);
2712N/A if (bundleLocale != null) {
2712N/A if (current.equals(bundleLocale)) {
2712N/A break;
2712N/A }
2712N/A } else {
2712N/A if (isJRESupported(current)) {
2712N/A break;
0N/A }
0N/A }
2712N/A if (provLoc.contains(current)) {
4565N/A // It is safe to assume that findProvider() returns the instance of type P.
4565N/A @SuppressWarnings("unchecked")
4565N/A P lsp = (P)findProvider(current);
2712N/A if (lsp != null) {
2712N/A providersObj = getter.getObject(lsp, locale, key, params);
2712N/A if (providersObj != null) {
2712N/A return providersObj;
2712N/A } else if (isObjectProvider) {
2712N/A config(
2712N/A "A locale sensitive service provider returned null for a localized objects, which should not happen. provider: " + lsp + " locale: " + locale);
2712N/A }
2712N/A }
2712N/A }
0N/A }
0N/A
0N/A // look up the JRE bundle and its parent chain. Only
0N/A // providers for localized names are checked hereafter.
0N/A while (bundle != null) {
0N/A bundleLocale = bundle.getLocale();
0N/A
0N/A if (bundle.handleGetKeys().contains(bundleKey)) {
0N/A // JRE has it.
0N/A return null;
0N/A } else {
4565N/A // It is safe to assume that findProvider() returns the instance of type P.
4565N/A @SuppressWarnings("unchecked")
4565N/A P lsp = (P)findProvider(bundleLocale);
0N/A if (lsp != null) {
2712N/A providersObj = getter.getObject(lsp, locale, key, params);
0N/A if (providersObj != null) {
0N/A return providersObj;
0N/A }
0N/A }
0N/A }
0N/A
0N/A // try parent bundle
0N/A bundle = bundle.getParent();
0N/A }
0N/A }
0N/A
0N/A // not found.
0N/A return null;
0N/A }
0N/A
0N/A /**
0N/A * Returns a locale service provider instance that supports
0N/A * the specified locale.
0N/A *
0N/A * @param locale the given locale
0N/A * @return the provider, or null if there is
0N/A * no provider available.
0N/A */
0N/A private LocaleServiceProvider findProvider(Locale locale) {
0N/A if (!hasProviders()) {
0N/A return null;
0N/A }
0N/A
0N/A if (providersCache.containsKey(locale)) {
0N/A LocaleServiceProvider provider = providersCache.get(locale);
0N/A if (provider != NullProvider.INSTANCE) {
0N/A return provider;
0N/A }
0N/A } else {
0N/A for (LocaleServiceProvider lsp : providers) {
0N/A Locale[] locales = lsp.getAvailableLocales();
0N/A for (Locale available: locales) {
2712N/A // normalize
2712N/A available = getLookupLocale(available);
0N/A if (locale.equals(available)) {
0N/A LocaleServiceProvider providerInCache =
0N/A providersCache.put(locale, lsp);
0N/A return (providerInCache != null ?
0N/A providerInCache :
0N/A lsp);
0N/A }
0N/A }
0N/A }
0N/A providersCache.put(locale, NullProvider.INSTANCE);
0N/A }
0N/A return null;
0N/A }
0N/A
0N/A /**
2712N/A * Returns a list of candidate locales for service look up.
2712N/A * @param locale the input locale
2712N/A * @return the list of candiate locales for the given locale
0N/A */
2712N/A private static List<Locale> getLookupLocales(Locale locale) {
2712N/A // Note: We currently use the default implementation of
2712N/A // ResourceBundle.Control.getCandidateLocales. The result
2712N/A // returned by getCandidateLocales are already normalized
2712N/A // (no extensions) for service look up.
2712N/A List<Locale> lookupLocales = new Control(){}.getCandidateLocales("", locale);
2712N/A return lookupLocales;
0N/A }
0N/A
0N/A /**
2712N/A * Returns an instance of Locale used for service look up.
2712N/A * The result Locale has no extensions except for ja_JP_JP
2712N/A * and th_TH_TH
0N/A *
0N/A * @param locale the locale
2712N/A * @return the locale used for service look up
0N/A */
2712N/A private static Locale getLookupLocale(Locale locale) {
2712N/A Locale lookupLocale = locale;
2712N/A Set<Character> extensions = locale.getExtensionKeys();
2712N/A if (!extensions.isEmpty()
2712N/A && !locale.equals(locale_ja_JP_JP)
2712N/A && !locale.equals(locale_th_TH_TH)) {
2712N/A // remove extensions
2712N/A Builder locbld = new Builder();
2712N/A try {
2712N/A locbld.setLocale(locale);
2712N/A locbld.clearExtensions();
2712N/A lookupLocale = locbld.build();
2712N/A } catch (IllformedLocaleException e) {
2712N/A // A Locale with non-empty extensions
2712N/A // should have well-formed fields except
2712N/A // for ja_JP_JP and th_TH_TH. Therefore,
2712N/A // it should never enter in this catch clause.
2712N/A config("A locale(" + locale + ") has non-empty extensions, but has illformed fields.");
2712N/A
2712N/A // Fallback - script field will be lost.
2712N/A lookupLocale = new Locale(locale.getLanguage(), locale.getCountry(), locale.getVariant());
0N/A }
0N/A }
2712N/A return lookupLocale;
0N/A }
0N/A
0N/A /**
0N/A * A dummy locale service provider that indicates there is no
0N/A * provider available
0N/A */
0N/A private static class NullProvider extends LocaleServiceProvider {
0N/A private static final NullProvider INSTANCE = new NullProvider();
0N/A
0N/A public Locale[] getAvailableLocales() {
0N/A throw new RuntimeException("Should not get called.");
0N/A }
0N/A }
0N/A
0N/A /**
0N/A * An interface to get a localized object for each locale sensitve
0N/A * service class.
0N/A */
0N/A public interface LocalizedObjectGetter<P, S> {
0N/A /**
0N/A * Returns an object from the provider
0N/A *
0N/A * @param lsp the provider
0N/A * @param locale the locale
0N/A * @param key key string to localize, or null if the provider is not
0N/A * a name provider
0N/A * @param params provider specific params
0N/A * @return localized object from the provider
0N/A */
0N/A public S getObject(P lsp,
0N/A Locale locale,
0N/A String key,
0N/A Object... params);
0N/A }
0N/A}