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