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