1 /*
   2  * Copyright (c) 2012, 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.io.File;
  29 import java.security.AccessController;
  30 import java.security.PrivilegedAction;
  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.HashSet;
  38 import java.util.Locale;
  39 import java.util.Set;
  40 import java.util.StringTokenizer;
  41 import java.util.concurrent.ConcurrentHashMap;
  42 import java.util.concurrent.ConcurrentMap;
  43 import java.util.spi.CalendarDataProvider;
  44 import java.util.spi.CalendarNameProvider;
  45 import java.util.spi.CurrencyNameProvider;
  46 import java.util.spi.LocaleNameProvider;
  47 import java.util.spi.LocaleServiceProvider;
  48 import java.util.spi.TimeZoneNameProvider;
  49 import sun.util.resources.LocaleData;
  50 import sun.util.spi.CalendarProvider;
  51 
  52 /**
  53  * LocaleProviderAdapter implementation for the legacy JRE locale data.
  54  *
  55  * @author Naoto Sato
  56  * @author Masayoshi Okutsu
  57  */
  58 public class JRELocaleProviderAdapter extends LocaleProviderAdapter implements ResourceBundleBasedAdapter {
  59 
  60     private static final String LOCALE_DATA_JAR_NAME = "localedata.jar";
  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 = new BreakIteratorProviderImpl(getAdapterType(),
 137                                                             getLanguageTagSet("FormatData"));
 138             synchronized (this) {
 139                 if (breakIteratorProvider == null) {
 140                     breakIteratorProvider = provider;
 141                 }
 142             }
 143         }
 144         return breakIteratorProvider;
 145     }
 146 
 147     @Override
 148     public CollatorProvider getCollatorProvider() {
 149         if (collatorProvider == null) {
 150             CollatorProvider provider = new CollatorProviderImpl(getAdapterType(),
 151                                                 getLanguageTagSet("CollationData"));
 152             synchronized (this) {
 153                 if (collatorProvider == null) {
 154                     collatorProvider = provider;
 155                 }
 156             }
 157         }
 158         return collatorProvider;
 159     }
 160 
 161     @Override
 162     public DateFormatProvider getDateFormatProvider() {
 163         if (dateFormatProvider == null) {
 164             DateFormatProvider provider = new DateFormatProviderImpl(getAdapterType(),
 165                                                     getLanguageTagSet("FormatData"));
 166             synchronized (this) {
 167                 if (dateFormatProvider == null) {
 168                     dateFormatProvider = provider;
 169                 }
 170             }
 171         }
 172         return dateFormatProvider;
 173     }
 174 
 175     @Override
 176     public DateFormatSymbolsProvider getDateFormatSymbolsProvider() {
 177         if (dateFormatSymbolsProvider == null) {
 178             DateFormatSymbolsProvider provider = new DateFormatSymbolsProviderImpl(getAdapterType(),
 179                                                                 getLanguageTagSet("FormatData"));
 180             synchronized (this) {
 181                 if (dateFormatSymbolsProvider == null) {
 182                     dateFormatSymbolsProvider = provider;
 183                 }
 184             }
 185         }
 186         return dateFormatSymbolsProvider;
 187     }
 188 
 189     @Override
 190     public DecimalFormatSymbolsProvider getDecimalFormatSymbolsProvider() {
 191         if (decimalFormatSymbolsProvider == null) {
 192             DecimalFormatSymbolsProvider provider = new DecimalFormatSymbolsProviderImpl(getAdapterType(), getLanguageTagSet("FormatData"));
 193             synchronized (this) {
 194                 if (decimalFormatSymbolsProvider == null) {
 195                     decimalFormatSymbolsProvider = provider;
 196                 }
 197             }
 198         }
 199         return decimalFormatSymbolsProvider;
 200     }
 201 
 202     @Override
 203     public NumberFormatProvider getNumberFormatProvider() {
 204         if (numberFormatProvider == null) {
 205             NumberFormatProvider provider = new NumberFormatProviderImpl(getAdapterType(),
 206                                                         getLanguageTagSet("FormatData"));
 207             synchronized (this) {
 208                 if (numberFormatProvider == null) {
 209                     numberFormatProvider = provider;
 210                 }
 211             }
 212         }
 213         return numberFormatProvider;
 214     }
 215 
 216     /**
 217      * Getter methods for java.util.spi.* providers
 218      */
 219     @Override
 220     public CurrencyNameProvider getCurrencyNameProvider() {
 221         if (currencyNameProvider == null) {
 222             CurrencyNameProvider provider = new CurrencyNameProviderImpl(getAdapterType(),
 223                                             getLanguageTagSet("CurrencyNames"));
 224             synchronized (this) {
 225                 if (currencyNameProvider == null) {
 226                     currencyNameProvider = provider;
 227                 }
 228             }
 229         }
 230         return currencyNameProvider;
 231     }
 232 
 233     @Override
 234     public LocaleNameProvider getLocaleNameProvider() {
 235         if (localeNameProvider == null) {
 236             LocaleNameProvider provider = new LocaleNameProviderImpl(getAdapterType(),
 237                                                     getLanguageTagSet("LocaleNames"));
 238             synchronized (this) {
 239                 if (localeNameProvider == null) {
 240                     localeNameProvider = provider;
 241                 }
 242             }
 243         }
 244         return localeNameProvider;
 245     }
 246 
 247     @Override
 248     public TimeZoneNameProvider getTimeZoneNameProvider() {
 249         if (timeZoneNameProvider == null) {
 250             TimeZoneNameProvider provider = new TimeZoneNameProviderImpl(getAdapterType(),
 251                                                     getLanguageTagSet("TimeZoneNames"));
 252             synchronized (this) {
 253                 if (timeZoneNameProvider == null) {
 254                     timeZoneNameProvider = provider;
 255                 }
 256             }
 257         }
 258         return timeZoneNameProvider;
 259     }
 260 
 261     @Override
 262     public CalendarDataProvider getCalendarDataProvider() {
 263         if (calendarDataProvider == null) {
 264             CalendarDataProvider provider;
 265             provider = new CalendarDataProviderImpl(getAdapterType(),
 266                                                     getLanguageTagSet("CalendarData"));
 267             synchronized (this) {
 268                 if (calendarDataProvider == null) {
 269                     calendarDataProvider = provider;
 270                 }
 271             }
 272         }
 273         return calendarDataProvider;
 274     }
 275 
 276     @Override
 277     public CalendarNameProvider getCalendarNameProvider() {
 278         if (calendarNameProvider == null) {
 279             CalendarNameProvider provider;
 280             provider = new CalendarNameProviderImpl(getAdapterType(),
 281                                                     getLanguageTagSet("FormatData"));
 282             synchronized (this) {
 283                 if (calendarNameProvider == null) {
 284                     calendarNameProvider = provider;
 285                 }
 286             }
 287         }
 288         return calendarNameProvider;
 289     }
 290 
 291     /**
 292      * Getter methods for sun.util.spi.* providers
 293      */
 294     @Override
 295     public CalendarProvider getCalendarProvider() {
 296         if (calendarProvider == null) {
 297             CalendarProvider provider = new CalendarProviderImpl(getAdapterType(),
 298                                                     getLanguageTagSet("CalendarData"));
 299             synchronized (this) {
 300                 if (calendarProvider == null) {
 301                     calendarProvider = provider;
 302                 }
 303             }
 304         }
 305         return calendarProvider;
 306     }
 307 
 308     @Override
 309     public LocaleResources getLocaleResources(Locale locale) {
 310         LocaleResources lr = localeResourcesMap.get(locale);
 311         if (lr == null) {
 312             lr = new LocaleResources(this, locale);
 313             LocaleResources lrc = localeResourcesMap.putIfAbsent(locale, lr);
 314             if (lrc != null) {
 315                 lr = lrc;
 316             }
 317         }
 318         return lr;
 319     }
 320 
 321     // ResourceBundleBasedAdapter method implementation
 322     @Override
 323     public LocaleData getLocaleData() {
 324         if (localeData == null) {
 325             synchronized (this) {
 326                 if (localeData == null) {
 327                     localeData = new LocaleData(getAdapterType());
 328                 }
 329             }
 330         }
 331         return localeData;
 332     }
 333 
 334     /**
 335      * Returns a list of the installed locales. Currently, this simply returns
 336      * the list of locales for which a sun.text.resources.FormatData bundle
 337      * exists. This bundle family happens to be the one with the broadest
 338      * locale coverage in the JRE.
 339      */
 340     @Override
 341     public Locale[] getAvailableLocales() {
 342         return AvailableJRELocales.localeList.clone();
 343     }
 344 
 345     public Set<String> getLanguageTagSet(String category) {
 346         Set<String> tagset = langtagSets.get(category);
 347         if (tagset == null) {
 348             tagset = createLanguageTagSet(category);
 349             Set<String> ts = langtagSets.putIfAbsent(category, tagset);
 350             if (ts != null) {
 351                 tagset = ts;
 352             }
 353         }
 354         return tagset;
 355     }
 356 
 357     protected Set<String> createLanguageTagSet(String category) {
 358         String supportedLocaleString = LocaleDataMetaInfo.getSupportedLocaleString(category);
 359         Set<String> tagset = new HashSet<>();
 360         StringTokenizer tokens = new StringTokenizer(supportedLocaleString);
 361         while (tokens.hasMoreTokens()) {
 362             String token = tokens.nextToken();
 363             if (token.equals("|")) {
 364                 if (isNonUSLangSupported()) {
 365                     continue;
 366                 }
 367                 break;
 368             }
 369             tagset.add(token);
 370         }
 371 
 372         return tagset;
 373     }
 374 
 375     /**
 376      * Lazy load available locales.
 377      */
 378     private static class AvailableJRELocales {
 379         private static final Locale[] localeList = createAvailableLocales();
 380         private AvailableJRELocales() {
 381         }
 382     }
 383 
 384     private static Locale[] createAvailableLocales() {
 385         /*
 386          * Gets the locale string list from LocaleDataMetaInfo class and then
 387          * contructs the Locale array and a set of language tags based on the
 388          * locale string returned above.
 389          */
 390         String supportedLocaleString = LocaleDataMetaInfo.getSupportedLocaleString("AvailableLocales");
 391 
 392         if (supportedLocaleString.length() == 0) {
 393             throw new InternalError("No available locales for JRE");
 394         }
 395 
 396         /*
 397          * Look for "|" and construct a new locale string list.
 398          */
 399         int barIndex = supportedLocaleString.indexOf('|');
 400         StringTokenizer localeStringTokenizer;
 401         if (isNonUSLangSupported()) {
 402             localeStringTokenizer = new StringTokenizer(supportedLocaleString.substring(0, barIndex)
 403                     + supportedLocaleString.substring(barIndex + 1));
 404         } else {
 405             localeStringTokenizer = new StringTokenizer(supportedLocaleString.substring(0, barIndex));
 406         }
 407 
 408         int length = localeStringTokenizer.countTokens();
 409         Locale[] locales = new Locale[length + 1];
 410         locales[0] = Locale.ROOT;
 411         for (int i = 1; i <= length; i++) {
 412             String currentToken = localeStringTokenizer.nextToken();
 413             switch (currentToken) {
 414                 case "ja-JP-JP":
 415                     locales[i] = JRELocaleConstants.JA_JP_JP;
 416                     break;
 417                 case "no-NO-NY":
 418                     locales[i] = JRELocaleConstants.NO_NO_NY;
 419                     break;
 420                 case "th-TH-TH":
 421                     locales[i] = JRELocaleConstants.TH_TH_TH;
 422                     break;
 423                 default:
 424                     locales[i] = Locale.forLanguageTag(currentToken);
 425             }
 426         }
 427         return locales;
 428     }
 429 
 430     private static volatile Boolean isNonUSSupported = null;
 431 
 432     /*
 433      * Returns true if the non US resources jar file exists in jre
 434      * extension directory. @returns true if the jar file is there. Otherwise,
 435      * returns false.
 436      */
 437     private static boolean isNonUSLangSupported() {
 438         if (isNonUSSupported == null) {
 439             synchronized (JRELocaleProviderAdapter.class) {
 440                 if (isNonUSSupported == null) {
 441                     final String sep = File.separator;
 442                     String localeDataJar =
 443                             java.security.AccessController.doPrivileged(
 444                             new sun.security.action.GetPropertyAction("java.home"))
 445                             + sep + "lib" + sep + "ext" + sep + LOCALE_DATA_JAR_NAME;
 446 
 447                     /*
 448                      * Peek at the installed extension directory to see if
 449                      * localedata.jar is installed or not.
 450                      */
 451                     final File f = new File(localeDataJar);
 452                     isNonUSSupported =
 453                         AccessController.doPrivileged(new PrivilegedAction<Boolean>() {
 454                             @Override
 455                             public Boolean run() {
 456                                 return f.exists();
 457                             }
 458                         });
 459                }
 460             }
 461         }
 462         return isNonUSSupported;
 463     }
 464 }