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 }