/*
* Copyright (c) 2005, 2013, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package sun.util.locale.provider;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.IllformedLocaleException;
import java.util.List;
import java.util.Locale;
import java.util.Locale.Builder;
import java.util.ResourceBundle.Control;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.spi.LocaleServiceProvider;
import sun.util.logging.PlatformLogger;
An instance of this class holds a set of the third party implementations of a particular locale sensitive service, such as LocaleNameProvider
. Author: Naoto Sato, Masayoshi Okutsu
/**
* An instance of this class holds a set of the third party implementations of a particular
* locale sensitive service, such as {@link java.util.spi.LocaleNameProvider}.
*
* @author Naoto Sato
* @author Masayoshi Okutsu
*/
public final class LocaleServiceProviderPool {
A Map that holds singleton instances of this class. Each instance holds a
set of provider implementations of a particular locale sensitive service.
/**
* A Map that holds singleton instances of this class. Each instance holds a
* set of provider implementations of a particular locale sensitive service.
*/
private static ConcurrentMap<Class<? extends LocaleServiceProvider>, LocaleServiceProviderPool> poolOfPools =
new ConcurrentHashMap<>();
A Map containing locale service providers that implement the
specified provider SPI, keyed by a LocaleProviderAdapter.Type
/**
* A Map containing locale service providers that implement the
* specified provider SPI, keyed by a LocaleProviderAdapter.Type
*/
private ConcurrentMap<LocaleProviderAdapter.Type, LocaleServiceProvider> providers =
new ConcurrentHashMap<>();
A Map that retains Locale->provider mapping
/**
* A Map that retains Locale->provider mapping
*/
private ConcurrentMap<Locale, List<LocaleProviderAdapter.Type>> providersCache =
new ConcurrentHashMap<>();
Available locales for this locale sensitive service. This also contains
JRE's available locales
/**
* Available locales for this locale sensitive service. This also contains
* JRE's available locales
*/
private Set<Locale> availableLocales = null;
Provider class
/**
* Provider class
*/
private Class<? extends LocaleServiceProvider> providerClass;
Array of all Locale Sensitive SPI classes.
We know "spiClasses" contains classes that extends LocaleServiceProvider,
but generic array creation is not allowed, thus the "unchecked" warning
is suppressed here.
/**
* Array of all Locale Sensitive SPI classes.
*
* We know "spiClasses" contains classes that extends LocaleServiceProvider,
* but generic array creation is not allowed, thus the "unchecked" warning
* is suppressed here.
*/
@SuppressWarnings("unchecked")
static final Class<LocaleServiceProvider>[] spiClasses =
(Class<LocaleServiceProvider>[]) new Class<?>[] {
java.text.spi.BreakIteratorProvider.class,
java.text.spi.CollatorProvider.class,
java.text.spi.DateFormatProvider.class,
java.text.spi.DateFormatSymbolsProvider.class,
java.text.spi.DecimalFormatSymbolsProvider.class,
java.text.spi.NumberFormatProvider.class,
java.util.spi.CurrencyNameProvider.class,
java.util.spi.LocaleNameProvider.class,
java.util.spi.TimeZoneNameProvider.class,
java.util.spi.CalendarDataProvider.class
};
A factory method that returns a singleton instance
/**
* A factory method that returns a singleton instance
*/
public static LocaleServiceProviderPool getPool(Class<? extends LocaleServiceProvider> providerClass) {
LocaleServiceProviderPool pool = poolOfPools.get(providerClass);
if (pool == null) {
LocaleServiceProviderPool newPool =
new LocaleServiceProviderPool(providerClass);
pool = poolOfPools.putIfAbsent(providerClass, newPool);
if (pool == null) {
pool = newPool;
}
}
return pool;
}
The sole constructor.
Params: - c – class of the locale sensitive service
/**
* The sole constructor.
*
* @param c class of the locale sensitive service
*/
private LocaleServiceProviderPool (final Class<? extends LocaleServiceProvider> c) {
providerClass = c;
for (LocaleProviderAdapter.Type type : LocaleProviderAdapter.getAdapterPreference()) {
LocaleProviderAdapter lda = LocaleProviderAdapter.forType(type);
if (lda != null) {
LocaleServiceProvider provider = lda.getLocaleServiceProvider(c);
if (provider != null) {
providers.putIfAbsent(type, provider);
}
}
}
}
static void config(Class<? extends Object> caller, String message) {
PlatformLogger logger = PlatformLogger.getLogger(caller.getCanonicalName());
logger.config(message);
}
Lazy loaded set of available locales.
Loading all locales is a very long operation.
/**
* Lazy loaded set of available locales.
* Loading all locales is a very long operation.
*/
private static class AllAvailableLocales {
Available locales for all locale sensitive services.
This also contains JRE's available locales
/**
* Available locales for all locale sensitive services.
* This also contains JRE's available locales
*/
static final Locale[] allAvailableLocales;
static {
Set<Locale> all = new HashSet<>();
for (Class<? extends LocaleServiceProvider> c : spiClasses) {
LocaleServiceProviderPool pool =
LocaleServiceProviderPool.getPool(c);
all.addAll(pool.getAvailableLocaleSet());
}
allAvailableLocales = all.toArray(new Locale[0]);
}
// No instantiation
private AllAvailableLocales() {
}
}
Returns an array of available locales for all the provider classes.
This array is a merged array of all the locales that are provided by each
provider, including the JRE.
Returns: an array of the available locales for all provider classes
/**
* Returns an array of available locales for all the provider classes.
* This array is a merged array of all the locales that are provided by each
* provider, including the JRE.
*
* @return an array of the available locales for all provider classes
*/
public static Locale[] getAllAvailableLocales() {
return AllAvailableLocales.allAvailableLocales.clone();
}
Returns an array of available locales. This array is a
merged array of all the locales that are provided by each
provider, including the JRE.
Returns: an array of the available locales
/**
* Returns an array of available locales. This array is a
* merged array of all the locales that are provided by each
* provider, including the JRE.
*
* @return an array of the available locales
*/
public Locale[] getAvailableLocales() {
Set<Locale> locList = new HashSet<>();
locList.addAll(getAvailableLocaleSet());
// Make sure it all contains JRE's locales for compatibility.
locList.addAll(Arrays.asList(LocaleProviderAdapter.forJRE().getAvailableLocales()));
Locale[] tmp = new Locale[locList.size()];
locList.toArray(tmp);
return tmp;
}
Returns the union of locale sets that are available from
each service provider. This method does NOT return the
defensive copy.
Returns: a set of available locales
/**
* Returns the union of locale sets that are available from
* each service provider. This method does NOT return the
* defensive copy.
*
* @return a set of available locales
*/
private synchronized Set<Locale> getAvailableLocaleSet() {
if (availableLocales == null) {
availableLocales = new HashSet<>();
for (LocaleServiceProvider lsp : providers.values()) {
Locale[] locales = lsp.getAvailableLocales();
for (Locale locale: locales) {
availableLocales.add(getLookupLocale(locale));
}
}
}
return availableLocales;
}
Returns whether any provider for this locale sensitive
service is available or not, excluding JRE's one.
Returns: true if any provider (other than JRE) is available
/**
* Returns whether any provider for this locale sensitive
* service is available or not, excluding JRE's one.
*
* @return true if any provider (other than JRE) is available
*/
boolean hasProviders() {
return providers.size() != 1 ||
(providers.get(LocaleProviderAdapter.Type.JRE) == null &&
providers.get(LocaleProviderAdapter.Type.FALLBACK) == null);
}
Returns the provider's localized object for the specified
locale.
Params: - getter – an object on which getObject() method
is called to obtain the provider's instance.
- locale – the given locale that is used as the starting one
- params – provider specific parameters
Returns: provider's instance, or null.
/**
* Returns the provider's localized object for the specified
* locale.
*
* @param getter an object on which getObject() method
* is called to obtain the provider's instance.
* @param locale the given locale that is used as the starting one
* @param params provider specific parameters
* @return provider's instance, or null.
*/
public <P extends LocaleServiceProvider, S> S getLocalizedObject(LocalizedObjectGetter<P, S> getter,
Locale locale,
Object... params) {
return getLocalizedObjectImpl(getter, locale, true, null, params);
}
Returns the provider's localized name for the specified
locale.
Params: - getter – an object on which getObject() method
is called to obtain the provider's instance.
- locale – the given locale that is used as the starting one
- key – the key string for name providers
- params – provider specific parameters
Returns: provider's instance, or null.
/**
* Returns the provider's localized name for the specified
* locale.
*
* @param getter an object on which getObject() method
* is called to obtain the provider's instance.
* @param locale the given locale that is used as the starting one
* @param key the key string for name providers
* @param params provider specific parameters
* @return provider's instance, or null.
*/
public <P extends LocaleServiceProvider, S> S getLocalizedObject(LocalizedObjectGetter<P, S> getter,
Locale locale,
String key,
Object... params) {
return getLocalizedObjectImpl(getter, locale, false, key, params);
}
@SuppressWarnings("unchecked")
private <P extends LocaleServiceProvider, S> S getLocalizedObjectImpl(LocalizedObjectGetter<P, S> getter,
Locale locale,
boolean isObjectProvider,
String key,
Object... params) {
if (locale == null) {
throw new NullPointerException();
}
// Check whether JRE is the sole locale data provider or not,
// and directly call it if it is.
if (!hasProviders()) {
return getter.getObject((P)providers.get(LocaleProviderAdapter.defaultLocaleProviderAdapter),
locale, key, params);
}
List<Locale> lookupLocales = getLookupLocales(locale);
Set<Locale> available = getAvailableLocaleSet();
for (Locale current : lookupLocales) {
if (available.contains(current)) {
S providersObj;
for (LocaleProviderAdapter.Type type: findProviders(current)) {
LocaleServiceProvider lsp = providers.get(type);
providersObj = getter.getObject((P)lsp, locale, key, params);
if (providersObj != null) {
return providersObj;
} else if (isObjectProvider) {
config(LocaleServiceProviderPool.class,
"A locale sensitive service provider returned null for a localized objects, which should not happen. provider: "
+ lsp + " locale: " + locale);
}
}
}
}
// not found.
return null;
}
Returns the list of locale service provider instances that support
the specified locale.
Params: - locale – the given locale
Returns: the list of locale data adapter types
/**
* Returns the list of locale service provider instances that support
* the specified locale.
*
* @param locale the given locale
* @return the list of locale data adapter types
*/
private List<LocaleProviderAdapter.Type> findProviders(Locale locale) {
List<LocaleProviderAdapter.Type> providersList = providersCache.get(locale);
if (providersList == null) {
for (LocaleProviderAdapter.Type type : LocaleProviderAdapter.getAdapterPreference()) {
LocaleServiceProvider lsp = providers.get(type);
if (lsp != null) {
if (lsp.isSupportedLocale(locale)) {
if (providersList == null) {
providersList = new ArrayList<>(2);
}
providersList.add(type);
}
}
}
if (providersList == null) {
providersList = NULL_LIST;
}
List<LocaleProviderAdapter.Type> val = providersCache.putIfAbsent(locale, providersList);
if (val != null) {
providersList = val;
}
}
return providersList;
}
Returns a list of candidate locales for service look up.
Params: - locale – the input locale
Returns: the list of candidate locales for the given locale
/**
* Returns a list of candidate locales for service look up.
* @param locale the input locale
* @return the list of candidate locales for the given locale
*/
static List<Locale> getLookupLocales(Locale locale) {
// Note: We currently use the default implementation of
// ResourceBundle.Control.getCandidateLocales. The result
// returned by getCandidateLocales are already normalized
// (no extensions) for service look up.
List<Locale> lookupLocales = Control.getNoFallbackControl(Control.FORMAT_DEFAULT)
.getCandidateLocales("", locale);
return lookupLocales;
}
Returns an instance of Locale used for service look up.
The result Locale has no extensions except for ja_JP_JP
and th_TH_TH
Params: - locale – the locale
Returns: the locale used for service look up
/**
* Returns an instance of Locale used for service look up.
* The result Locale has no extensions except for ja_JP_JP
* and th_TH_TH
*
* @param locale the locale
* @return the locale used for service look up
*/
static Locale getLookupLocale(Locale locale) {
Locale lookupLocale = locale;
if (locale.hasExtensions()
&& !locale.equals(JRELocaleConstants.JA_JP_JP)
&& !locale.equals(JRELocaleConstants.TH_TH_TH)) {
// remove extensions
Builder locbld = new Builder();
try {
locbld.setLocale(locale);
locbld.clearExtensions();
lookupLocale = locbld.build();
} catch (IllformedLocaleException e) {
// A Locale with non-empty extensions
// should have well-formed fields except
// for ja_JP_JP and th_TH_TH. Therefore,
// it should never enter in this catch clause.
config(LocaleServiceProviderPool.class,
"A locale(" + locale + ") has non-empty extensions, but has illformed fields.");
// Fallback - script field will be lost.
lookupLocale = new Locale(locale.getLanguage(), locale.getCountry(), locale.getVariant());
}
}
return lookupLocale;
}
A dummy locale service provider list that indicates there is no
provider available
/**
* A dummy locale service provider list that indicates there is no
* provider available
*/
private static List<LocaleProviderAdapter.Type> NULL_LIST =
Collections.emptyList();
An interface to get a localized object for each locale sensitive
service class.
/**
* An interface to get a localized object for each locale sensitive
* service class.
*/
public interface LocalizedObjectGetter<P extends LocaleServiceProvider, S> {
Returns an object from the provider
Params: - lsp – the provider
- locale – the locale
- key – key string to localize, or null if the provider is not
a name provider
- params – provider specific params
Returns: localized object from the provider
/**
* Returns an object from the provider
*
* @param lsp the provider
* @param locale the locale
* @param key key string to localize, or null if the provider is not
* a name provider
* @param params provider specific params
* @return localized object from the provider
*/
public S getObject(P lsp,
Locale locale,
String key,
Object... params);
}
}