1 /*
   2  * Copyright (c) 2012, 2018, 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.security.PrivilegedActionException;
  30 import java.security.PrivilegedExceptionAction;
  31 import java.text.BreakIterator;
  32 import java.text.Collator;
  33 import java.text.DateFormat;
  34 import java.text.DateFormatSymbols;
  35 import java.text.DecimalFormatSymbols;
  36 import java.text.NumberFormat;
  37 import java.text.spi.BreakIteratorProvider;
  38 import java.text.spi.CollatorProvider;
  39 import java.text.spi.DateFormatProvider;
  40 import java.text.spi.DateFormatSymbolsProvider;
  41 import java.text.spi.DecimalFormatSymbolsProvider;
  42 import java.text.spi.NumberFormatProvider;
  43 import java.util.Arrays;
  44 import java.util.Locale;
  45 import java.util.Map;
  46 import java.util.ServiceLoader;
  47 import java.util.concurrent.ConcurrentHashMap;
  48 import java.util.concurrent.ConcurrentMap;
  49 import java.util.spi.CalendarDataProvider;
  50 import java.util.spi.CalendarNameProvider;
  51 import java.util.spi.CurrencyNameProvider;
  52 import java.util.spi.LocaleNameProvider;
  53 import java.util.spi.LocaleServiceProvider;
  54 import java.util.spi.TimeZoneNameProvider;
  55 
  56 /**
  57  * LocaleProviderAdapter implementation for the installed SPI implementations.
  58  *
  59  * @author Naoto Sato
  60  * @author Masayoshi Okutsu
  61  */
  62 public class SPILocaleProviderAdapter extends AuxLocaleProviderAdapter {
  63 
  64     /**
  65      * Returns the type of this LocaleProviderAdapter
  66      */
  67     @Override
  68     public LocaleProviderAdapter.Type getAdapterType() {
  69         return LocaleProviderAdapter.Type.SPI;
  70     }
  71 
  72     @Override
  73     protected <P extends LocaleServiceProvider> P findInstalledProvider(final Class<P> c) {
  74         try {
  75             return AccessController.doPrivileged(new PrivilegedExceptionAction<P>() {
  76                 @Override
  77                 @SuppressWarnings(value={"unchecked", "deprecation"})
  78                 public P run() {
  79                     P delegate = null;
  80 
  81                     for (LocaleServiceProvider provider :
  82                              ServiceLoader.load(c, ClassLoader.getSystemClassLoader())) {
  83                         if (delegate == null) {
  84                             try {
  85                                 delegate =
  86                                     (P) Class.forName(SPILocaleProviderAdapter.class.getCanonicalName() +
  87                                               "$" +
  88                                               c.getSimpleName() +
  89                                               "Delegate")
  90                                               .newInstance();
  91                             }  catch (ClassNotFoundException |
  92                                       InstantiationException |
  93                                       IllegalAccessException e) {
  94                                 LocaleServiceProviderPool.config(SPILocaleProviderAdapter.class, e.toString());
  95                                 return null;
  96                             }
  97                         }
  98 
  99                         ((Delegate)delegate).addImpl(provider);
 100                     }
 101                     return delegate;
 102                 }
 103             });
 104         }  catch (PrivilegedActionException e) {
 105             LocaleServiceProviderPool.config(SPILocaleProviderAdapter.class, e.toString());
 106         }
 107         return null;
 108     }
 109 
 110     /*
 111      * Delegate interface. All the implementations have to have the class name
 112      * following "<provider class name>Delegate" convention.
 113      */
 114     private interface Delegate<P extends LocaleServiceProvider> {
 115         default public void addImpl(P impl) {
 116             for (Locale l : impl.getAvailableLocales()) {
 117                 getDelegateMap().putIfAbsent(l, impl);
 118             }
 119         }
 120 
 121         /*
 122          * Obtain the real SPI implementation, using locale fallback
 123          */
 124         default public P getImpl(Locale locale) {
 125             for (Locale l : LocaleServiceProviderPool.getLookupLocales(locale.stripExtensions())) {
 126                 P ret = getDelegateMap().get(l);
 127                 if (ret != null) {
 128                     return ret;
 129                 }
 130             }
 131             return null;
 132         }
 133 
 134         public Map<Locale, P> getDelegateMap();
 135 
 136         default public Locale[] getAvailableLocalesDelegate() {
 137             return getDelegateMap().keySet().stream().toArray(Locale[]::new);
 138         }
 139 
 140         default public boolean isSupportedLocaleDelegate(Locale locale) {
 141             Map<Locale, P> map = getDelegateMap();
 142             Locale override = CalendarDataUtility.findRegionOverride(locale);
 143 
 144             // First, call the method with extensions (if any)
 145             P impl = map.get(override);
 146             if (impl != null) {
 147                 return impl.isSupportedLocale(override);
 148             } else {
 149                 // The default behavior
 150                 Locale overrideNoExt = override.stripExtensions();
 151                 impl = map.get(overrideNoExt);
 152                 if (impl != null) {
 153                     return Arrays.stream(impl.getAvailableLocales())
 154                                 .anyMatch(overrideNoExt::equals);
 155                 }
 156             }
 157 
 158             return false;
 159         }
 160     }
 161 
 162     /*
 163      * Delegates for the actual SPI implementations.
 164      */
 165     static class BreakIteratorProviderDelegate extends BreakIteratorProvider
 166                                         implements Delegate<BreakIteratorProvider> {
 167         private final Map<Locale, BreakIteratorProvider> map = new ConcurrentHashMap<>();
 168 
 169         @Override
 170         public Map<Locale, BreakIteratorProvider> getDelegateMap() {
 171             return map;
 172         }
 173 
 174         @Override
 175         public Locale[] getAvailableLocales() {
 176             return getAvailableLocalesDelegate();
 177         }
 178 
 179         @Override
 180         public boolean isSupportedLocale(Locale locale) {
 181             return isSupportedLocaleDelegate(locale);
 182         }
 183 
 184         @Override
 185         public BreakIterator getWordInstance(Locale locale) {
 186             locale = CalendarDataUtility.findRegionOverride(locale);
 187             BreakIteratorProvider bip = getImpl(locale);
 188             return bip.getWordInstance(locale);
 189         }
 190 
 191         @Override
 192         public BreakIterator getLineInstance(Locale locale) {
 193             locale = CalendarDataUtility.findRegionOverride(locale);
 194             BreakIteratorProvider bip = getImpl(locale);
 195             return bip.getLineInstance(locale);
 196         }
 197 
 198         @Override
 199         public BreakIterator getCharacterInstance(Locale locale) {
 200             locale = CalendarDataUtility.findRegionOverride(locale);
 201             BreakIteratorProvider bip = getImpl(locale);
 202             return bip.getCharacterInstance(locale);
 203         }
 204 
 205         @Override
 206         public BreakIterator getSentenceInstance(Locale locale) {
 207             locale = CalendarDataUtility.findRegionOverride(locale);
 208             BreakIteratorProvider bip = getImpl(locale);
 209             return bip.getSentenceInstance(locale);
 210         }
 211 
 212     }
 213 
 214     static class CollatorProviderDelegate extends CollatorProvider implements Delegate<CollatorProvider> {
 215         private final Map<Locale, CollatorProvider> map = new ConcurrentHashMap<>();
 216 
 217         @Override
 218         public Map<Locale, CollatorProvider> getDelegateMap() {
 219             return map;
 220         }
 221 
 222         @Override
 223         public Locale[] getAvailableLocales() {
 224             return getAvailableLocalesDelegate();
 225         }
 226 
 227         @Override
 228         public boolean isSupportedLocale(Locale locale) {
 229             return isSupportedLocaleDelegate(locale);
 230         }
 231 
 232         @Override
 233         public Collator getInstance(Locale locale) {
 234             locale = CalendarDataUtility.findRegionOverride(locale);
 235             CollatorProvider cp = getImpl(locale);
 236             return cp.getInstance(locale);
 237         }
 238     }
 239 
 240     static class DateFormatProviderDelegate extends DateFormatProvider
 241                                      implements Delegate<DateFormatProvider> {
 242         private final Map<Locale, DateFormatProvider> map = new ConcurrentHashMap<>();
 243 
 244         @Override
 245         public Map<Locale, DateFormatProvider> getDelegateMap() {
 246             return map;
 247         }
 248 
 249         @Override
 250         public Locale[] getAvailableLocales() {
 251             return getAvailableLocalesDelegate();
 252         }
 253 
 254         @Override
 255         public boolean isSupportedLocale(Locale locale) {
 256             return isSupportedLocaleDelegate(locale);
 257         }
 258 
 259         @Override
 260         public DateFormat getTimeInstance(int style, Locale locale) {
 261             locale = CalendarDataUtility.findRegionOverride(locale);
 262             DateFormatProvider dfp = getImpl(locale);
 263             return dfp.getTimeInstance(style, locale);
 264         }
 265 
 266         @Override
 267         public DateFormat getDateInstance(int style, Locale locale) {
 268             locale = CalendarDataUtility.findRegionOverride(locale);
 269             DateFormatProvider dfp = getImpl(locale);
 270             return dfp.getDateInstance(style, locale);
 271         }
 272 
 273         @Override
 274         public DateFormat getDateTimeInstance(int dateStyle, int timeStyle, Locale locale) {
 275             locale = CalendarDataUtility.findRegionOverride(locale);
 276             DateFormatProvider dfp = getImpl(locale);
 277             return dfp.getDateTimeInstance(dateStyle, timeStyle, locale);
 278         }
 279     }
 280 
 281     static class DateFormatSymbolsProviderDelegate extends DateFormatSymbolsProvider
 282                                             implements Delegate<DateFormatSymbolsProvider> {
 283         private final Map<Locale, DateFormatSymbolsProvider> map = new ConcurrentHashMap<>();
 284 
 285         @Override
 286         public Map<Locale, DateFormatSymbolsProvider> getDelegateMap() {
 287             return map;
 288         }
 289 
 290         @Override
 291         public Locale[] getAvailableLocales() {
 292             return getAvailableLocalesDelegate();
 293         }
 294 
 295         @Override
 296         public boolean isSupportedLocale(Locale locale) {
 297             return isSupportedLocaleDelegate(locale);
 298         }
 299 
 300         @Override
 301         public DateFormatSymbols getInstance(Locale locale) {
 302             locale = CalendarDataUtility.findRegionOverride(locale);
 303             DateFormatSymbolsProvider dfsp = getImpl(locale);
 304             return dfsp.getInstance(locale);
 305         }
 306     }
 307 
 308     static class DecimalFormatSymbolsProviderDelegate extends DecimalFormatSymbolsProvider
 309                                                implements Delegate<DecimalFormatSymbolsProvider> {
 310         private final Map<Locale, DecimalFormatSymbolsProvider> map = new ConcurrentHashMap<>();
 311 
 312         @Override
 313         public Map<Locale, DecimalFormatSymbolsProvider> getDelegateMap() {
 314             return map;
 315         }
 316 
 317         @Override
 318         public Locale[] getAvailableLocales() {
 319             return getAvailableLocalesDelegate();
 320         }
 321 
 322         @Override
 323         public boolean isSupportedLocale(Locale locale) {
 324             return isSupportedLocaleDelegate(locale);
 325         }
 326 
 327         @Override
 328         public DecimalFormatSymbols getInstance(Locale locale) {
 329             locale = CalendarDataUtility.findRegionOverride(locale);
 330             DecimalFormatSymbolsProvider dfsp = getImpl(locale);
 331             return dfsp.getInstance(locale);
 332         }
 333     }
 334 
 335     static class NumberFormatProviderDelegate extends NumberFormatProvider
 336                                        implements Delegate<NumberFormatProvider> {
 337         private final Map<Locale, NumberFormatProvider> map = new ConcurrentHashMap<>();
 338 
 339         @Override
 340         public Map<Locale, NumberFormatProvider> getDelegateMap() {
 341             return map;
 342         }
 343 
 344         @Override
 345         public Locale[] getAvailableLocales() {
 346             return getAvailableLocalesDelegate();
 347         }
 348 
 349         @Override
 350         public boolean isSupportedLocale(Locale locale) {
 351             return isSupportedLocaleDelegate(locale);
 352         }
 353 
 354         @Override
 355         public NumberFormat getCurrencyInstance(Locale locale) {
 356             locale = CalendarDataUtility.findRegionOverride(locale);
 357             NumberFormatProvider nfp = getImpl(locale);
 358             return nfp.getCurrencyInstance(locale);
 359         }
 360 
 361         @Override
 362         public NumberFormat getIntegerInstance(Locale locale) {
 363             locale = CalendarDataUtility.findRegionOverride(locale);
 364             NumberFormatProvider nfp = getImpl(locale);
 365             return nfp.getIntegerInstance(locale);
 366         }
 367 
 368         @Override
 369         public NumberFormat getNumberInstance(Locale locale) {
 370             locale = CalendarDataUtility.findRegionOverride(locale);
 371             NumberFormatProvider nfp = getImpl(locale);
 372             return nfp.getNumberInstance(locale);
 373         }
 374 
 375         @Override
 376         public NumberFormat getPercentInstance(Locale locale) {
 377             locale = CalendarDataUtility.findRegionOverride(locale);
 378             NumberFormatProvider nfp = getImpl(locale);
 379             return nfp.getPercentInstance(locale);
 380         }
 381 
 382         @Override
 383         public NumberFormat getCompactNumberInstance(Locale locale,
 384                                 NumberFormat.Style style) {
 385             locale = CalendarDataUtility.findRegionOverride(locale);
 386             NumberFormatProvider nfp = getImpl(locale);
 387             return nfp.getCompactNumberInstance(locale, style);
 388         }
 389     }
 390 
 391     static class CalendarDataProviderDelegate extends CalendarDataProvider
 392                                        implements Delegate<CalendarDataProvider> {
 393         private final Map<Locale, CalendarDataProvider> map = new ConcurrentHashMap<>();
 394 
 395         @Override
 396         public Map<Locale, CalendarDataProvider> getDelegateMap() {
 397             return map;
 398         }
 399 
 400         @Override
 401         public Locale[] getAvailableLocales() {
 402             return getAvailableLocalesDelegate();
 403         }
 404 
 405         @Override
 406         public boolean isSupportedLocale(Locale locale) {
 407             return isSupportedLocaleDelegate(locale);
 408         }
 409 
 410         @Override
 411         public int getFirstDayOfWeek(Locale locale) {
 412             locale = CalendarDataUtility.findRegionOverride(locale);
 413             CalendarDataProvider cdp = getImpl(locale);
 414             return cdp.getFirstDayOfWeek(locale);
 415         }
 416 
 417         @Override
 418         public int getMinimalDaysInFirstWeek(Locale locale) {
 419             locale = CalendarDataUtility.findRegionOverride(locale);
 420             CalendarDataProvider cdp = getImpl(locale);
 421             return cdp.getMinimalDaysInFirstWeek(locale);
 422         }
 423     }
 424 
 425     static class CalendarNameProviderDelegate extends CalendarNameProvider
 426                                        implements Delegate<CalendarNameProvider> {
 427         private final Map<Locale, CalendarNameProvider> map = new ConcurrentHashMap<>();
 428 
 429         @Override
 430         public Map<Locale, CalendarNameProvider> getDelegateMap() {
 431             return map;
 432         }
 433 
 434         @Override
 435         public Locale[] getAvailableLocales() {
 436             return getAvailableLocalesDelegate();
 437         }
 438 
 439         @Override
 440         public boolean isSupportedLocale(Locale locale) {
 441             return isSupportedLocaleDelegate(locale);
 442         }
 443 
 444         @Override
 445         public String getDisplayName(String calendarType,
 446                                               int field, int value,
 447                                               int style, Locale locale) {
 448             locale = CalendarDataUtility.findRegionOverride(locale);
 449             CalendarNameProvider cdp = getImpl(locale);
 450             return cdp.getDisplayName(calendarType, field, value, style, locale);
 451         }
 452 
 453         @Override
 454         public Map<String, Integer> getDisplayNames(String calendarType,
 455                                                              int field, int style,
 456                                                              Locale locale) {
 457             locale = CalendarDataUtility.findRegionOverride(locale);
 458             CalendarNameProvider cdp = getImpl(locale);
 459             return cdp.getDisplayNames(calendarType, field, style, locale);
 460         }
 461     }
 462 
 463     static class CurrencyNameProviderDelegate extends CurrencyNameProvider
 464                                        implements Delegate<CurrencyNameProvider> {
 465         private final Map<Locale, CurrencyNameProvider> map = new ConcurrentHashMap<>();
 466 
 467         @Override
 468         public Map<Locale, CurrencyNameProvider> getDelegateMap() {
 469             return map;
 470         }
 471 
 472         @Override
 473         public Locale[] getAvailableLocales() {
 474             return getAvailableLocalesDelegate();
 475         }
 476 
 477         @Override
 478         public boolean isSupportedLocale(Locale locale) {
 479             return isSupportedLocaleDelegate(locale);
 480         }
 481 
 482         @Override
 483         public String getSymbol(String currencyCode, Locale locale) {
 484             locale = CalendarDataUtility.findRegionOverride(locale);
 485             CurrencyNameProvider cnp = getImpl(locale);
 486             return cnp.getSymbol(currencyCode, locale);
 487         }
 488 
 489         @Override
 490         public String getDisplayName(String currencyCode, Locale locale) {
 491             locale = CalendarDataUtility.findRegionOverride(locale);
 492             CurrencyNameProvider cnp = getImpl(locale);
 493             return cnp.getDisplayName(currencyCode, locale);
 494         }
 495     }
 496 
 497     static class LocaleNameProviderDelegate extends LocaleNameProvider
 498                                      implements Delegate<LocaleNameProvider> {
 499         private final Map<Locale, LocaleNameProvider> map = new ConcurrentHashMap<>();
 500 
 501         @Override
 502         public Map<Locale, LocaleNameProvider> getDelegateMap() {
 503             return map;
 504         }
 505 
 506         @Override
 507         public Locale[] getAvailableLocales() {
 508             return getAvailableLocalesDelegate();
 509         }
 510 
 511         @Override
 512         public boolean isSupportedLocale(Locale locale) {
 513             return isSupportedLocaleDelegate(locale);
 514         }
 515 
 516         @Override
 517         public String getDisplayLanguage(String languageCode, Locale locale) {
 518             locale = CalendarDataUtility.findRegionOverride(locale);
 519             LocaleNameProvider lnp = getImpl(locale);
 520             return lnp.getDisplayLanguage(languageCode, locale);
 521         }
 522 
 523         @Override
 524         public String getDisplayScript(String scriptCode, Locale locale) {
 525             locale = CalendarDataUtility.findRegionOverride(locale);
 526             LocaleNameProvider lnp = getImpl(locale);
 527             return lnp.getDisplayScript(scriptCode, locale);
 528         }
 529 
 530         @Override
 531         public String getDisplayCountry(String countryCode, Locale locale) {
 532             locale = CalendarDataUtility.findRegionOverride(locale);
 533             LocaleNameProvider lnp = getImpl(locale);
 534             return lnp.getDisplayCountry(countryCode, locale);
 535         }
 536 
 537         @Override
 538         public String getDisplayVariant(String variant, Locale locale) {
 539             locale = CalendarDataUtility.findRegionOverride(locale);
 540             LocaleNameProvider lnp = getImpl(locale);
 541             return lnp.getDisplayVariant(variant, locale);
 542         }
 543 
 544         @Override
 545         public String getDisplayUnicodeExtensionKey(String key, Locale locale) {
 546             locale = CalendarDataUtility.findRegionOverride(locale);
 547             LocaleNameProvider lnp = getImpl(locale);
 548             return lnp.getDisplayUnicodeExtensionKey(key, locale);
 549         }
 550 
 551         @Override
 552         public String getDisplayUnicodeExtensionType(String extType, String key, Locale locale) {
 553             locale = CalendarDataUtility.findRegionOverride(locale);
 554             LocaleNameProvider lnp = getImpl(locale);
 555             return lnp.getDisplayUnicodeExtensionType(extType, key, locale);
 556         }
 557     }
 558 
 559     static class TimeZoneNameProviderDelegate extends TimeZoneNameProvider
 560                                      implements Delegate<TimeZoneNameProvider> {
 561         private final Map<Locale, TimeZoneNameProvider> map = new ConcurrentHashMap<>();
 562 
 563         @Override
 564         public Map<Locale, TimeZoneNameProvider> getDelegateMap() {
 565             return map;
 566         }
 567 
 568         @Override
 569         public Locale[] getAvailableLocales() {
 570             return getAvailableLocalesDelegate();
 571         }
 572 
 573         @Override
 574         public boolean isSupportedLocale(Locale locale) {
 575             return isSupportedLocaleDelegate(locale);
 576         }
 577 
 578         @Override
 579         public String getDisplayName(String ID, boolean daylight, int style, Locale locale) {
 580             locale = CalendarDataUtility.findRegionOverride(locale);
 581             TimeZoneNameProvider tznp = getImpl(locale);
 582             return tznp.getDisplayName(ID, daylight, style, locale);
 583         }
 584 
 585         @Override
 586         public String getGenericDisplayName(String ID, int style, Locale locale) {
 587             locale = CalendarDataUtility.findRegionOverride(locale);
 588             TimeZoneNameProvider tznp = getImpl(locale);
 589             return tznp.getGenericDisplayName(ID, style, locale);
 590         }
 591     }
 592 }