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.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.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 = GetPropertyAction.getProperty("java.locale.providers");
 120         List<Type> typeList = new ArrayList<>();
 121 
 122         // Check user specified adapter preference
 123         if (order != null && order.length() != 0) {
 124             String[] types = order.split(",");
 125             for (String type : types) {
 126                 type = type.trim().toUpperCase(Locale.ROOT);
 127                 if (type.equals("COMPAT")) {
 128                     type = "JRE";
 129                 }
 130                 try {
 131                     Type aType = Type.valueOf(type.trim().toUpperCase(Locale.ROOT));
 132                     if (!typeList.contains(aType)) {
 133                         typeList.add(aType);
 134                     }
 135                 } catch (IllegalArgumentException | UnsupportedOperationException e) {
 136                     // could be caused by the user specifying wrong
 137                     // provider name or format in the system property
 138                     LocaleServiceProviderPool.config(LocaleProviderAdapter.class, e.toString());
 139                 }
 140             }
 141         }
 142 
 143         defaultLocaleProviderAdapter = Type.CLDR;
 144         if (!typeList.isEmpty()) {
 145             // bona fide preference exists
 146             if (!typeList.contains(Type.CLDR)) {
 147                 // Append FALLBACK as the last resort.
 148                 typeList.add(Type.FALLBACK);
 149                 defaultLocaleProviderAdapter = Type.FALLBACK;
 150             }
 151         } else {
 152             // Default preference list.
 153             typeList.add(Type.CLDR);
 154             typeList.add(Type.JRE);
 155         }
 156         adapterPreference = Collections.unmodifiableList(typeList);
 157     }
 158 
 159     /**
 160      * Returns the singleton instance for each adapter type
 161      */
 162     public static LocaleProviderAdapter forType(Type type) {
 163         switch (type) {
 164         case JRE:
 165         case CLDR:
 166         case SPI:
 167         case HOST:
 168         case FALLBACK:
 169             LocaleProviderAdapter adapter = null;
 170             LocaleProviderAdapter cached = adapterInstances.get(type);
 171             if (cached == null) {
 172                 try {
 173                     // lazily load adapters here
 174                     adapter = (LocaleProviderAdapter)Class.forName(type.getAdapterClassName())
 175                         .newInstance();
 176                     cached = adapterInstances.putIfAbsent(type, adapter);
 177                     if (cached != null) {
 178                         adapter = cached;
 179                     }
 180                 } catch (ClassNotFoundException |
 181                          IllegalAccessException |
 182                          InstantiationException |
 183                          UnsupportedOperationException e) {
 184                     LocaleServiceProviderPool.config(LocaleProviderAdapter.class, e.toString());
 185                     adapterInstances.putIfAbsent(type, NONEXISTENT_ADAPTER);
 186                     if (defaultLocaleProviderAdapter == type) {
 187                         defaultLocaleProviderAdapter = Type.FALLBACK;
 188                     }
 189                 }
 190             } else if (cached != NONEXISTENT_ADAPTER) {
 191                 adapter = cached;
 192             }
 193             return adapter;
 194         default:
 195             throw new InternalError("unknown locale data adapter type");
 196         }
 197     }
 198 
 199     public static LocaleProviderAdapter forJRE() {
 200         return forType(Type.JRE);
 201     }
 202 
 203     public static LocaleProviderAdapter getResourceBundleBased() {
 204         for (Type type : getAdapterPreference()) {
 205             if (type == Type.JRE || type == Type.CLDR || type == Type.FALLBACK) {
 206                 LocaleProviderAdapter adapter = forType(type);
 207                 if (adapter != null) {
 208                     return adapter;
 209                 }
 210             }
 211         }
 212         // Shouldn't happen.
 213         throw new InternalError();
 214     }
 215 
 216     /**
 217      * Returns the preference order of LocaleProviderAdapter.Type
 218      */
 219     public static List<Type> getAdapterPreference() {
 220         return adapterPreference;
 221     }
 222 
 223     /**
 224      * Returns a LocaleProviderAdapter for the given locale service provider that
 225      * best matches the given locale. This method returns the LocaleProviderAdapter
 226      * for JRE if none is found for the given locale.
 227      *
 228      * @param providerClass the class for the locale service provider
 229      * @param locale the desired locale.
 230      * @return a LocaleProviderAdapter
 231      */
 232     public static LocaleProviderAdapter getAdapter(Class<? extends LocaleServiceProvider> providerClass,
 233                                                Locale locale) {
 234         LocaleProviderAdapter adapter;
 235 
 236         // cache lookup
 237         ConcurrentMap<Locale, LocaleProviderAdapter> adapterMap = adapterCache.get(providerClass);
 238         if (adapterMap != null) {
 239             if ((adapter = adapterMap.get(locale)) != null) {
 240                 return adapter;
 241             }
 242         } else {
 243             adapterMap = new ConcurrentHashMap<>();
 244             adapterCache.putIfAbsent(providerClass, adapterMap);
 245         }
 246 
 247         // Fast look-up for the given locale
 248         adapter = findAdapter(providerClass, locale);
 249         if (adapter != null) {
 250             adapterMap.putIfAbsent(locale, adapter);
 251             return adapter;
 252         }
 253 
 254         // Try finding an adapter in the normal candidate locales path of the given locale.
 255         List<Locale> lookupLocales = ResourceBundle.Control.getControl(ResourceBundle.Control.FORMAT_DEFAULT)
 256                                         .getCandidateLocales("", locale);
 257         for (Locale loc : lookupLocales) {
 258             if (loc.equals(locale)) {
 259                 // We've already done with this loc.
 260                 continue;
 261             }
 262             adapter = findAdapter(providerClass, loc);
 263             if (adapter != null) {
 264                 adapterMap.putIfAbsent(locale, adapter);
 265                 return adapter;
 266             }
 267         }
 268 
 269         // returns the adapter for FALLBACK as the last resort
 270         adapterMap.putIfAbsent(locale, forType(Type.FALLBACK));
 271         return forType(Type.FALLBACK);
 272     }
 273 
 274     private static LocaleProviderAdapter findAdapter(Class<? extends LocaleServiceProvider> providerClass,
 275                                                  Locale locale) {
 276         for (Type type : getAdapterPreference()) {
 277             LocaleProviderAdapter adapter = forType(type);
 278             if (adapter != null) {
 279                 LocaleServiceProvider provider = adapter.getLocaleServiceProvider(providerClass);
 280                 if (provider != null) {
 281                     if (provider.isSupportedLocale(locale)) {
 282                         return adapter;
 283                     }
 284                 }
 285             }
 286         }
 287         return null;
 288     }
 289 
 290     /**
 291      * A utility method for implementing the default LocaleServiceProvider.isSupportedLocale
 292      * for the JRE, CLDR, and FALLBACK adapters.
 293      */
 294     public boolean isSupportedProviderLocale(Locale locale,  Set<String> langtags) {
 295         LocaleProviderAdapter.Type type = getAdapterType();
 296         assert type == Type.JRE || type == Type.CLDR || type == Type.FALLBACK;
 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     /**
 423      * Returns a CalendarProvider for this LocaleProviderAdapter, or null if no
 424      * CalendarProvider is available.
 425      *
 426      * @return a CalendarProvider
 427      */
 428     public abstract CalendarProvider getCalendarProvider();
 429 
 430     public abstract LocaleResources getLocaleResources(Locale locale);
 431 
 432     public abstract Locale[] getAvailableLocales();
 433 
 434     private static final LocaleProviderAdapter NONEXISTENT_ADAPTER = new NonExistentAdapter();
 435     private static final class NonExistentAdapter extends FallbackLocaleProviderAdapter {
 436         @Override
 437         public LocaleProviderAdapter.Type getAdapterType() {
 438             return null;
 439         }
 440 
 441         private NonExistentAdapter() {};
 442     }
 443 }