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