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