1 /*
   2  * Copyright (c) 1996, 2013, 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.MissingResourceException;
  50 import java.util.ResourceBundle;
  51 import sun.util.locale.provider.LocaleDataMetaInfo;
  52 import sun.util.locale.provider.LocaleProviderAdapter;
  53 import static sun.util.locale.provider.LocaleProviderAdapter.Type.JRE;
  54 
  55 /**
  56  * Provides information about and access to resource bundles in the
  57  * sun.text.resources and sun.util.resources packages or in their corresponding
  58  * packages for CLDR.
  59  *
  60  * @author Asmus Freytag
  61  * @author Mark Davis
  62  */
  63 
  64 public class LocaleData {
  65     private final LocaleProviderAdapter.Type type;
  66 
  67     public LocaleData(LocaleProviderAdapter.Type type) {
  68         this.type = type;
  69     }
  70 
  71     /**
  72      * Gets a calendar data resource bundle, using privileges
  73      * to allow accessing a sun.* package.
  74      */
  75     public ResourceBundle getCalendarData(Locale locale) {
  76         return getBundle(type.getUtilResourcesPackage() + ".CalendarData", locale);
  77     }
  78 
  79     /**
  80      * Gets a currency names resource bundle, using privileges
  81      * to allow accessing a sun.* package.
  82      */
  83     public OpenListResourceBundle getCurrencyNames(Locale locale) {
  84         return (OpenListResourceBundle) getBundle(type.getUtilResourcesPackage() + ".CurrencyNames", locale);
  85     }
  86 
  87     /**
  88      * Gets a locale names resource bundle, using privileges
  89      * to allow accessing a sun.* package.
  90      */
  91     public OpenListResourceBundle getLocaleNames(Locale locale) {
  92         return (OpenListResourceBundle) getBundle(type.getUtilResourcesPackage() + ".LocaleNames", locale);
  93     }
  94 
  95     /**
  96      * Gets a time zone names resource bundle, using privileges
  97      * to allow accessing a sun.* package.
  98      */
  99     public TimeZoneNamesBundle getTimeZoneNames(Locale locale) {
 100         return (TimeZoneNamesBundle) getBundle(type.getUtilResourcesPackage() + ".TimeZoneNames", locale);
 101     }
 102 
 103     /**
 104      * Gets a break iterator info resource bundle, using privileges
 105      * to allow accessing a sun.* package.
 106      */
 107     public ResourceBundle getBreakIteratorInfo(Locale locale) {
 108         return getBundle(type.getTextResourcesPackage() + ".BreakIteratorInfo", locale);
 109     }
 110 
 111     /**
 112      * Gets a collation data resource bundle, using privileges
 113      * to allow accessing a sun.* package.
 114      */
 115     public ResourceBundle getCollationData(Locale locale) {
 116         return getBundle(type.getTextResourcesPackage() + ".CollationData", locale);
 117     }
 118 
 119     /**
 120      * Gets a date format data resource bundle, using privileges
 121      * to allow accessing a sun.* package.
 122      */
 123     public ResourceBundle getDateFormatData(Locale locale) {
 124         return getBundle(type.getTextResourcesPackage() + ".FormatData", locale);
 125     }
 126 
 127     public void setSupplementary(ParallelListResourceBundle formatData) {
 128         assert type == LocaleProviderAdapter.Type.JRE
 129                 && formatData.getClass().getSimpleName().contains("FormatData");
 130         if (!formatData.areParallelContentsComplete()) {
 131             String suppName = type.getTextResourcesPackage() + ".JavaTimeSupplementary";
 132             setSupplementary(suppName, formatData);
 133         }
 134     }
 135 
 136     private boolean setSupplementary(String suppName, ParallelListResourceBundle formatData) {
 137         ParallelListResourceBundle parent = (ParallelListResourceBundle) formatData.getParent();
 138         boolean resetKeySet = false;
 139         if (parent != null) {
 140             resetKeySet = setSupplementary(suppName, parent);
 141         }
 142         OpenListResourceBundle supp = getSupplementary(suppName, formatData.getLocale());
 143         formatData.setParallelContents(supp);
 144         resetKeySet |= supp != null;
 145         // If any parents or this bundle has parallel data, reset keyset to creat
 146         // a new keyset with the data.
 147         if (resetKeySet) {
 148             formatData.resetKeySet();
 149         }
 150         return resetKeySet;
 151     }
 152 
 153     /**
 154      * Gets a number format data resource bundle, using privileges
 155      * to allow accessing a sun.* package.
 156      */
 157     public ResourceBundle getNumberFormatData(Locale locale) {
 158         return getBundle(type.getTextResourcesPackage() + ".FormatData", locale);
 159     }
 160 
 161     public static ResourceBundle getBundle(final String baseName, final Locale locale) {
 162         return AccessController.doPrivileged(new PrivilegedAction<ResourceBundle>() {
 163             @Override
 164             public ResourceBundle run() {
 165                 return ResourceBundle
 166                         .getBundle(baseName, locale, LocaleDataResourceBundleControl.INSTANCE);
 167             }
 168         });
 169     }
 170 
 171     private static OpenListResourceBundle getSupplementary(final String baseName, final Locale locale) {
 172         return AccessController.doPrivileged(new PrivilegedAction<OpenListResourceBundle>() {
 173            @Override
 174            public OpenListResourceBundle run() {
 175                OpenListResourceBundle rb = null;
 176                try {
 177                    rb = (OpenListResourceBundle) ResourceBundle.getBundle(baseName,
 178                            locale, SupplementaryResourceBundleControl.INSTANCE);
 179 
 180                } catch (MissingResourceException e) {
 181                    // return null if no supplementary is available
 182                }
 183                return rb;
 184            }
 185         });
 186     }
 187 
 188     private static class LocaleDataResourceBundleControl extends ResourceBundle.Control {
 189         /* Singlton instance of ResourceBundle.Control. */
 190         private static final LocaleDataResourceBundleControl INSTANCE =
 191             new LocaleDataResourceBundleControl();
 192 
 193         private LocaleDataResourceBundleControl() {
 194         }
 195 
 196         /*
 197          * This method overrides the default implementation to search
 198          * from a prebaked locale string list to determin the candidate
 199          * locale list.
 200          *
 201          * @param baseName the resource bundle base name.
 202          *        locale   the requested locale for the resource bundle.
 203          * @returns a list of candidate locales to search from.
 204          * @exception NullPointerException if baseName or locale is null.
 205          */
 206         @Override
 207          public List<Locale> getCandidateLocales(String baseName, Locale locale) {
 208             List<Locale> candidates = super.getCandidateLocales(baseName, locale);
 209             /* Get the locale string list from LocaleDataMetaInfo class. */
 210             String localeString = LocaleDataMetaInfo.getSupportedLocaleString(baseName);
 211 
 212             if (localeString != null && localeString.length() != 0) {
 213                 for (Iterator<Locale> l = candidates.iterator(); l.hasNext();) {
 214                     Locale loc = l.next();
 215                     String lstr;
 216                     if (loc.getScript().length() > 0) {
 217                         lstr = loc.toLanguageTag().replace('-', '_');
 218                     } else {
 219                         lstr = loc.toString();
 220                         int idx = lstr.indexOf("_#");
 221                         if (idx >= 0) {
 222                             lstr = lstr.substring(0, idx);
 223                         }
 224                     }
 225                     /* Every locale string in the locale string list returned from
 226                      the above getSupportedLocaleString is enclosed
 227                      within two white spaces so that we could check some locale
 228                      such as "en".
 229                      */
 230                     if (lstr.length() != 0 && localeString.indexOf(" " + lstr + " ") == -1) {
 231                         l.remove();
 232                     }
 233                 }
 234             }
 235             // Force fallback to Locale.ENGLISH for CLDR time zone names support
 236             if (locale.getLanguage() != "en"
 237                     && baseName.contains(CLDR) && baseName.endsWith("TimeZoneNames")) {
 238                 candidates.add(candidates.size() - 1, Locale.ENGLISH);
 239             }
 240             return candidates;
 241         }
 242 
 243         /*
 244          * Overrides "getFallbackLocale" to return null so
 245          * that the fallback locale will be null.
 246          * @param baseName the resource bundle base name.
 247          *        locale   the requested locale for the resource bundle.
 248          * @return null for the fallback locale.
 249          * @exception NullPointerException if baseName or locale is null.
 250          */
 251         @Override
 252         public Locale getFallbackLocale(String baseName, Locale locale) {
 253             if (baseName == null || locale == null) {
 254                 throw new NullPointerException();
 255             }
 256             return null;
 257         }
 258 
 259         private static final String CLDR      = ".cldr";
 260 
 261         /**
 262          * Changes baseName to its per-language package name and
 263          * calls the super class implementation. For example,
 264          * if the baseName is "sun.text.resources.FormatData" and locale is ja_JP,
 265          * the baseName is changed to "sun.text.resources.ja.FormatData". If
 266          * baseName contains "cldr", such as "sun.text.resources.cldr.FormatData",
 267          * the name is changed to "sun.text.resources.cldr.jp.FormatData".
 268          */
 269         @Override
 270         public String toBundleName(String baseName, Locale locale) {
 271             String newBaseName = baseName;
 272             String lang = locale.getLanguage();
 273             if (lang.length() > 0) {
 274                 if (baseName.startsWith(JRE.getUtilResourcesPackage())
 275                         || baseName.startsWith(JRE.getTextResourcesPackage())) {
 276                     // Assume the lengths are the same.
 277                     assert JRE.getUtilResourcesPackage().length()
 278                         == JRE.getTextResourcesPackage().length();
 279                     int index = JRE.getUtilResourcesPackage().length();
 280                     if (baseName.indexOf(CLDR, index) > 0) {
 281                         index += CLDR.length();
 282                     }
 283                     newBaseName = baseName.substring(0, index + 1) + lang
 284                                       + baseName.substring(index);
 285                 }
 286             }
 287             return super.toBundleName(newBaseName, locale);
 288         }
 289     }
 290 
 291     private static class SupplementaryResourceBundleControl extends LocaleDataResourceBundleControl {
 292         private static final SupplementaryResourceBundleControl INSTANCE =
 293                 new SupplementaryResourceBundleControl();
 294 
 295         private SupplementaryResourceBundleControl() {
 296         }
 297 
 298         @Override
 299         public List<Locale> getCandidateLocales(String baseName, Locale locale) {
 300             // Specifiy only the given locale
 301             return Arrays.asList(locale);
 302         }
 303 
 304         @Override
 305         public long getTimeToLive(String baseName, Locale locale) {
 306             assert baseName.contains("JavaTimeSupplementary");
 307             return TTL_DONT_CACHE;
 308         }
 309     }
 310 }