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