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