1 /*
   2  * Copyright (c) 1996, 2016, 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 /*
  27  * (C) Copyright Taligent, Inc. 1996, 1997 - All Rights Reserved
  28  * (C) Copyright IBM Corp. 1996 - 1998 - All Rights Reserved
  29  *
  30  * The original version of this source code and documentation
  31  * is copyrighted and owned by Taligent, Inc., a wholly-owned
  32  * subsidiary of IBM. These materials are provided under terms
  33  * of a License Agreement between Taligent and Sun. This technology
  34  * is protected by multiple US and International patents.
  35  *
  36  * This notice and attribution to Taligent may not be removed.
  37  * Taligent is a registered trademark of Taligent, Inc.
  38  *
  39  */
  40 
  41 package sun.util.resources;
  42 
  43 import java.security.AccessController;
  44 import java.security.PrivilegedAction;
  45 import java.util.Arrays;
  46 import java.util.Iterator;
  47 import java.util.List;
  48 import java.util.Locale;
  49 import java.util.Map;
  50 import java.util.MissingResourceException;
  51 import java.util.ResourceBundle;
  52 import java.util.Set;
  53 import java.util.concurrent.ConcurrentHashMap;
  54 import java.util.spi.ResourceBundleProvider;
  55 import sun.util.locale.provider.JRELocaleProviderAdapter;
  56 import sun.util.locale.provider.LocaleProviderAdapter;
  57 import static sun.util.locale.provider.LocaleProviderAdapter.Type.CLDR;
  58 import static sun.util.locale.provider.LocaleProviderAdapter.Type.JRE;
  59 import sun.util.locale.provider.ResourceBundleBasedAdapter;
  60 
  61 /**
  62  * Provides information about and access to resource bundles in the
  63  * sun.text.resources and sun.util.resources packages or in their corresponding
  64  * packages for CLDR.
  65  *
  66  * @author Asmus Freytag
  67  * @author Mark Davis
  68  */
  69 
  70 public class LocaleData {
  71     private static final ResourceBundle.Control defaultControl
  72         = ResourceBundle.Control.getControl(ResourceBundle.Control.FORMAT_DEFAULT);
  73 
  74     private static final String DOTCLDR      = ".cldr";
  75 
  76     // Map of key (base name + locale) to candidates
  77     private static final Map<String, List<Locale>> CANDIDATES_MAP = new ConcurrentHashMap<>();
  78 
  79     private final LocaleProviderAdapter.Type type;
  80 
  81     public LocaleData(LocaleProviderAdapter.Type type) {
  82         this.type = type;
  83     }
  84 
  85     /**
  86      * Gets a calendar data resource bundle, using privileges
  87      * to allow accessing a sun.* package.
  88      */
  89     public ResourceBundle getCalendarData(Locale locale) {
  90         return getBundle(type.getUtilResourcesPackage() + ".CalendarData", locale);
  91     }
  92 
  93     /**
  94      * Gets a currency names resource bundle, using privileges
  95      * to allow accessing a sun.* package.
  96      */
  97     public OpenListResourceBundle getCurrencyNames(Locale locale) {
  98         return (OpenListResourceBundle) getBundle(type.getUtilResourcesPackage() + ".CurrencyNames", locale);
  99     }
 100 
 101     /**
 102      * Gets a locale names resource bundle, using privileges
 103      * to allow accessing a sun.* package.
 104      */
 105     public OpenListResourceBundle getLocaleNames(Locale locale) {
 106         return (OpenListResourceBundle) getBundle(type.getUtilResourcesPackage() + ".LocaleNames", locale);
 107     }
 108 
 109     /**
 110      * Gets a time zone names resource bundle, using privileges
 111      * to allow accessing a sun.* package.
 112      */
 113     public TimeZoneNamesBundle getTimeZoneNames(Locale locale) {
 114         return (TimeZoneNamesBundle) getBundle(type.getUtilResourcesPackage() + ".TimeZoneNames", locale);
 115     }
 116 
 117     /**
 118      * Gets a break iterator info resource bundle, using privileges
 119      * to allow accessing a sun.* package.
 120      */
 121     public ResourceBundle getBreakIteratorInfo(Locale locale) {
 122         return getBundle(type.getTextResourcesPackage() + ".BreakIteratorInfo", locale);
 123     }
 124 
 125     /**
 126      * Gets a collation data resource bundle, using privileges
 127      * to allow accessing a sun.* package.
 128      */
 129     public ResourceBundle getCollationData(Locale locale) {
 130         return getBundle(type.getTextResourcesPackage() + ".CollationData", locale);
 131     }
 132 
 133     /**
 134      * Gets a date format data resource bundle, using privileges
 135      * to allow accessing a sun.* package.
 136      */
 137     public ResourceBundle getDateFormatData(Locale locale) {
 138         return getBundle(type.getTextResourcesPackage() + ".FormatData", locale);
 139     }
 140 
 141     public void setSupplementary(ParallelListResourceBundle formatData) {
 142         if (!formatData.areParallelContentsComplete()) {
 143             String suppName = type.getTextResourcesPackage() + ".JavaTimeSupplementary";
 144             setSupplementary(suppName, formatData);
 145         }
 146     }
 147 
 148     private boolean setSupplementary(String suppName, ParallelListResourceBundle formatData) {
 149         ParallelListResourceBundle parent = (ParallelListResourceBundle) formatData.getParent();
 150         boolean resetKeySet = false;
 151         if (parent != null) {
 152             resetKeySet = setSupplementary(suppName, parent);
 153         }
 154         OpenListResourceBundle supp = getSupplementary(suppName, formatData.getLocale());
 155         formatData.setParallelContents(supp);
 156         resetKeySet |= supp != null;
 157         // If any parents or this bundle has parallel data, reset keyset to create
 158         // a new keyset with the data.
 159         if (resetKeySet) {
 160             formatData.resetKeySet();
 161         }
 162         return resetKeySet;
 163     }
 164 
 165     /**
 166      * Gets a number format data resource bundle, using privileges
 167      * to allow accessing a sun.* package.
 168      */
 169     public ResourceBundle getNumberFormatData(Locale locale) {
 170         return getBundle(type.getTextResourcesPackage() + ".FormatData", locale);
 171     }
 172 
 173     public static ResourceBundle getBundle(final String baseName, final Locale locale) {
 174         return AccessController.doPrivileged(new PrivilegedAction<>() {
 175             @Override
 176             public ResourceBundle run() {
 177                 return Bundles.of(baseName, locale, LocaleDataStrategy.INSTANCE);
 178             }
 179         });
 180     }
 181 
 182     private static OpenListResourceBundle getSupplementary(final String baseName, final Locale locale) {
 183         return AccessController.doPrivileged(new PrivilegedAction<>() {
 184            @Override
 185            public OpenListResourceBundle run() {
 186                OpenListResourceBundle rb = null;
 187                try {
 188                    rb = (OpenListResourceBundle) Bundles.of(baseName, locale,
 189                                                             SupplementaryStrategy.INSTANCE);
 190                } catch (MissingResourceException e) {
 191                    // return null if no supplementary is available
 192                }
 193                return rb;
 194            }
 195         });
 196     }
 197 
 198     private static abstract class LocaleDataResourceBundleProvider
 199                                             implements ResourceBundleProvider {
 200         /**
 201          * Changes baseName to its module dependent package name and
 202          * calls the super class implementation. For example,
 203          * if the baseName is "sun.text.resources.FormatData" and locale is ja_JP,
 204          * the baseName is changed to "sun.text.resources.ext.FormatData". If
 205          * baseName contains ".cldr", such as "sun.text.resources.cldr.FormatData",
 206          * the name is changed to "sun.text.resources.cldr.ext.FormatData".
 207          */
 208         protected String toBundleName(String baseName, Locale locale) {
 209             return LocaleDataStrategy.INSTANCE.toBundleName(baseName, locale);
 210         }
 211     }
 212 
 213     /**
 214      * A ResourceBundleProvider implementation for loading locale data
 215      * resource bundles except for the java.time supplementary data.
 216      */
 217     public static abstract class CommonResourceBundleProvider extends LocaleDataResourceBundleProvider {
 218     }
 219 
 220     /**
 221      * A ResourceBundleProvider implementation for loading supplementary
 222      * resource bundles for java.time.
 223      */
 224     public static abstract class SupplementaryResourceBundleProvider extends LocaleDataResourceBundleProvider {
 225     }
 226 
 227     // Bundles.Strategy implementations
 228 
 229     private static class LocaleDataStrategy implements Bundles.Strategy {
 230         private static final LocaleDataStrategy INSTANCE = new LocaleDataStrategy();
 231         // TODO: avoid hard-coded Locales
 232         private static Set<Locale> JAVA_BASE_LOCALES
 233             = Set.of(Locale.ROOT, Locale.ENGLISH, Locale.US, new Locale("en", "US", "POSIX"));
 234 
 235         private LocaleDataStrategy() {
 236         }
 237 
 238         /*
 239          * This method overrides the default implementation to search
 240          * from a prebaked locale string list to determin the candidate
 241          * locale list.
 242          *
 243          * @param baseName the resource bundle base name.
 244          *        locale   the requested locale for the resource bundle.
 245          * @return a list of candidate locales to search from.
 246          * @exception NullPointerException if baseName or locale is null.
 247          */
 248         @Override
 249         public List<Locale> getCandidateLocales(String baseName, Locale locale) {
 250             String key = baseName + '-' + locale.toLanguageTag();
 251             List<Locale> candidates = CANDIDATES_MAP.get(key);
 252             if (candidates == null) {
 253                 LocaleProviderAdapter.Type type = baseName.contains(DOTCLDR) ? CLDR : JRE;
 254                 LocaleProviderAdapter adapter = LocaleProviderAdapter.forType(type);
 255                 candidates = adapter instanceof ResourceBundleBasedAdapter ?
 256                     ((ResourceBundleBasedAdapter)adapter).getCandidateLocales(baseName, locale) :
 257                     defaultControl.getCandidateLocales(baseName, locale);
 258 
 259                 // Weed out Locales which are known to have no resource bundles
 260                 int lastDot = baseName.lastIndexOf('.');
 261                 String category = (lastDot >= 0) ? baseName.substring(lastDot + 1) : baseName;
 262                 Set<String> langtags = ((JRELocaleProviderAdapter)adapter).getLanguageTagSet(category);
 263                 if (!langtags.isEmpty()) {
 264                     for (Iterator<Locale> itr = candidates.iterator(); itr.hasNext();) {
 265                         if (!adapter.isSupportedProviderLocale(itr.next(), langtags)) {
 266                             itr.remove();
 267                         }
 268                     }
 269                 }
 270                 // Force fallback to Locale.ENGLISH for CLDR time zone names support
 271                 if (locale.getLanguage() != "en"
 272                         && type == CLDR && category.equals("TimeZoneNames")) {
 273                     candidates.add(candidates.size() - 1, Locale.ENGLISH);
 274                 }
 275                 CANDIDATES_MAP.putIfAbsent(key, candidates);
 276             }
 277             return candidates;
 278         }
 279 
 280         boolean inJavaBaseModule(String baseName, Locale locale) {
 281             return JAVA_BASE_LOCALES.contains(locale);
 282         }
 283 
 284         @Override
 285         public String toBundleName(String baseName, Locale locale) {
 286             String newBaseName = baseName;
 287             if (!inJavaBaseModule(baseName, locale)) {
 288                 if (baseName.startsWith(JRE.getUtilResourcesPackage())
 289                         || baseName.startsWith(JRE.getTextResourcesPackage())) {
 290                     // Assume the lengths are the same.
 291                     assert JRE.getUtilResourcesPackage().length()
 292                         == JRE.getTextResourcesPackage().length();
 293                     int index = JRE.getUtilResourcesPackage().length();
 294                     if (baseName.indexOf(DOTCLDR, index) > 0) {
 295                         index += DOTCLDR.length();
 296                     }
 297                     newBaseName = baseName.substring(0, index + 1) + "ext"
 298                                       + baseName.substring(index);
 299                 }
 300             }
 301             return defaultControl.toBundleName(newBaseName, locale);
 302         }
 303 
 304         @Override
 305         public Class<? extends ResourceBundleProvider> getResourceBundleProviderType(String baseName,
 306                                                                                      Locale locale) {
 307             return inJavaBaseModule(baseName, locale) ?
 308                         null : CommonResourceBundleProvider.class;
 309         }
 310     }
 311 
 312     private static class SupplementaryStrategy extends LocaleDataStrategy {
 313         private static final SupplementaryStrategy INSTANCE
 314                 = new SupplementaryStrategy();
 315         // TODO: avoid hard-coded Locales
 316         private static Set<Locale> JAVA_BASE_LOCALES
 317             = Set.of(Locale.ROOT, Locale.ENGLISH, Locale.US);
 318 
 319         private SupplementaryStrategy() {
 320         }
 321 
 322         @Override
 323         public List<Locale> getCandidateLocales(String baseName, Locale locale) {
 324             // Specifiy only the given locale
 325             return Arrays.asList(locale);
 326         }
 327 
 328         @Override
 329         public Class<? extends ResourceBundleProvider> getResourceBundleProviderType(String baseName,
 330                                                                                      Locale locale) {
 331             return inJavaBaseModule(baseName, locale) ?
 332                     null : SupplementaryResourceBundleProvider.class;
 333         }
 334 
 335         @Override
 336         boolean inJavaBaseModule(String baseName, Locale locale) {
 337             return JAVA_BASE_LOCALES.contains(locale);
 338         }
 339     }
 340 }