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