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         if (!formatData.areParallelContentsComplete()) {
 129             String suppName = type.getTextResourcesPackage() + ".JavaTimeSupplementary";
 130             setSupplementary(suppName, formatData);
 131         }
 132     }
 133 
 134     private boolean setSupplementary(String suppName, ParallelListResourceBundle formatData) {
 135         ParallelListResourceBundle parent = (ParallelListResourceBundle) formatData.getParent();
 136         boolean resetKeySet = false;
 137         if (parent != null) {
 138             resetKeySet = setSupplementary(suppName, parent);
 139         }
 140         OpenListResourceBundle supp = getSupplementary(suppName, formatData.getLocale());
 141         formatData.setParallelContents(supp);
 142         resetKeySet |= supp != null;
 143         // If any parents or this bundle has parallel data, reset keyset to create
 144         // a new keyset with the data.
 145         if (resetKeySet) {
 146             formatData.resetKeySet();
 147         }
 148         return resetKeySet;
 149     }
 150 
 151     /**
 152      * Gets a number format data resource bundle, using privileges
 153      * to allow accessing a sun.* package.
 154      */
 155     public ResourceBundle getNumberFormatData(Locale locale) {
 156         return getBundle(type.getTextResourcesPackage() + ".FormatData", locale);
 157     }
 158 
 159     public static ResourceBundle getBundle(final String baseName, final Locale locale) {
 160         return AccessController.doPrivileged(new PrivilegedAction<ResourceBundle>() {
 161             @Override
 162             public ResourceBundle run() {
 163                 return ResourceBundle
 164                         .getBundle(baseName, locale, LocaleDataResourceBundleControl.INSTANCE);
 165             }
 166         });
 167     }
 168 
 169     private static OpenListResourceBundle getSupplementary(final String baseName, final Locale locale) {
 170         return AccessController.doPrivileged(new PrivilegedAction<OpenListResourceBundle>() {
 171            @Override
 172            public OpenListResourceBundle run() {
 173                OpenListResourceBundle rb = null;
 174                try {
 175                    rb = (OpenListResourceBundle) ResourceBundle.getBundle(baseName,
 176                            locale, SupplementaryResourceBundleControl.INSTANCE);
 177 
 178                } catch (MissingResourceException e) {
 179                    // return null if no supplementary is available
 180                }
 181                return rb;
 182            }
 183         });
 184     }
 185 
 186     private static class LocaleDataResourceBundleControl extends ResourceBundle.Control {
 187         /* Singlton instance of ResourceBundle.Control. */
 188         private static final LocaleDataResourceBundleControl INSTANCE =
 189             new LocaleDataResourceBundleControl();
 190 
 191         private LocaleDataResourceBundleControl() {
 192         }
 193 
 194         /*
 195          * This method overrides the default implementation to search
 196          * from a prebaked locale string list to determin the candidate
 197          * locale list.
 198          *
 199          * @param baseName the resource bundle base name.
 200          *        locale   the requested locale for the resource bundle.
 201          * @returns a list of candidate locales to search from.
 202          * @exception NullPointerException if baseName or locale is null.
 203          */
 204         @Override
 205          public List<Locale> getCandidateLocales(String baseName, Locale locale) {
 206             List<Locale> candidates = super.getCandidateLocales(baseName, locale);
 207             /* Get the locale string list from LocaleDataMetaInfo class. */
 208             String localeString = LocaleDataMetaInfo.getSupportedLocaleString(baseName);
 209 
 210             if (localeString != null && localeString.length() != 0) {
 211                 for (Iterator<Locale> l = candidates.iterator(); l.hasNext();) {
 212                     Locale loc = l.next();
 213                     String lstr;
 214                     if (loc.getScript().length() > 0) {
 215                         lstr = loc.toLanguageTag().replace('-', '_');
 216                     } else {
 217                         lstr = loc.toString();
 218                         int idx = lstr.indexOf("_#");
 219                         if (idx >= 0) {
 220                             lstr = lstr.substring(0, idx);
 221                         }
 222                     }
 223                     /* Every locale string in the locale string list returned from
 224                      the above getSupportedLocaleString is enclosed
 225                      within two white spaces so that we could check some locale
 226                      such as "en".
 227                      */
 228                     if (lstr.length() != 0 && localeString.indexOf(" " + lstr + " ") == -1) {
 229                         l.remove();
 230                     }
 231                 }
 232             }
 233             // Force fallback to Locale.ENGLISH for CLDR time zone names support
 234             if (locale.getLanguage() != "en"
 235                     && baseName.contains(CLDR) && baseName.endsWith("TimeZoneNames")) {
 236                 candidates.add(candidates.size() - 1, Locale.ENGLISH);
 237             }
 238             return candidates;
 239         }
 240 
 241         /*
 242          * Overrides "getFallbackLocale" to return null so
 243          * that the fallback locale will be null.
 244          * @param baseName the resource bundle base name.
 245          *        locale   the requested locale for the resource bundle.
 246          * @return null for the fallback locale.
 247          * @exception NullPointerException if baseName or locale is null.
 248          */
 249         @Override
 250         public Locale getFallbackLocale(String baseName, Locale locale) {
 251             if (baseName == null || locale == null) {
 252                 throw new NullPointerException();
 253             }
 254             return null;
 255         }
 256 
 257         private static final String CLDR      = ".cldr";
 258 
 259         /**
 260          * Changes baseName to its per-language package name and
 261          * calls the super class implementation. For example,
 262          * if the baseName is "sun.text.resources.FormatData" and locale is ja_JP,
 263          * the baseName is changed to "sun.text.resources.ja.FormatData". If
 264          * baseName contains "cldr", such as "sun.text.resources.cldr.FormatData",
 265          * the name is changed to "sun.text.resources.cldr.jp.FormatData".
 266          */
 267         @Override
 268         public String toBundleName(String baseName, Locale locale) {
 269             String newBaseName = baseName;
 270             String lang = locale.getLanguage();
 271             if (lang.length() > 0) {
 272                 if (baseName.startsWith(JRE.getUtilResourcesPackage())
 273                         || baseName.startsWith(JRE.getTextResourcesPackage())) {
 274                     // Assume the lengths are the same.
 275                     assert JRE.getUtilResourcesPackage().length()
 276                         == JRE.getTextResourcesPackage().length();
 277                     int index = JRE.getUtilResourcesPackage().length();
 278                     if (baseName.indexOf(CLDR, index) > 0) {
 279                         index += CLDR.length();
 280                     }
 281                     newBaseName = baseName.substring(0, index + 1) + lang
 282                                       + baseName.substring(index);
 283                 }
 284             }
 285             return super.toBundleName(newBaseName, locale);
 286         }
 287     }
 288 
 289     private static class SupplementaryResourceBundleControl extends LocaleDataResourceBundleControl {
 290         private static final SupplementaryResourceBundleControl INSTANCE =
 291                 new SupplementaryResourceBundleControl();
 292 
 293         private SupplementaryResourceBundleControl() {
 294         }
 295 
 296         @Override
 297         public List<Locale> getCandidateLocales(String baseName, Locale locale) {
 298             // Specifiy only the given locale
 299             return Arrays.asList(locale);
 300         }
 301 
 302         @Override
 303         public long getTimeToLive(String baseName, Locale locale) {
 304             assert baseName.contains("JavaTimeSupplementary");
 305             return TTL_DONT_CACHE;
 306         }
 307     }
 308 }