1 /*
   2  * Copyright (c) 2012, 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.lang.reflect.InvocationTargetException;
  29 import java.text.spi.BreakIteratorProvider;
  30 import java.text.spi.CollatorProvider;
  31 import java.text.spi.DateFormatProvider;
  32 import java.text.spi.DateFormatSymbolsProvider;
  33 import java.text.spi.DecimalFormatSymbolsProvider;
  34 import java.text.spi.NumberFormatProvider;
  35 import java.util.ArrayList;
  36 import java.util.Collections;
  37 import java.util.List;
  38 import java.util.Locale;
  39 import java.util.Map;
  40 import java.util.ResourceBundle;
  41 import java.util.ServiceConfigurationError;
  42 import java.util.Set;
  43 import java.util.concurrent.ConcurrentHashMap;
  44 import java.util.concurrent.ConcurrentMap;
  45 import java.util.spi.CalendarDataProvider;
  46 import java.util.spi.CalendarNameProvider;
  47 import java.util.spi.CurrencyNameProvider;
  48 import java.util.spi.LocaleNameProvider;
  49 import java.util.spi.LocaleServiceProvider;
  50 import java.util.spi.TimeZoneNameProvider;
  51 import sun.security.action.GetPropertyAction;
  52 import sun.text.spi.JavaTimeDateTimePatternProvider;
  53 import sun.util.spi.CalendarProvider;
  54 
  55 import static java.lang.System.*;
  56 
  57 /**
  58  * The LocaleProviderAdapter abstract class.
  59  *
  60  * @author Naoto Sato
  61  * @author Masayoshi Okutsu
  62  */
  63 public abstract class LocaleProviderAdapter {
  64     /**
  65      * Adapter type.
  66      */
  67     public enum Type {
  68         JRE("sun.util.locale.provider.JRELocaleProviderAdapter", "sun.util.resources", "sun.text.resources"),
  69         CLDR("sun.util.cldr.CLDRLocaleProviderAdapter", "sun.util.resources.cldr", "sun.text.resources.cldr"),
  70         SPI("sun.util.locale.provider.SPILocaleProviderAdapter"),
  71         HOST("sun.util.locale.provider.HostLocaleProviderAdapter"),
  72         FALLBACK("sun.util.locale.provider.FallbackLocaleProviderAdapter", "sun.util.resources", "sun.text.resources");
  73 
  74         private final String CLASSNAME;
  75         private final String UTIL_RESOURCES_PACKAGE;
  76         private final String TEXT_RESOURCES_PACKAGE;
  77 
  78         Type(String className) {
  79             this(className, null, null);
  80         }
  81 
  82         Type(String className, String util, String text) {
  83             CLASSNAME = className;
  84             UTIL_RESOURCES_PACKAGE = util;
  85             TEXT_RESOURCES_PACKAGE = text;
  86         }
  87 
  88         public String getAdapterClassName() {
  89             return CLASSNAME;
  90         }
  91 
  92         public String getUtilResourcesPackage() {
  93             return UTIL_RESOURCES_PACKAGE;
  94         }
  95 
  96         public String getTextResourcesPackage() {
  97             return TEXT_RESOURCES_PACKAGE;
  98         }
  99     }
 100 
 101     /**
 102      * LocaleProviderAdapter preference list.
 103      */
 104     private static final List<Type> adapterPreference;
 105 
 106     /**
 107      * LocaleProviderAdapter instances
 108      */
 109     private static final Map<Type, LocaleProviderAdapter> adapterInstances = new ConcurrentHashMap<>();
 110 
 111     /**
 112      * Default fallback adapter type, which should return something meaningful in any case.
 113      * This is either CLDR or FALLBACK.
 114      */
 115     static volatile LocaleProviderAdapter.Type defaultLocaleProviderAdapter;
 116 
 117     /**
 118      * Adapter lookup cache.
 119      */
 120     private static final ConcurrentMap<Class<? extends LocaleServiceProvider>, ConcurrentMap<Locale, LocaleProviderAdapter>>
 121         adapterCache = new ConcurrentHashMap<>();
 122 
 123     static {
 124         String order = GetPropertyAction.privilegedGetProperty("java.locale.providers");
 125         ArrayList<Type> typeList = new ArrayList<>();
 126         String invalidTypeMessage = null;
 127 
 128         // Check user specified adapter preference
 129         if (order != null && !order.isEmpty()) {
 130             String[] types = order.split(",");
 131             for (String type : types) {
 132                 type = type.trim().toUpperCase(Locale.ROOT);
 133                 if (type.equals("COMPAT")) {
 134                     type = "JRE";
 135                 }
 136                 try {
 137                     Type aType = Type.valueOf(type.trim().toUpperCase(Locale.ROOT));
 138                     if (!typeList.contains(aType)) {
 139                         typeList.add(aType);
 140                     }
 141                 } catch (IllegalArgumentException e) {
 142                     // construct a log message.
 143                     invalidTypeMessage = "Invalid locale provider adapter \"" + type + "\" ignored.";
 144                 }
 145             }
 146         }
 147 
 148         defaultLocaleProviderAdapter = Type.CLDR;
 149         if (!typeList.isEmpty()) {
 150             // bona fide preference exists
 151             if (!(typeList.contains(Type.CLDR) || typeList.contains(Type.JRE))) {
 152                 // Append FALLBACK as the last resort when no ResourceBundleBasedAdapter is available.
 153                 typeList.add(Type.FALLBACK);
 154                 defaultLocaleProviderAdapter = Type.FALLBACK;
 155             }
 156         } else {
 157             // Default preference list.
 158             typeList.add(Type.CLDR);
 159             typeList.add(Type.JRE);
 160         }
 161         adapterPreference = Collections.unmodifiableList(typeList);
 162 
 163         // Emit logs, if any, after 'adapterPreference' is initialized which is needed
 164         // for logging.
 165         if (invalidTypeMessage != null) {
 166             // could be caused by the user specifying wrong
 167             // provider name or format in the system property
 168             getLogger(LocaleProviderAdapter.class.getCanonicalName())
 169                 .log(Logger.Level.INFO, invalidTypeMessage);
 170         }
 171     }
 172 
 173     /**
 174      * Returns the singleton instance for each adapter type
 175      */
 176     public static LocaleProviderAdapter forType(Type type) {
 177         switch (type) {
 178         case JRE:
 179         case CLDR:
 180         case SPI:
 181         case HOST:
 182         case FALLBACK:
 183             LocaleProviderAdapter adapter = adapterInstances.get(type);
 184             if (adapter == null) {
 185                 try {
 186                     // lazily load adapters here
 187                     adapter = (LocaleProviderAdapter)Class.forName(type.getAdapterClassName())
 188                             .getDeclaredConstructor().newInstance();
 189                     LocaleProviderAdapter cached = adapterInstances.putIfAbsent(type, adapter);
 190                     if (cached != null) {
 191                         adapter = cached;
 192                     }
 193                 } catch (NoSuchMethodException |
 194                          InvocationTargetException |
 195                          ClassNotFoundException |
 196                          IllegalAccessException |
 197                          InstantiationException |
 198                          UnsupportedOperationException e) {
 199                     throw new ServiceConfigurationError("Locale provider adapter \"" +
 200                             type + "\"cannot be instantiated.", e);
 201                 }
 202             }
 203             return adapter;
 204         default:
 205             throw new InternalError("unknown locale data adapter type");
 206         }
 207     }
 208 
 209     public static LocaleProviderAdapter forJRE() {
 210         return forType(Type.JRE);
 211     }
 212 
 213     public static LocaleProviderAdapter getResourceBundleBased() {
 214         for (Type type : getAdapterPreference()) {
 215             if (type == Type.JRE || type == Type.CLDR || type == Type.FALLBACK) {
 216                 LocaleProviderAdapter adapter = forType(type);
 217                 if (adapter != null) {
 218                     return adapter;
 219                 }
 220             }
 221         }
 222         // Shouldn't happen.
 223         throw new InternalError();
 224     }
 225 
 226     /**
 227      * Returns the preference order of LocaleProviderAdapter.Type
 228      */
 229     public static List<Type> getAdapterPreference() {
 230         return adapterPreference;
 231     }
 232 
 233     /**
 234      * Returns a LocaleProviderAdapter for the given locale service provider that
 235      * best matches the given locale. This method returns the LocaleProviderAdapter
 236      * for JRE if none is found for the given locale.
 237      *
 238      * @param providerClass the class for the locale service provider
 239      * @param locale the desired locale.
 240      * @return a LocaleProviderAdapter
 241      */
 242     public static LocaleProviderAdapter getAdapter(Class<? extends LocaleServiceProvider> providerClass,
 243                                                Locale locale) {
 244         LocaleProviderAdapter adapter;
 245 
 246         // cache lookup
 247         ConcurrentMap<Locale, LocaleProviderAdapter> adapterMap = adapterCache.get(providerClass);
 248         if (adapterMap != null) {
 249             if ((adapter = adapterMap.get(locale)) != null) {
 250                 return adapter;
 251             }
 252         } else {
 253             adapterMap = new ConcurrentHashMap<>();
 254             adapterCache.putIfAbsent(providerClass, adapterMap);
 255         }
 256 
 257         // Fast look-up for the given locale
 258         adapter = findAdapter(providerClass, locale);
 259         if (adapter != null) {
 260             adapterMap.putIfAbsent(locale, adapter);
 261             return adapter;
 262         }
 263 
 264         // Try finding an adapter in the normal candidate locales path of the given locale.
 265         List<Locale> lookupLocales = ResourceBundle.Control.getControl(ResourceBundle.Control.FORMAT_DEFAULT)
 266                                         .getCandidateLocales("", locale);
 267         for (Locale loc : lookupLocales) {
 268             if (loc.equals(locale)) {
 269                 // We've already done with this loc.
 270                 continue;
 271             }
 272             adapter = findAdapter(providerClass, loc);
 273             if (adapter != null) {
 274                 adapterMap.putIfAbsent(locale, adapter);
 275                 return adapter;
 276             }
 277         }
 278 
 279         // returns the adapter for FALLBACK as the last resort
 280         adapterMap.putIfAbsent(locale, forType(Type.FALLBACK));
 281         return forType(Type.FALLBACK);
 282     }
 283 
 284     private static LocaleProviderAdapter findAdapter(Class<? extends LocaleServiceProvider> providerClass,
 285                                                  Locale locale) {
 286         for (Type type : getAdapterPreference()) {
 287             LocaleProviderAdapter adapter = forType(type);
 288             if (adapter != null) {
 289                 LocaleServiceProvider provider = adapter.getLocaleServiceProvider(providerClass);
 290                 if (provider != null) {
 291                     if (provider.isSupportedLocale(locale)) {
 292                         return adapter;
 293                     }
 294                 }
 295             }
 296         }
 297         return null;
 298     }
 299 
 300     /**
 301      * A utility method for implementing the default LocaleServiceProvider.isSupportedLocale
 302      * for the JRE, CLDR, and FALLBACK adapters.
 303      */
 304     public boolean isSupportedProviderLocale(Locale locale,  Set<String> langtags) {
 305         LocaleProviderAdapter.Type type = getAdapterType();
 306         assert type == Type.JRE || type == Type.CLDR || type == Type.FALLBACK;
 307         return false;
 308     }
 309 
 310     public static Locale[] toLocaleArray(Set<String> tags) {
 311         Locale[] locs = new Locale[tags.size() + 1];
 312         int index = 0;
 313         locs[index++] = Locale.ROOT;
 314         for (String tag : tags) {
 315             switch (tag) {
 316             case "ja-JP-JP":
 317                 locs[index++] = JRELocaleConstants.JA_JP_JP;
 318                 break;
 319             case "th-TH-TH":
 320                 locs[index++] = JRELocaleConstants.TH_TH_TH;
 321                 break;
 322             default:
 323                 locs[index++] = Locale.forLanguageTag(tag);
 324                 break;
 325             }
 326         }
 327         return locs;
 328     }
 329 
 330     /**
 331      * Returns the type of this LocaleProviderAdapter
 332      */
 333     public abstract LocaleProviderAdapter.Type getAdapterType();
 334 
 335     /**
 336      * Getter method for Locale Service Providers.
 337      */
 338     public abstract <P extends LocaleServiceProvider> P getLocaleServiceProvider(Class<P> c);
 339 
 340     /**
 341      * Returns a BreakIteratorProvider for this LocaleProviderAdapter, or null if no
 342      * BreakIteratorProvider is available.
 343      *
 344      * @return a BreakIteratorProvider
 345      */
 346     public abstract BreakIteratorProvider getBreakIteratorProvider();
 347 
 348     /**
 349      * Returns a ollatorProvider for this LocaleProviderAdapter, or null if no
 350      * ollatorProvider is available.
 351      *
 352      * @return a ollatorProvider
 353      */
 354     public abstract CollatorProvider getCollatorProvider();
 355 
 356     /**
 357      * Returns a DateFormatProvider for this LocaleProviderAdapter, or null if no
 358      * DateFormatProvider is available.
 359      *
 360      * @return a DateFormatProvider
 361      */
 362     public abstract DateFormatProvider getDateFormatProvider();
 363 
 364     /**
 365      * Returns a DateFormatSymbolsProvider for this LocaleProviderAdapter, or null if no
 366      * DateFormatSymbolsProvider is available.
 367      *
 368      * @return a DateFormatSymbolsProvider
 369      */
 370     public abstract DateFormatSymbolsProvider getDateFormatSymbolsProvider();
 371 
 372     /**
 373      * Returns a DecimalFormatSymbolsProvider for this LocaleProviderAdapter, or null if no
 374      * DecimalFormatSymbolsProvider is available.
 375      *
 376      * @return a DecimalFormatSymbolsProvider
 377      */
 378     public abstract DecimalFormatSymbolsProvider getDecimalFormatSymbolsProvider();
 379 
 380     /**
 381      * Returns a NumberFormatProvider for this LocaleProviderAdapter, or null if no
 382      * NumberFormatProvider is available.
 383      *
 384      * @return a NumberFormatProvider
 385      */
 386     public abstract NumberFormatProvider getNumberFormatProvider();
 387 
 388     /*
 389      * Getter methods for java.util.spi.* providers
 390      */
 391 
 392     /**
 393      * Returns a CurrencyNameProvider for this LocaleProviderAdapter, or null if no
 394      * CurrencyNameProvider is available.
 395      *
 396      * @return a CurrencyNameProvider
 397      */
 398     public abstract CurrencyNameProvider getCurrencyNameProvider();
 399 
 400     /**
 401      * Returns a LocaleNameProvider for this LocaleProviderAdapter, or null if no
 402      * LocaleNameProvider is available.
 403      *
 404      * @return a LocaleNameProvider
 405      */
 406     public abstract LocaleNameProvider getLocaleNameProvider();
 407 
 408     /**
 409      * Returns a TimeZoneNameProvider for this LocaleProviderAdapter, or null if no
 410      * TimeZoneNameProvider is available.
 411      *
 412      * @return a TimeZoneNameProvider
 413      */
 414     public abstract TimeZoneNameProvider getTimeZoneNameProvider();
 415 
 416     /**
 417      * Returns a CalendarDataProvider for this LocaleProviderAdapter, or null if no
 418      * CalendarDataProvider is available.
 419      *
 420      * @return a CalendarDataProvider
 421      */
 422     public abstract CalendarDataProvider getCalendarDataProvider();
 423 
 424     /**
 425      * Returns a CalendarNameProvider for this LocaleProviderAdapter, or null if no
 426      * CalendarNameProvider is available.
 427      *
 428      * @return a CalendarNameProvider
 429      */
 430     public abstract CalendarNameProvider getCalendarNameProvider();
 431 
 432     /**
 433      * Returns a CalendarProvider for this LocaleProviderAdapter, or null if no
 434      * CalendarProvider is available.
 435      *
 436      * @return a CalendarProvider
 437      */
 438     public abstract CalendarProvider getCalendarProvider();
 439 
 440     /**
 441      * Returns a JavaTimeDateTimePatternProvider for this LocaleProviderAdapter,
 442      * or null if no JavaTimeDateTimePatternProvider is available.
 443      *
 444      * @return a JavaTimeDateTimePatternProvider
 445      */
 446     public abstract JavaTimeDateTimePatternProvider getJavaTimeDateTimePatternProvider();
 447 
 448     public abstract LocaleResources getLocaleResources(Locale locale);
 449 
 450     public abstract Locale[] getAvailableLocales();
 451 }