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