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 }