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