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