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