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.AccessControlException;
  30 import java.security.PrivilegedAction;
  31 import java.security.PrivilegedActionException;
  32 import java.security.PrivilegedExceptionAction;
  33 import java.text.spi.BreakIteratorProvider;
  34 import java.text.spi.CollatorProvider;
  35 import java.text.spi.DateFormatProvider;
  36 import java.text.spi.DateFormatSymbolsProvider;
  37 import java.text.spi.DecimalFormatSymbolsProvider;
  38 import java.text.spi.NumberFormatProvider;
  39 import java.util.Collections;
  40 import java.util.HashSet;
  41 import java.util.List;
  42 import java.util.Locale;
  43 import java.util.ResourceBundle;
  44 import java.util.ServiceLoader;
  45 import java.util.ServiceConfigurationError;
  46 import java.util.Set;
  47 import java.util.StringTokenizer;
  48 import java.util.concurrent.ConcurrentHashMap;
  49 import java.util.concurrent.ConcurrentMap;
  50 import java.util.spi.CalendarDataProvider;
  51 import java.util.spi.CalendarNameProvider;
  52 import java.util.spi.CurrencyNameProvider;
  53 import java.util.spi.LocaleNameProvider;
  54 import java.util.spi.LocaleServiceProvider;
  55 import java.util.spi.TimeZoneNameProvider;
  56 import sun.text.spi.JavaTimeDateTimePatternProvider;
  57 import sun.util.resources.LocaleData;
  58 import sun.util.spi.CalendarProvider;
  59 
  60 /**
  61  * LocaleProviderAdapter implementation for the legacy JRE locale data.
  62  *
  63  * @author Naoto Sato
  64  * @author Masayoshi Okutsu
  65  */
  66 public class JRELocaleProviderAdapter extends LocaleProviderAdapter implements ResourceBundleBasedAdapter {
  67 
  68     private final ConcurrentMap<String, Set<String>> langtagSets
  69         = new ConcurrentHashMap<>();
  70 
  71     private final ConcurrentMap<Locale, LocaleResources> localeResourcesMap
  72         = new ConcurrentHashMap<>();
  73 
  74     // LocaleData specific to this LocaleProviderAdapter.
  75     private volatile LocaleData localeData;
  76 
  77     /**
  78      * Returns the type of this LocaleProviderAdapter
  79      */
  80     @Override
  81     public LocaleProviderAdapter.Type getAdapterType() {
  82         return Type.JRE;
  83     }
  84 
  85     /**
  86      * Getter method for Locale Service Providers
  87      */
  88     @Override
  89     @SuppressWarnings("unchecked")
  90     public <P extends LocaleServiceProvider> P getLocaleServiceProvider(Class<P> c) {
  91         switch (c.getSimpleName()) {
  92         case "BreakIteratorProvider":
  93             return (P) getBreakIteratorProvider();
  94         case "CollatorProvider":
  95             return (P) getCollatorProvider();
  96         case "DateFormatProvider":
  97             return (P) getDateFormatProvider();
  98         case "DateFormatSymbolsProvider":
  99             return (P) getDateFormatSymbolsProvider();
 100         case "DecimalFormatSymbolsProvider":
 101             return (P) getDecimalFormatSymbolsProvider();
 102         case "NumberFormatProvider":
 103             return (P) getNumberFormatProvider();
 104         case "CurrencyNameProvider":
 105             return (P) getCurrencyNameProvider();
 106         case "LocaleNameProvider":
 107             return (P) getLocaleNameProvider();
 108         case "TimeZoneNameProvider":
 109             return (P) getTimeZoneNameProvider();
 110         case "CalendarDataProvider":
 111             return (P) getCalendarDataProvider();
 112         case "CalendarNameProvider":
 113             return (P) getCalendarNameProvider();
 114         case "CalendarProvider":
 115             return (P) getCalendarProvider();
 116         case "JavaTimeDateTimePatternProvider":
 117             return (P) getJavaTimeDateTimePatternProvider();
 118         default:
 119             throw new InternalError("should not come down here");
 120         }
 121     }
 122 
 123     private volatile BreakIteratorProvider breakIteratorProvider;
 124     private volatile CollatorProvider collatorProvider;
 125     private volatile DateFormatProvider dateFormatProvider;
 126     private volatile DateFormatSymbolsProvider dateFormatSymbolsProvider;
 127     private volatile DecimalFormatSymbolsProvider decimalFormatSymbolsProvider;
 128     private volatile NumberFormatProvider numberFormatProvider;
 129 
 130     private volatile CurrencyNameProvider currencyNameProvider;
 131     private volatile LocaleNameProvider localeNameProvider;
 132     protected volatile TimeZoneNameProvider timeZoneNameProvider;
 133     protected volatile CalendarDataProvider calendarDataProvider;
 134     protected volatile CalendarNameProvider calendarNameProvider;
 135 
 136     private volatile CalendarProvider calendarProvider;
 137     private volatile JavaTimeDateTimePatternProvider javaTimeDateTimePatternProvider;
 138 
 139     /*
 140      * Getter methods for java.text.spi.* providers
 141      */
 142     @Override
 143     public BreakIteratorProvider getBreakIteratorProvider() {
 144         if (breakIteratorProvider == null) {
 145             BreakIteratorProvider provider = AccessController.doPrivileged(
 146                 (PrivilegedAction<BreakIteratorProvider>) () ->
 147                     new BreakIteratorProviderImpl(
 148                         getAdapterType(),
 149                         getLanguageTagSet("FormatData")));
 150 
 151             synchronized (this) {
 152                 if (breakIteratorProvider == null) {
 153                     breakIteratorProvider = provider;
 154                 }
 155             }
 156         }
 157         return breakIteratorProvider;
 158     }
 159 
 160     @Override
 161     public CollatorProvider getCollatorProvider() {
 162         if (collatorProvider == null) {
 163             CollatorProvider provider = AccessController.doPrivileged(
 164                 (PrivilegedAction<CollatorProvider>) () ->
 165                     new CollatorProviderImpl(
 166                         getAdapterType(),
 167                         getLanguageTagSet("CollationData")));
 168 
 169             synchronized (this) {
 170                 if (collatorProvider == null) {
 171                     collatorProvider = provider;
 172                 }
 173             }
 174         }
 175         return collatorProvider;
 176     }
 177 
 178     @Override
 179     public DateFormatProvider getDateFormatProvider() {
 180         if (dateFormatProvider == null) {
 181             DateFormatProvider provider = AccessController.doPrivileged(
 182                 (PrivilegedAction<DateFormatProvider>) () ->
 183                     new DateFormatProviderImpl(
 184                         getAdapterType(),
 185                         getLanguageTagSet("FormatData")));
 186 
 187             synchronized (this) {
 188                 if (dateFormatProvider == null) {
 189                     dateFormatProvider = provider;
 190                 }
 191             }
 192         }
 193         return dateFormatProvider;
 194     }
 195 
 196     @Override
 197     public DateFormatSymbolsProvider getDateFormatSymbolsProvider() {
 198         if (dateFormatSymbolsProvider == null) {
 199             DateFormatSymbolsProvider provider = AccessController.doPrivileged(
 200                 (PrivilegedAction<DateFormatSymbolsProvider>) () ->
 201                     new DateFormatSymbolsProviderImpl(
 202                         getAdapterType(),
 203                         getLanguageTagSet("FormatData")));
 204 
 205             synchronized (this) {
 206                 if (dateFormatSymbolsProvider == null) {
 207                     dateFormatSymbolsProvider = provider;
 208                 }
 209             }
 210         }
 211         return dateFormatSymbolsProvider;
 212     }
 213 
 214     @Override
 215     public DecimalFormatSymbolsProvider getDecimalFormatSymbolsProvider() {
 216         if (decimalFormatSymbolsProvider == null) {
 217             DecimalFormatSymbolsProvider provider = AccessController.doPrivileged(
 218                 (PrivilegedAction<DecimalFormatSymbolsProvider>) () ->
 219                     new DecimalFormatSymbolsProviderImpl(
 220                         getAdapterType(),
 221                         getLanguageTagSet("FormatData")));
 222 
 223             synchronized (this) {
 224                 if (decimalFormatSymbolsProvider == null) {
 225                     decimalFormatSymbolsProvider = provider;
 226                 }
 227             }
 228         }
 229         return decimalFormatSymbolsProvider;
 230     }
 231 
 232     @Override
 233     public NumberFormatProvider getNumberFormatProvider() {
 234         if (numberFormatProvider == null) {
 235             NumberFormatProvider provider = AccessController.doPrivileged(
 236                 (PrivilegedAction<NumberFormatProvider>) () ->
 237                     new NumberFormatProviderImpl(
 238                         getAdapterType(),
 239                         getLanguageTagSet("FormatData")));
 240 
 241             synchronized (this) {
 242                 if (numberFormatProvider == null) {
 243                     numberFormatProvider = provider;
 244                 }
 245             }
 246         }
 247         return numberFormatProvider;
 248     }
 249 
 250     /**
 251      * Getter methods for java.util.spi.* providers
 252      */
 253     @Override
 254     public CurrencyNameProvider getCurrencyNameProvider() {
 255         if (currencyNameProvider == null) {
 256             CurrencyNameProvider provider = AccessController.doPrivileged(
 257                 (PrivilegedAction<CurrencyNameProvider>) () ->
 258                     new CurrencyNameProviderImpl(
 259                         getAdapterType(),
 260                         getLanguageTagSet("CurrencyNames")));
 261 
 262             synchronized (this) {
 263                 if (currencyNameProvider == null) {
 264                     currencyNameProvider = provider;
 265                 }
 266             }
 267         }
 268         return currencyNameProvider;
 269     }
 270 
 271     @Override
 272     public LocaleNameProvider getLocaleNameProvider() {
 273         if (localeNameProvider == null) {
 274             LocaleNameProvider provider = AccessController.doPrivileged(
 275                 (PrivilegedAction<LocaleNameProvider>) () ->
 276                     new LocaleNameProviderImpl(
 277                         getAdapterType(),
 278                         getLanguageTagSet("LocaleNames")));
 279 
 280             synchronized (this) {
 281                 if (localeNameProvider == null) {
 282                     localeNameProvider = provider;
 283                 }
 284             }
 285         }
 286         return localeNameProvider;
 287     }
 288 
 289     @Override
 290     public TimeZoneNameProvider getTimeZoneNameProvider() {
 291         if (timeZoneNameProvider == null) {
 292             TimeZoneNameProvider provider = AccessController.doPrivileged(
 293                 (PrivilegedAction<TimeZoneNameProvider>) () ->
 294                     new TimeZoneNameProviderImpl(
 295                         getAdapterType(),
 296                         getLanguageTagSet("TimeZoneNames")));
 297 
 298             synchronized (this) {
 299                 if (timeZoneNameProvider == null) {
 300                     timeZoneNameProvider = provider;
 301                 }
 302             }
 303         }
 304         return timeZoneNameProvider;
 305     }
 306 
 307     @Override
 308     public CalendarDataProvider getCalendarDataProvider() {
 309         if (calendarDataProvider == null) {
 310             CalendarDataProvider provider = AccessController.doPrivileged(
 311                 (PrivilegedAction<CalendarDataProvider>) () ->
 312                     new CalendarDataProviderImpl(
 313                         getAdapterType(),
 314                         getLanguageTagSet("CalendarData")));
 315 
 316             synchronized (this) {
 317                 if (calendarDataProvider == null) {
 318                     calendarDataProvider = provider;
 319                 }
 320             }
 321         }
 322         return calendarDataProvider;
 323     }
 324 
 325     @Override
 326     public CalendarNameProvider getCalendarNameProvider() {
 327         if (calendarNameProvider == null) {
 328             CalendarNameProvider provider = AccessController.doPrivileged(
 329                 (PrivilegedAction<CalendarNameProvider>) () ->
 330                     new CalendarNameProviderImpl(
 331                         getAdapterType(),
 332                         getLanguageTagSet("FormatData")));
 333 
 334             synchronized (this) {
 335                 if (calendarNameProvider == null) {
 336                     calendarNameProvider = provider;
 337                 }
 338             }
 339         }
 340         return calendarNameProvider;
 341     }
 342 
 343     /**
 344      * Getter methods for sun.util.spi.* providers
 345      */
 346     @Override
 347     public CalendarProvider getCalendarProvider() {
 348         if (calendarProvider == null) {
 349             CalendarProvider provider = AccessController.doPrivileged(
 350                 (PrivilegedAction<CalendarProvider>) () ->
 351                     new CalendarProviderImpl(
 352                         getAdapterType(),
 353                         getLanguageTagSet("CalendarData")));
 354 
 355             synchronized (this) {
 356                 if (calendarProvider == null) {
 357                     calendarProvider = provider;
 358                 }
 359             }
 360         }
 361         return calendarProvider;
 362     }
 363 
 364     /**
 365      * Getter methods for sun.text.spi.JavaTimeDateTimePatternProvider provider
 366      */
 367     @Override
 368     public JavaTimeDateTimePatternProvider getJavaTimeDateTimePatternProvider() {
 369         if (javaTimeDateTimePatternProvider == null) {
 370             JavaTimeDateTimePatternProvider provider = AccessController.doPrivileged(
 371                     (PrivilegedAction<JavaTimeDateTimePatternProvider>) ()
 372                     -> new JavaTimeDateTimePatternImpl(
 373                             getAdapterType(),
 374                             getLanguageTagSet("FormatData")));
 375 
 376             synchronized (this) {
 377                 if (javaTimeDateTimePatternProvider == null) {
 378                     javaTimeDateTimePatternProvider = provider;
 379                 }
 380             }
 381         }
 382         return javaTimeDateTimePatternProvider;
 383     }
 384 
 385     @Override
 386     public LocaleResources getLocaleResources(Locale locale) {
 387         LocaleResources lr = localeResourcesMap.get(locale);
 388         if (lr == null) {
 389             lr = new LocaleResources(this, locale);
 390             LocaleResources lrc = localeResourcesMap.putIfAbsent(locale, lr);
 391             if (lrc != null) {
 392                 lr = lrc;
 393             }
 394         }
 395         return lr;
 396     }
 397 
 398     // ResourceBundleBasedAdapter method implementation
 399 
 400     @Override
 401     public LocaleData getLocaleData() {
 402         if (localeData == null) {
 403             synchronized (this) {
 404                 if (localeData == null) {
 405                     localeData = new LocaleData(getAdapterType());
 406                 }
 407             }
 408         }
 409         return localeData;
 410     }
 411 
 412     @Override
 413     public List<Locale> getCandidateLocales(String baseName, Locale locale) {
 414         return ResourceBundle.Control
 415             .getNoFallbackControl(ResourceBundle.Control.FORMAT_DEFAULT)
 416             .getCandidateLocales(baseName, locale);
 417     }
 418 
 419     /**
 420      * Returns a list of the installed locales. Currently, this simply returns
 421      * the list of locales for which a sun.text.resources.FormatData bundle
 422      * exists. This bundle family happens to be the one with the broadest
 423      * locale coverage in the JRE.
 424      */
 425     @Override
 426     public Locale[] getAvailableLocales() {
 427         return AvailableJRELocales.localeList.clone();
 428     }
 429 
 430     public Set<String> getLanguageTagSet(String category) {
 431         Set<String> tagset = langtagSets.get(category);
 432         if (tagset == null) {
 433             tagset = createLanguageTagSet(category);
 434             Set<String> ts = langtagSets.putIfAbsent(category, tagset);
 435             if (ts != null) {
 436                 tagset = ts;
 437             }
 438         }
 439         return tagset;
 440     }
 441 
 442     protected Set<String> createLanguageTagSet(String category) {
 443         String supportedLocaleString = createSupportedLocaleString(category);
 444         if (supportedLocaleString == null) {
 445             return Collections.emptySet();
 446         }
 447         Set<String> tagset = new HashSet<>();
 448         StringTokenizer tokens = new StringTokenizer(supportedLocaleString);
 449         while (tokens.hasMoreTokens()) {
 450             tagset.add(tokens.nextToken());
 451         }
 452 
 453         return tagset;
 454     }
 455 
 456     private static String createSupportedLocaleString(String category) {
 457         // Directly call Base tags, as we know it's in the base module.
 458         String supportedLocaleString = BaseLocaleDataMetaInfo.getSupportedLocaleString(category);
 459 
 460         // Use ServiceLoader to dynamically acquire installed locales' tags.
 461         try {
 462             String nonBaseTags = AccessController.doPrivileged((PrivilegedExceptionAction<String>) () -> {
 463                 StringBuilder tags = new StringBuilder();
 464                 for (LocaleDataMetaInfo ldmi :
 465                         ServiceLoader.loadInstalled(LocaleDataMetaInfo.class)) {
 466                     if (ldmi.getType() == LocaleProviderAdapter.Type.JRE) {
 467                         String t = ldmi.availableLanguageTags(category);
 468                         if (t != null) {
 469                             if (tags.length() > 0) {
 470                                 tags.append(' ');
 471                             }
 472                             tags.append(t);
 473                         }
 474                     }
 475                 }
 476                 return tags.toString();
 477             });
 478 
 479             if (nonBaseTags != null) {
 480                 supportedLocaleString += " " + nonBaseTags;
 481             }
 482         } catch (PrivilegedActionException pae) {
 483             throw new InternalError(pae.getCause());
 484         }
 485 
 486         return supportedLocaleString;
 487     }
 488 
 489     /**
 490      * Lazy load available locales.
 491      */
 492     private static class AvailableJRELocales {
 493         private static final Locale[] localeList = createAvailableLocales();
 494         private AvailableJRELocales() {
 495         }
 496     }
 497 
 498     private static Locale[] createAvailableLocales() {
 499         /*
 500          * Gets the locale string list from LocaleDataMetaInfo classes and then
 501          * contructs the Locale array and a set of language tags based on the
 502          * locale string returned above.
 503          */
 504         String supportedLocaleString = createSupportedLocaleString("AvailableLocales");
 505 
 506         if (supportedLocaleString.isEmpty()) {
 507             throw new InternalError("No available locales for JRE");
 508         }
 509 
 510         StringTokenizer localeStringTokenizer = new StringTokenizer(supportedLocaleString);
 511 
 512         int length = localeStringTokenizer.countTokens();
 513         Locale[] locales = new Locale[length + 1];
 514         locales[0] = Locale.ROOT;
 515         for (int i = 1; i <= length; i++) {
 516             String currentToken = localeStringTokenizer.nextToken();
 517             switch (currentToken) {
 518                 case "ja-JP-JP":
 519                     locales[i] = JRELocaleConstants.JA_JP_JP;
 520                     break;
 521                 case "no-NO-NY":
 522                     locales[i] = JRELocaleConstants.NO_NO_NY;
 523                     break;
 524                 case "th-TH-TH":
 525                     locales[i] = JRELocaleConstants.TH_TH_TH;
 526                     break;
 527                 default:
 528                     locales[i] = Locale.forLanguageTag(currentToken);
 529             }
 530         }
 531         return locales;
 532     }
 533 
 534     @Override
 535     public boolean isSupportedProviderLocale(Locale locale,  Set<String> langtags) {
 536         if (Locale.ROOT.equals(locale)) {
 537             return true;
 538 }
 539 
 540         locale = locale.stripExtensions();
 541         if (langtags.contains(locale.toLanguageTag())) {
 542             return true;
 543         }
 544 
 545         String oldname = locale.toString().replace('_', '-');
 546         return langtags.contains(oldname) ||
 547                    "ja-JP-JP".equals(oldname) ||
 548                    "th-TH-TH".equals(oldname) ||
 549                    "no-NO-NY".equals(oldname);
 550     }
 551 }