| /* | |
| * Copyright (c) 2005, 2023, 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 java.util.stream.Stream; | |
| /** | |
| * 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. | |
| */ | |
| private static final ConcurrentMap<Class<? extends LocaleServiceProvider>, LocaleServiceProviderPool> poolOfPools = | |
| new ConcurrentHashMap<>(); | |
| /** | |
| * A Map that retains Locale->provider mapping | |
| */ | |
| private final ConcurrentMap<Locale, List<LocaleServiceProvider>> providersCache = | |
| new ConcurrentHashMap<>(); | |
| /** | |
| * Available locales for this locale sensitive service. This also contains | |
| * JRE's available locales | |
| */ | |
| private Set<Locale> availableLocales = null; | |
| /** | |
| * Provider class | |
| */ | |
| private final 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. | |
| */ | |
| @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 | |
| */ | |
| 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. | |
| * | |
| * @param c class of the locale sensitive service | |
| */ | |
| private LocaleServiceProviderPool (final Class<? extends LocaleServiceProvider> c) { | |
| providerClass = c; | |
| } | |
| /** | |
| * 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 | |
| */ | |
| 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() { | |
| } | |
| } | |
| /** | |
| * {@return a stream of the available locales for all the provider classes} | |
| * | |
| * This stream is constructed from all the locales that are provided by each | |
| * provider, including the JRE. | |
| */ | |
| public static Stream<Locale> streamAllAvailableLocales() { | |
| return Arrays.stream(AllAvailableLocales.allAvailableLocales); | |
| } | |
| /** | |
| * {@return an array of the 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. | |
| */ | |
| public static Locale[] getAllAvailableLocales() { | |
| return AllAvailableLocales.allAvailableLocales.clone(); | |
| } | |
| /** | |
| * {@return an array of the available locales} | |
| * | |
| * This array is a | |
| * merged array of all the locales that are provided by each | |
| * provider, including the JRE. | |
| */ | |
| 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. | |
| * | |
| * @return a set of available locales | |
| */ | |
| private synchronized Set<Locale> getAvailableLocaleSet() { | |
| if (availableLocales == null) { | |
| availableLocales = new HashSet<>(); | |
| for (LocaleProviderAdapter.Type type : LocaleProviderAdapter.getAdapterPreference()) { | |
| LocaleProviderAdapter lda = LocaleProviderAdapter.forType(type); | |
| if (lda != null) { | |
| LocaleServiceProvider lsp = lda.getLocaleServiceProvider(providerClass); | |
| if (lsp != null) { | |
| Locale[] locales = lsp.getAvailableLocales(); | |
| for (Locale locale: locales) { | |
| availableLocales.add(getLookupLocale(locale)); | |
| } | |
| } | |
| } | |
| } | |
| } | |
| return availableLocales; | |
| } | |
| /** | |
| * 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. | |
| * | |
| * @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); | |
| } | |
| /** | |
| * 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 isObjectProvider flag designating object provider or not | |
| * @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, | |
| Boolean isObjectProvider, | |
| String key, | |
| Object... params) { | |
| return getLocalizedObjectImpl(getter, locale, isObjectProvider, 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(); | |
| } | |
| List<Locale> lookupLocales = getLookupLocales(locale); | |
| for (Locale current : lookupLocales) { | |
| S providersObj; | |
| for (LocaleServiceProvider lsp: findProviders(current, isObjectProvider)) { | |
| providersObj = getter.getObject((P)lsp, locale, key, params); | |
| if (providersObj != null) { | |
| return providersObj; | |
| } else if (isObjectProvider) { | |
| System.getLogger(LocaleServiceProviderPool.class.getCanonicalName()) | |
| .log(System.Logger.Level.INFO, | |
| "A locale sensitive service object provider returned null, " + | |
| "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. | |
| * | |
| * @param locale the given locale | |
| * @return the list of locale data adapter types | |
| */ | |
| private List<LocaleServiceProvider> findProviders(Locale locale, boolean isObjectProvider) { | |
| List<LocaleServiceProvider> providersList = providersCache.get(locale); | |
| if (providersList == null) { | |
| for (LocaleProviderAdapter.Type type : LocaleProviderAdapter.getAdapterPreference()) { | |
| LocaleProviderAdapter lda = LocaleProviderAdapter.forType(type); | |
| if (lda != null) { | |
| LocaleServiceProvider lsp = lda.getLocaleServiceProvider(providerClass); | |
| if (lsp != null) { | |
| if (lsp.isSupportedLocale(locale)) { | |
| if (providersList == null) { | |
| providersList = new ArrayList<>(2); | |
| } | |
| providersList.add(lsp); | |
| if (isObjectProvider) { | |
| break; | |
| } | |
| } | |
| } | |
| } | |
| } | |
| if (providersList == null) { | |
| providersList = NULL_LIST; | |
| } | |
| List<LocaleServiceProvider> val = providersCache.putIfAbsent(locale, providersList); | |
| if (val != null) { | |
| providersList = val; | |
| } | |
| } | |
| return providersList; | |
| } | |
| /** | |
| * 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. | |
| return Control.getNoFallbackControl(Control.FORMAT_DEFAULT) | |
| .getCandidateLocales("", locale); | |
| } | |
| /** | |
| * 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. | |
| System.getLogger(LocaleServiceProviderPool.class.getCanonicalName()) | |
| .log(System.Logger.Level.INFO, | |
| "A locale(" + locale + ") has non-empty extensions, but has illformed fields."); | |
| // Fallback - script field will be lost. | |
| lookupLocale = Locale.of(locale.getLanguage(), locale.getCountry(), locale.getVariant()); | |
| } | |
| } | |
| return lookupLocale; | |
| } | |
| /** | |
| * A dummy locale service provider list that indicates there is no | |
| * provider available | |
| */ | |
| private static final List<LocaleServiceProvider> NULL_LIST = | |
| Collections.emptyList(); | |
| /** | |
| * 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 | |
| * | |
| * @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 | |
| */ | |
| S getObject(P lsp, | |
| Locale locale, | |
| String key, | |
| Object... params); | |
| } | |
| } |