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 break iterator resources resource bundle, using
 127      * privileges to allow accessing a sun.* package.
 128      */
 129     public ResourceBundle getBreakIteratorResources(Locale locale) {
 130         return getBundle(type.getTextResourcesPackage() + ".BreakIteratorResources", locale);
 131     }
 132 
 133     /**
 134      * Gets a collation data resource bundle, using privileges
 135      * to allow accessing a sun.* package.
 136      */
 137     public ResourceBundle getCollationData(Locale locale) {
 138         return getBundle(type.getTextResourcesPackage() + ".CollationData", locale);
 139     }
 140 
 141     /**
 142      * Gets a date format data resource bundle, using privileges
 143      * to allow accessing a sun.* package.
 144      */
 145     public ResourceBundle getDateFormatData(Locale locale) {
 146         return getBundle(type.getTextResourcesPackage() + ".FormatData", locale);
 147     }
 148 
 149     public void setSupplementary(ParallelListResourceBundle formatData) {
 150         if (!formatData.areParallelContentsComplete()) {
 151             String suppName = type.getTextResourcesPackage() + ".JavaTimeSupplementary";
 152             setSupplementary(suppName, formatData);
 153         }
 154     }
 155 
 156     private boolean setSupplementary(String suppName, ParallelListResourceBundle formatData) {
 157         ParallelListResourceBundle parent = (ParallelListResourceBundle) formatData.getParent();
 158         boolean resetKeySet = false;
 159         if (parent != null) {
 160             resetKeySet = setSupplementary(suppName, parent);
 161         }
 162         OpenListResourceBundle supp = getSupplementary(suppName, formatData.getLocale());
 163         formatData.setParallelContents(supp);
 164         resetKeySet |= supp != null;
 165         // If any parents or this bundle has parallel data, reset keyset to create
 166         // a new keyset with the data.
 167         if (resetKeySet) {
 168             formatData.resetKeySet();
 169         }
 170         return resetKeySet;
 171     }
 172 
 173     /**
 174      * Gets a number format data resource bundle, using privileges
 175      * to allow accessing a sun.* package.
 176      */
 177     public ResourceBundle getNumberFormatData(Locale locale) {
 178         return getBundle(type.getTextResourcesPackage() + ".FormatData", locale);
 179     }
 180 
 181     public static ResourceBundle getBundle(final String baseName, final Locale locale) {
 182         return AccessController.doPrivileged(new PrivilegedAction<>() {
 183             @Override
 184             public ResourceBundle run() {
 185                 return Bundles.of(baseName, locale, LocaleDataStrategy.INSTANCE);
 186             }
 187         });
 188     }
 189 
 190     private static OpenListResourceBundle getSupplementary(final String baseName, final Locale locale) {
 191         return AccessController.doPrivileged(new PrivilegedAction<>() {
 192            @Override
 193            public OpenListResourceBundle run() {
 194                OpenListResourceBundle rb = null;
 195                try {
 196                    rb = (OpenListResourceBundle) Bundles.of(baseName, locale,
 197                                                             SupplementaryStrategy.INSTANCE);
 198                } catch (MissingResourceException e) {
 199                    // return null if no supplementary is available
 200                }
 201                return rb;
 202            }
 203         });
 204     }
 205 
 206     private static abstract class LocaleDataResourceBundleProvider
 207                                             implements ResourceBundleProvider {
 208         /**
 209          * Changes baseName to its module dependent package name and
 210          * calls the super class implementation. For example,
 211          * if the baseName is "sun.text.resources.FormatData" and locale is ja_JP,
 212          * the baseName is changed to "sun.text.resources.ext.FormatData". If
 213          * baseName contains ".cldr", such as "sun.text.resources.cldr.FormatData",
 214          * the name is changed to "sun.text.resources.cldr.ext.FormatData".
 215          */
 216         protected String toBundleName(String baseName, Locale locale) {
 217             return LocaleDataStrategy.INSTANCE.toBundleName(baseName, locale);
 218         }
 219     }
 220 
 221     /**
 222      * A ResourceBundleProvider implementation for loading locale data
 223      * resource bundles except for the java.time supplementary data.
 224      */
 225     public static abstract class CommonResourceBundleProvider extends LocaleDataResourceBundleProvider {
 226     }
 227 
 228     /**
 229      * A ResourceBundleProvider implementation for loading supplementary
 230      * resource bundles for java.time.
 231      */
 232     public static abstract class SupplementaryResourceBundleProvider extends LocaleDataResourceBundleProvider {
 233     }
 234 
 235     // Bundles.Strategy implementations
 236 
 237     private static class LocaleDataStrategy implements Bundles.Strategy {
 238         private static final LocaleDataStrategy INSTANCE = new LocaleDataStrategy();
 239         // TODO: avoid hard-coded Locales
 240         private static Set<Locale> JAVA_BASE_LOCALES
 241             = Set.of(Locale.ROOT, Locale.ENGLISH, Locale.US, new Locale("en", "US", "POSIX"));
 242 
 243         private LocaleDataStrategy() {
 244         }
 245 
 246         /*
 247          * This method overrides the default implementation to search
 248          * from a prebaked locale string list to determin the candidate
 249          * locale list.
 250          *
 251          * @param baseName the resource bundle base name.
 252          *        locale   the requested locale for the resource bundle.
 253          * @return a list of candidate locales to search from.
 254          * @exception NullPointerException if baseName or locale is null.
 255          */
 256         @Override
 257         public List<Locale> getCandidateLocales(String baseName, Locale locale) {
 258             String key = baseName + '-' + locale.toLanguageTag();
 259             List<Locale> candidates = CANDIDATES_MAP.get(key);
 260             if (candidates == null) {
 261                 LocaleProviderAdapter.Type type = baseName.contains(DOTCLDR) ? CLDR : JRE;
 262                 LocaleProviderAdapter adapter = LocaleProviderAdapter.forType(type);
 263                 candidates = adapter instanceof ResourceBundleBasedAdapter ?
 264                     ((ResourceBundleBasedAdapter)adapter).getCandidateLocales(baseName, locale) :
 265                     defaultControl.getCandidateLocales(baseName, locale);
 266 
 267                 // Weed out Locales which are known to have no resource bundles
 268                 int lastDot = baseName.lastIndexOf('.');
 269                 String category = (lastDot >= 0) ? baseName.substring(lastDot + 1) : baseName;
 270                 Set<String> langtags = ((JRELocaleProviderAdapter)adapter).getLanguageTagSet(category);
 271                 if (!langtags.isEmpty()) {
 272                     for (Iterator<Locale> itr = candidates.iterator(); itr.hasNext();) {
 273                         if (!adapter.isSupportedProviderLocale(itr.next(), langtags)) {
 274                             itr.remove();
 275                         }
 276                     }
 277                 }
 278                 // Force fallback to Locale.ENGLISH for CLDR time zone names support
 279                 if (locale.getLanguage() != "en"
 280                         && type == CLDR && category.equals("TimeZoneNames")) {
 281                     candidates.add(candidates.size() - 1, Locale.ENGLISH);
 282                 }
 283                 CANDIDATES_MAP.putIfAbsent(key, candidates);
 284             }
 285             return candidates;
 286         }
 287 
 288         boolean inJavaBaseModule(String baseName, Locale locale) {
 289             return JAVA_BASE_LOCALES.contains(locale);
 290         }
 291 
 292         @Override
 293         public String toBundleName(String baseName, Locale locale) {
 294             String newBaseName = baseName;
 295             if (!inJavaBaseModule(baseName, locale)) {
 296                 if (baseName.startsWith(JRE.getUtilResourcesPackage())
 297                         || baseName.startsWith(JRE.getTextResourcesPackage())) {
 298                     // Assume the lengths are the same.
 299                     assert JRE.getUtilResourcesPackage().length()
 300                         == JRE.getTextResourcesPackage().length();
 301                     int index = JRE.getUtilResourcesPackage().length();
 302                     if (baseName.indexOf(DOTCLDR, index) > 0) {
 303                         index += DOTCLDR.length();
 304                     }
 305                     newBaseName = baseName.substring(0, index + 1) + "ext"
 306                                       + baseName.substring(index);
 307                 }
 308             }
 309             return defaultControl.toBundleName(newBaseName, locale);
 310         }
 311 
 312         @Override
 313         public Class<? extends ResourceBundleProvider> getResourceBundleProviderType(String baseName,
 314                                                                                      Locale locale) {
 315             return inJavaBaseModule(baseName, locale) ?
 316                         null : CommonResourceBundleProvider.class;
 317         }
 318     }
 319 
 320     private static class SupplementaryStrategy extends LocaleDataStrategy {
 321         private static final SupplementaryStrategy INSTANCE
 322                 = new SupplementaryStrategy();
 323         // TODO: avoid hard-coded Locales
 324         private static Set<Locale> JAVA_BASE_LOCALES
 325             = Set.of(Locale.ROOT, Locale.ENGLISH, Locale.US);
 326 
 327         private SupplementaryStrategy() {
 328         }
 329 
 330         @Override
 331         public List<Locale> getCandidateLocales(String baseName, Locale locale) {
 332             // Specifiy only the given locale
 333             return Arrays.asList(locale);
 334         }
 335 
 336         @Override
 337         public Class<? extends ResourceBundleProvider> getResourceBundleProviderType(String baseName,
 338                                                                                      Locale locale) {
 339             return inJavaBaseModule(baseName, locale) ?
 340                     null : SupplementaryResourceBundleProvider.class;
 341         }
 342 
 343         @Override
 344         boolean inJavaBaseModule(String baseName, Locale locale) {
 345             return JAVA_BASE_LOCALES.contains(locale);
 346         }
 347     }
 348 }