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         // Add the JRE Locale Data Adapter implementation.
 131         providers.putIfAbsent(LocaleProviderAdapter.Type.JRE,
 132             LocaleProviderAdapter.forJRE().getLocaleServiceProvider(c));
 133 
 134         // Add the SPI Locale Data Adapter implementation.
 135         LocaleProviderAdapter lda = LocaleProviderAdapter.forType(LocaleProviderAdapter.Type.SPI);
 136         LocaleServiceProvider provider = lda.getLocaleServiceProvider(c);
 137         if (provider != null) {
 138             providers.putIfAbsent(LocaleProviderAdapter.Type.SPI, provider);
 139         }
 140 
 141         // Add the CLDR Locale Data Adapter implementation, if needed.
 142         lda =  LocaleProviderAdapter.forType(LocaleProviderAdapter.Type.CLDR);
 143         if (lda != null) {
 144             provider = lda.getLocaleServiceProvider(c);
 145             if (provider != null) {
 146                 providers.putIfAbsent(LocaleProviderAdapter.Type.CLDR, provider);
 147             }
 148         }
 149 
 150         // Add the Host Locale Data Adapter implementation, if needed.
 151         lda =  LocaleProviderAdapter.forType(LocaleProviderAdapter.Type.HOST);
 152         if (lda != null) {
 153             provider = lda.getLocaleServiceProvider(c);
 154             if (provider != null) {
 155                 providers.putIfAbsent(LocaleProviderAdapter.Type.HOST, provider);
 156             }
 157         }
 158     }
 159 
 160     static void config(Class<? extends Object> caller, String message) {
 161         PlatformLogger logger = PlatformLogger.getLogger(caller.getCanonicalName());
 162         logger.config(message);
 163     }
 164 
 165     /**
 166      * Lazy loaded set of available locales.
 167      * Loading all locales is a very long operation.
 168      */
 169     private static class AllAvailableLocales {
 170         /**
 171          * Available locales for all locale sensitive services.
 172          * This also contains JRE's available locales
 173          */
 174         static final Locale[] allAvailableLocales;
 175 
 176         static {
 177             Set<Locale> all = new HashSet<>();
 178             for (Class<? extends LocaleServiceProvider> c : spiClasses) {
 179                 LocaleServiceProviderPool pool =
 180                     LocaleServiceProviderPool.getPool(c);
 181                 all.addAll(pool.getAvailableLocaleSet());
 182             }
 183 
 184             allAvailableLocales = all.toArray(new Locale[0]);
 185         }
 186 
 187         // No instantiation
 188         private AllAvailableLocales() {
 189         }
 190     }
 191 
 192     /**
 193      * Returns an array of available locales for all the provider classes.
 194      * This array is a merged array of all the locales that are provided by each
 195      * provider, including the JRE.
 196      *
 197      * @return an array of the available locales for all provider classes
 198      */
 199     public static Locale[] getAllAvailableLocales() {
 200         return AllAvailableLocales.allAvailableLocales.clone();
 201     }
 202 
 203     /**
 204      * Returns an array of available locales.  This array is a
 205      * merged array of all the locales that are provided by each
 206      * provider, including the JRE.
 207      *
 208      * @return an array of the available locales
 209      */
 210     public Locale[] getAvailableLocales() {
 211         Set<Locale> locList = new HashSet<>();
 212         locList.addAll(getAvailableLocaleSet());
 213         // Make sure it all contains JRE's locales for compatibility.
 214         locList.addAll(Arrays.asList(LocaleProviderAdapter.forJRE().getAvailableLocales()));
 215         Locale[] tmp = new Locale[locList.size()];
 216         locList.toArray(tmp);
 217         return tmp;
 218     }
 219 
 220     /**
 221      * Returns the union of locale sets that are available from
 222      * each service provider. This method does NOT return the
 223      * defensive copy.
 224      *
 225      * @return a set of available locales
 226      */
 227     private synchronized Set<Locale> getAvailableLocaleSet() {
 228         if (availableLocales == null) {
 229             availableLocales = new HashSet<>();
 230             for (LocaleServiceProvider lsp : providers.values()) {
 231                 Locale[] locales = lsp.getAvailableLocales();
 232                 for (Locale locale: locales) {
 233                     availableLocales.add(getLookupLocale(locale));
 234                 }
 235             }
 236         }
 237 
 238         return availableLocales;
 239     }
 240 
 241     /**
 242      * Returns whether any provider for this locale sensitive
 243      * service is available or not, excluding JRE's one.
 244      *
 245      * @return true if any provider (other than JRE) is available
 246      */
 247     boolean hasProviders() {
 248         return providers.size() != 1 ||
 249                providers.get(LocaleProviderAdapter.Type.JRE) == null;
 250     }
 251 
 252     /**
 253      * Returns the provider's localized object for the specified
 254      * locale.
 255      *
 256      * @param getter an object on which getObject() method
 257      *     is called to obtain the provider's instance.
 258      * @param locale the given locale that is used as the starting one
 259      * @param params provider specific parameters
 260      * @return provider's instance, or null.
 261      */
 262     public <P extends LocaleServiceProvider, S> S getLocalizedObject(LocalizedObjectGetter<P, S> getter,
 263                                      Locale locale,
 264                                      Object... params) {
 265         return getLocalizedObjectImpl(getter, locale, true, null, params);
 266     }
 267 
 268     /**
 269      * Returns the provider's localized name for the specified
 270      * locale.
 271      *
 272      * @param getter an object on which getObject() method
 273      *     is called to obtain the provider's instance.
 274      * @param locale the given locale that is used as the starting one
 275      * @param key the key string for name providers
 276      * @param params provider specific parameters
 277      * @return provider's instance, or null.
 278      */
 279     public <P extends LocaleServiceProvider, S> S getLocalizedObject(LocalizedObjectGetter<P, S> getter,
 280                                      Locale locale,
 281                                      String key,
 282                                      Object... params) {
 283         return getLocalizedObjectImpl(getter, locale, false, key, params);
 284     }
 285 
 286     @SuppressWarnings("unchecked")
 287     private <P extends LocaleServiceProvider, S> S getLocalizedObjectImpl(LocalizedObjectGetter<P, S> getter,
 288                                      Locale locale,
 289                                      boolean isObjectProvider,
 290                                      String key,
 291                                      Object... params) {
 292         if (locale == null) {
 293             throw new NullPointerException();
 294         }
 295 
 296         // Check whether JRE is the sole locale data provider or not,
 297         // and directly call it if it is.
 298         if (!hasProviders()) {
 299             return getter.getObject(
 300                 (P)providers.get(LocaleProviderAdapter.Type.JRE),
 301                 locale, key, params);
 302         }
 303 
 304         List<Locale> lookupLocales = getLookupLocales(locale);
 305 
 306         Set<Locale> available = getAvailableLocaleSet();
 307         for (Locale current : lookupLocales) {
 308             if (available.contains(current)) {
 309                 S providersObj;
 310 
 311                 for (LocaleProviderAdapter.Type type: findProviders(current)) {
 312                     LocaleServiceProvider lsp = providers.get(type);
 313                     providersObj = getter.getObject((P)lsp, locale, key, params);
 314                     if (providersObj != null) {
 315                         return providersObj;
 316                     } else if (isObjectProvider) {
 317                         config(LocaleServiceProviderPool.class,
 318                             "A locale sensitive service provider returned null for a localized objects,  which should not happen.  provider: "
 319                                 + lsp + " locale: " + locale);
 320                     }
 321                 }
 322             }
 323         }
 324 
 325         // not found.
 326         return null;
 327     }
 328 
 329     /**
 330      * Returns the list of locale service provider instances that support
 331      * the specified locale.
 332      *
 333      * @param locale the given locale
 334      * @return the list of locale data adapter types
 335      */
 336     private List<LocaleProviderAdapter.Type> findProviders(Locale locale) {
 337         List<LocaleProviderAdapter.Type> providersList = providersCache.get(locale);
 338         if (providersList == null) {
 339             for (LocaleProviderAdapter.Type type : LocaleProviderAdapter.getAdapterPreference()) {
 340                 LocaleServiceProvider lsp = providers.get(type);
 341                 if (lsp != null) {
 342                     if (lsp.isSupportedLocale(locale)) {
 343                         if (providersList == null) {
 344                             providersList = new ArrayList<>(2);
 345                         }
 346                         providersList.add(type);
 347 
 348                     }
 349                 }
 350             }
 351             if (providersList == null) {
 352                 providersList = NULL_LIST;
 353             }
 354             List<LocaleProviderAdapter.Type> val = providersCache.putIfAbsent(locale, providersList);
 355             if (val != null) {
 356                 providersList = val;
 357             }
 358         }
 359             return providersList;
 360         }
 361 
 362     /**
 363      * Returns a list of candidate locales for service look up.
 364      * @param locale the input locale
 365      * @return the list of candidate locales for the given locale
 366      */
 367     static List<Locale> getLookupLocales(Locale locale) {
 368         // Note: We currently use the default implementation of
 369         // ResourceBundle.Control.getCandidateLocales. The result
 370         // returned by getCandidateLocales are already normalized
 371         // (no extensions) for service look up.
 372         List<Locale> lookupLocales = Control.getNoFallbackControl(Control.FORMAT_DEFAULT)
 373                                             .getCandidateLocales("", locale);
 374         return lookupLocales;
 375     }
 376 
 377     /**
 378      * Returns an instance of Locale used for service look up.
 379      * The result Locale has no extensions except for ja_JP_JP
 380      * and th_TH_TH
 381      *
 382      * @param locale the locale
 383      * @return the locale used for service look up
 384      */
 385     static Locale getLookupLocale(Locale locale) {
 386         Locale lookupLocale = locale;
 387         if (locale.hasExtensions()
 388                 && !locale.equals(JRELocaleConstants.JA_JP_JP)
 389                 && !locale.equals(JRELocaleConstants.TH_TH_TH)) {
 390             // remove extensions
 391             Builder locbld = new Builder();
 392             try {
 393                 locbld.setLocale(locale);
 394                 locbld.clearExtensions();
 395                 lookupLocale = locbld.build();
 396             } catch (IllformedLocaleException e) {
 397                 // A Locale with non-empty extensions
 398                 // should have well-formed fields except
 399                 // for ja_JP_JP and th_TH_TH. Therefore,
 400                 // it should never enter in this catch clause.
 401                 config(LocaleServiceProviderPool.class,
 402                        "A locale(" + locale + ") has non-empty extensions, but has illformed fields.");
 403 
 404                 // Fallback - script field will be lost.
 405                 lookupLocale = new Locale(locale.getLanguage(), locale.getCountry(), locale.getVariant());
 406             }
 407         }
 408         return lookupLocale;
 409     }
 410 
 411     /**
 412      * A dummy locale service provider list that indicates there is no
 413      * provider available
 414      */
 415     private static List<LocaleProviderAdapter.Type> NULL_LIST =
 416         Collections.emptyList();
 417 
 418     /**
 419      * An interface to get a localized object for each locale sensitive
 420      * service class.
 421      */
 422     public interface LocalizedObjectGetter<P extends LocaleServiceProvider, S> {
 423         /**
 424          * Returns an object from the provider
 425          *
 426          * @param lsp the provider
 427          * @param locale the locale
 428          * @param key key string to localize, or null if the provider is not
 429          *     a name provider
 430          * @param params provider specific params
 431          * @return localized object from the provider
 432          */
 433         public S getObject(P lsp,
 434                            Locale locale,
 435                            String key,
 436                            Object... params);
 437     }
 438 }