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