1 /*
   2  * Copyright (c) 2005, 2020, Oracle and/or its affiliates. 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.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle 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 Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package sun.util.locale.provider;
  27 
  28 import java.util.ArrayList;
  29 import java.util.Arrays;
  30 import java.util.Collections;
  31 import java.util.HashSet;
  32 import java.util.IllformedLocaleException;
  33 import java.util.List;
  34 import java.util.Locale;
  35 import java.util.Locale.Builder;
  36 import java.util.ResourceBundle.Control;
  37 import java.util.Set;
  38 import java.util.concurrent.ConcurrentHashMap;
  39 import java.util.concurrent.ConcurrentMap;
  40 import java.util.spi.LocaleServiceProvider;
  41 
  42 /**
  43  * An instance of this class holds a set of the third party implementations of a particular
  44  * locale sensitive service, such as {@link java.util.spi.LocaleNameProvider}.
  45  *
  46  * @author Naoto Sato
  47  * @author Masayoshi Okutsu
  48  */
  49 public final class LocaleServiceProviderPool {
  50 
  51     /**
  52      * A Map that holds singleton instances of this class.  Each instance holds a
  53      * set of provider implementations of a particular locale sensitive service.
  54      */
  55     private static final ConcurrentMap<Class<? extends LocaleServiceProvider>, LocaleServiceProviderPool> poolOfPools =
  56         new ConcurrentHashMap<>();
  57 
  58     /**
  59      * A Map that retains Locale->provider mapping
  60      */
  61     private final ConcurrentMap<Locale, List<LocaleServiceProvider>> providersCache =
  62         new ConcurrentHashMap<>();
  63 
  64     /**
  65      * Available locales for this locale sensitive service.  This also contains
  66      * JRE's available locales
  67      */
  68     private Set<Locale> availableLocales = null;
  69 
  70     /**
  71      * Provider class
  72      */
  73     private final Class<? extends LocaleServiceProvider> providerClass;
  74 
  75     /**
  76      * Array of all Locale Sensitive SPI classes.
  77      *
  78      * We know "spiClasses" contains classes that extends LocaleServiceProvider,
  79      * but generic array creation is not allowed, thus the "unchecked" warning
  80      * is suppressed here.
  81      */
  82     @SuppressWarnings("unchecked")
  83     static final Class<LocaleServiceProvider>[] spiClasses =
  84                 (Class<LocaleServiceProvider>[]) new Class<?>[] {
  85         java.text.spi.BreakIteratorProvider.class,
  86         java.text.spi.CollatorProvider.class,
  87         java.text.spi.DateFormatProvider.class,
  88         java.text.spi.DateFormatSymbolsProvider.class,
  89         java.text.spi.DecimalFormatSymbolsProvider.class,
  90         java.text.spi.NumberFormatProvider.class,
  91         java.util.spi.CurrencyNameProvider.class,
  92         java.util.spi.LocaleNameProvider.class,
  93         java.util.spi.TimeZoneNameProvider.class,
  94         java.util.spi.CalendarDataProvider.class
  95     };
  96 
  97     /**
  98      * A factory method that returns a singleton instance
  99      */
 100     public static LocaleServiceProviderPool getPool(Class<? extends LocaleServiceProvider> providerClass) {
 101         LocaleServiceProviderPool pool = poolOfPools.get(providerClass);
 102         if (pool == null) {
 103             LocaleServiceProviderPool newPool =
 104                 new LocaleServiceProviderPool(providerClass);
 105             pool = poolOfPools.putIfAbsent(providerClass, newPool);
 106             if (pool == null) {
 107                 pool = newPool;
 108             }
 109         }
 110 
 111         return pool;
 112     }
 113 
 114     /**
 115      * The sole constructor.
 116      *
 117      * @param c class of the locale sensitive service
 118      */
 119     private LocaleServiceProviderPool (final Class<? extends LocaleServiceProvider> c) {
 120         providerClass = c;
 121     }
 122 
 123     /**
 124      * Lazy loaded set of available locales.
 125      * Loading all locales is a very long operation.
 126      */
 127     private static class AllAvailableLocales {
 128         /**
 129          * Available locales for all locale sensitive services.
 130          * This also contains JRE's available locales
 131          */
 132         static final Locale[] allAvailableLocales;
 133 
 134         static {
 135             Set<Locale> all = new HashSet<>();
 136             for (Class<? extends LocaleServiceProvider> c : spiClasses) {
 137                 LocaleServiceProviderPool pool =
 138                     LocaleServiceProviderPool.getPool(c);
 139                 all.addAll(pool.getAvailableLocaleSet());
 140             }
 141 
 142             allAvailableLocales = all.toArray(new Locale[0]);
 143         }
 144 
 145         // No instantiation
 146         private AllAvailableLocales() {
 147         }
 148     }
 149 
 150     /**
 151      * Returns an array of available locales for all the provider classes.
 152      * This array is a merged array of all the locales that are provided by each
 153      * provider, including the JRE.
 154      *
 155      * @return an array of the available locales for all provider classes
 156      */
 157     public static Locale[] getAllAvailableLocales() {
 158         return AllAvailableLocales.allAvailableLocales.clone();
 159     }
 160 
 161     /**
 162      * Returns an array of available locales.  This array is a
 163      * merged array of all the locales that are provided by each
 164      * provider, including the JRE.
 165      *
 166      * @return an array of the available locales
 167      */
 168     public Locale[] getAvailableLocales() {
 169         Set<Locale> locList = new HashSet<>();
 170         locList.addAll(getAvailableLocaleSet());
 171         // Make sure it all contains JRE's locales for compatibility.
 172         locList.addAll(Arrays.asList(LocaleProviderAdapter.forJRE().getAvailableLocales()));
 173         Locale[] tmp = new Locale[locList.size()];
 174         locList.toArray(tmp);
 175         return tmp;
 176     }
 177 
 178     /**
 179      * Returns the union of locale sets that are available from
 180      * each service provider. This method does NOT return the
 181      * defensive copy.
 182      *
 183      * @return a set of available locales
 184      */
 185     private synchronized Set<Locale> getAvailableLocaleSet() {
 186         if (availableLocales == null) {
 187             availableLocales = new HashSet<>();
 188             for (LocaleProviderAdapter.Type type : LocaleProviderAdapter.getAdapterPreference()) {
 189                 LocaleProviderAdapter lda = LocaleProviderAdapter.forType(type);
 190                 if (lda != null) {
 191                     LocaleServiceProvider lsp = lda.getLocaleServiceProvider(providerClass);
 192                     if (lsp != null) {
 193                         Locale[] locales = lsp.getAvailableLocales();
 194                         for (Locale locale: locales) {
 195                             availableLocales.add(getLookupLocale(locale));
 196                         }
 197                     }
 198                 }
 199             }
 200         }
 201 
 202         return availableLocales;
 203     }
 204 
 205     /**
 206      * Returns the provider's localized object for the specified
 207      * locale.
 208      *
 209      * @param getter an object on which getObject() method
 210      *     is called to obtain the provider's instance.
 211      * @param locale the given locale that is used as the starting one
 212      * @param params provider specific parameters
 213      * @return provider's instance, or null.
 214      */
 215     public <P extends LocaleServiceProvider, S> S getLocalizedObject(LocalizedObjectGetter<P, S> getter,
 216                                      Locale locale,
 217                                      Object... params) {
 218         return getLocalizedObjectImpl(getter, locale, true, null, params);
 219     }
 220 
 221     /**
 222      * Returns the provider's localized name for the specified
 223      * locale.
 224      *
 225      * @param getter an object on which getObject() method
 226      *     is called to obtain the provider's instance.
 227      * @param locale the given locale that is used as the starting one
 228      * @param key the key string for name providers
 229      * @param params provider specific parameters
 230      * @return provider's instance, or null.
 231      */
 232     public <P extends LocaleServiceProvider, S> S getLocalizedObject(LocalizedObjectGetter<P, S> getter,
 233                                      Locale locale,
 234                                      String key,
 235                                      Object... params) {
 236         return getLocalizedObjectImpl(getter, locale, false, key, params);
 237     }
 238 
 239     /**
 240      * Returns the provider's localized name for the specified
 241      * locale.
 242      *
 243      * @param getter an object on which getObject() method
 244      *     is called to obtain the provider's instance.
 245      * @param locale the given locale that is used as the starting one
 246      * @param isObjectProvider flag designating object provder or not
 247      * @param key the key string for name providers
 248      * @param params provider specific parameters
 249      * @return provider's instance, or null.
 250      */
 251     public <P extends LocaleServiceProvider, S> S getLocalizedObject(LocalizedObjectGetter<P, S> getter,
 252                                      Locale locale,
 253                                      Boolean isObjectProvider,
 254                                      String key,
 255                                      Object... params) {
 256         return getLocalizedObjectImpl(getter, locale, isObjectProvider, key, params);
 257     }
 258 
 259     @SuppressWarnings("unchecked")
 260     private <P extends LocaleServiceProvider, S> S getLocalizedObjectImpl(LocalizedObjectGetter<P, S> getter,
 261                                      Locale locale,
 262                                      boolean isObjectProvider,
 263                                      String key,
 264                                      Object... params) {
 265         if (locale == null) {
 266             throw new NullPointerException();
 267         }
 268 
 269         List<Locale> lookupLocales = getLookupLocales(locale);
 270 
 271         for (Locale current : lookupLocales) {
 272             S providersObj;
 273 
 274             for (LocaleServiceProvider lsp: findProviders(current, isObjectProvider)) {
 275                 providersObj = getter.getObject((P)lsp, locale, key, params);
 276                 if (providersObj != null) {
 277                     return providersObj;
 278                 } else if (isObjectProvider) {
 279                     System.getLogger(LocaleServiceProviderPool.class.getCanonicalName())
 280                             .log(System.Logger.Level.INFO,
 281                            "A locale sensitive service object provider returned null, " +
 282                                 "which should not happen. Provider: " + lsp + " Locale: " + locale);
 283                 }
 284             }
 285         }
 286 
 287         // not found.
 288         return null;
 289     }
 290 
 291     /**
 292      * Returns the list of locale service provider instances that support
 293      * the specified locale.
 294      *
 295      * @param locale the given locale
 296      * @return the list of locale data adapter types
 297      */
 298     private List<LocaleServiceProvider> findProviders(Locale locale, boolean isObjectProvider) {
 299         List<LocaleServiceProvider> providersList = providersCache.get(locale);
 300         if (providersList == null) {
 301             for (LocaleProviderAdapter.Type type : LocaleProviderAdapter.getAdapterPreference()) {
 302                 LocaleProviderAdapter lda = LocaleProviderAdapter.forType(type);
 303                 if (lda != null) {
 304                     LocaleServiceProvider lsp = lda.getLocaleServiceProvider(providerClass);
 305                     if (lsp != null) {
 306                         if (lsp.isSupportedLocale(locale)) {
 307                             if (providersList == null) {
 308                                 providersList = new ArrayList<>(2);
 309                             }
 310                             providersList.add(lsp);
 311                             if (isObjectProvider) {
 312                                 break;
 313                             }
 314                         }
 315                     }
 316                 }
 317             }
 318             if (providersList == null) {
 319                 providersList = NULL_LIST;
 320             }
 321             List<LocaleServiceProvider> val = providersCache.putIfAbsent(locale, providersList);
 322             if (val != null) {
 323                 providersList = val;
 324             }
 325         }
 326         return providersList;
 327     }
 328 
 329     /**
 330      * Returns a list of candidate locales for service look up.
 331      * @param locale the input locale
 332      * @return the list of candidate locales for the given locale
 333      */
 334     static List<Locale> getLookupLocales(Locale locale) {
 335         // Note: We currently use the default implementation of
 336         // ResourceBundle.Control.getCandidateLocales. The result
 337         // returned by getCandidateLocales are already normalized
 338         // (no extensions) for service look up.
 339         return Control.getNoFallbackControl(Control.FORMAT_DEFAULT)
 340                                             .getCandidateLocales("", locale);
 341     }
 342 
 343     /**
 344      * Returns an instance of Locale used for service look up.
 345      * The result Locale has no extensions except for ja_JP_JP
 346      * and th_TH_TH
 347      *
 348      * @param locale the locale
 349      * @return the locale used for service look up
 350      */
 351     static Locale getLookupLocale(Locale locale) {
 352         Locale lookupLocale = locale;
 353         if (locale.hasExtensions()
 354                 && !locale.equals(JRELocaleConstants.JA_JP_JP)
 355                 && !locale.equals(JRELocaleConstants.TH_TH_TH)) {
 356             // remove extensions
 357             Builder locbld = new Builder();
 358             try {
 359                 locbld.setLocale(locale);
 360                 locbld.clearExtensions();
 361                 lookupLocale = locbld.build();
 362             } catch (IllformedLocaleException e) {
 363                 // A Locale with non-empty extensions
 364                 // should have well-formed fields except
 365                 // for ja_JP_JP and th_TH_TH. Therefore,
 366                 // it should never enter in this catch clause.
 367                 System.getLogger(LocaleServiceProviderPool.class.getCanonicalName())
 368                     .log(System.Logger.Level.INFO,
 369                         "A locale(" + locale + ") has non-empty extensions, but has illformed fields.");
 370 
 371                 // Fallback - script field will be lost.
 372                 lookupLocale = new Locale(locale.getLanguage(), locale.getCountry(), locale.getVariant());
 373             }
 374         }
 375         return lookupLocale;
 376     }
 377 
 378     /**
 379      * A dummy locale service provider list that indicates there is no
 380      * provider available
 381      */
 382     private static final List<LocaleServiceProvider> NULL_LIST =
 383         Collections.emptyList();
 384 
 385     /**
 386      * An interface to get a localized object for each locale sensitive
 387      * service class.
 388      */
 389     public interface LocalizedObjectGetter<P extends LocaleServiceProvider, S> {
 390         /**
 391          * Returns an object from the provider
 392          *
 393          * @param lsp the provider
 394          * @param locale the locale
 395          * @param key key string to localize, or null if the provider is not
 396          *     a name provider
 397          * @param params provider specific params
 398          * @return localized object from the provider
 399          */
 400         S getObject(P lsp,
 401                     Locale locale,
 402                     String key,
 403                     Object... params);
 404     }
 405 }