1 /* 2 * Copyright (c) 2012, 2020, 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 package sun.util.cldr; 27 28 import java.security.AccessController; 29 import java.security.AccessControlException; 30 import java.security.PrivilegedAction; 31 import java.security.PrivilegedActionException; 32 import java.security.PrivilegedExceptionAction; 33 import java.text.spi.BreakIteratorProvider; 34 import java.text.spi.CollatorProvider; 35 import java.util.Arrays; 36 import java.util.Collections; 37 import java.util.HashSet; 38 import java.util.List; 39 import java.util.Locale; 40 import java.util.Map; 41 import java.util.Optional; 42 import java.util.ServiceLoader; 43 import java.util.ServiceConfigurationError; 44 import java.util.Set; 45 import java.util.StringTokenizer; 46 import java.util.concurrent.ConcurrentHashMap; 47 import java.util.spi.CalendarDataProvider; 48 import java.util.spi.CalendarNameProvider; 49 import java.util.spi.TimeZoneNameProvider; 50 import sun.util.locale.provider.JRELocaleProviderAdapter; 51 import sun.util.locale.provider.LocaleDataMetaInfo; 52 import sun.util.locale.provider.LocaleProviderAdapter; 53 54 /** 55 * LocaleProviderAdapter implementation for the CLDR locale data. 56 * 57 * @author Masayoshi Okutsu 58 * @author Naoto Sato 59 */ 60 public class CLDRLocaleProviderAdapter extends JRELocaleProviderAdapter { 61 62 private static final CLDRBaseLocaleDataMetaInfo baseMetaInfo = new CLDRBaseLocaleDataMetaInfo(); 63 // Assumption: CLDR has only one non-Base module. 64 private final LocaleDataMetaInfo nonBaseMetaInfo; 65 66 // parent locales map 67 private static volatile Map<Locale, Locale> parentLocalesMap; 68 // language aliases map 69 private static volatile Map<String,String> langAliasesMap; 70 // cache to hold locale to locale mapping for language aliases. 71 private static final Map<Locale, Locale> langAliasesCache; 72 static { 73 parentLocalesMap = new ConcurrentHashMap<>(); 74 langAliasesMap = new ConcurrentHashMap<>(); 75 langAliasesCache = new ConcurrentHashMap<>(); 76 // Assuming these locales do NOT have irregular parent locales. parentLocalesMap.put(Locale.ROOT, Locale.ROOT)77 parentLocalesMap.put(Locale.ROOT, Locale.ROOT); parentLocalesMap.put(Locale.ENGLISH, Locale.ENGLISH)78 parentLocalesMap.put(Locale.ENGLISH, Locale.ENGLISH); parentLocalesMap.put(Locale.US, Locale.US)79 parentLocalesMap.put(Locale.US, Locale.US); 80 } 81 CLDRLocaleProviderAdapter()82 public CLDRLocaleProviderAdapter() { 83 LocaleDataMetaInfo nbmi = null; 84 85 try { 86 nbmi = AccessController.doPrivileged(new PrivilegedExceptionAction<LocaleDataMetaInfo>() { 87 @Override 88 public LocaleDataMetaInfo run() { 89 for (LocaleDataMetaInfo ldmi : ServiceLoader.loadInstalled(LocaleDataMetaInfo.class)) { 90 if (ldmi.getType() == LocaleProviderAdapter.Type.CLDR) { 91 return ldmi; 92 } 93 } 94 return null; 95 } 96 }); 97 } catch (PrivilegedActionException pae) { 98 throw new InternalError(pae.getCause()); 99 } 100 101 nonBaseMetaInfo = nbmi; 102 } 103 104 /** 105 * Returns the type of this LocaleProviderAdapter 106 * @return the type of this 107 */ 108 @Override getAdapterType()109 public LocaleProviderAdapter.Type getAdapterType() { 110 return LocaleProviderAdapter.Type.CLDR; 111 } 112 113 @Override getBreakIteratorProvider()114 public BreakIteratorProvider getBreakIteratorProvider() { 115 return null; 116 } 117 118 @Override getCalendarDataProvider()119 public CalendarDataProvider getCalendarDataProvider() { 120 if (calendarDataProvider == null) { 121 CalendarDataProvider provider = AccessController.doPrivileged( 122 (PrivilegedAction<CalendarDataProvider>) () -> 123 new CLDRCalendarDataProviderImpl( 124 getAdapterType(), 125 getLanguageTagSet("CalendarData"))); 126 127 synchronized (this) { 128 if (calendarDataProvider == null) { 129 calendarDataProvider = provider; 130 } 131 } 132 } 133 return calendarDataProvider; 134 } 135 136 @Override getCalendarNameProvider()137 public CalendarNameProvider getCalendarNameProvider() { 138 if (calendarNameProvider == null) { 139 CalendarNameProvider provider = AccessController.doPrivileged( 140 (PrivilegedAction<CalendarNameProvider>) () 141 -> new CLDRCalendarNameProviderImpl( 142 getAdapterType(), 143 getLanguageTagSet("FormatData"))); 144 145 synchronized (this) { 146 if (calendarNameProvider == null) { 147 calendarNameProvider = provider; 148 } 149 } 150 } 151 return calendarNameProvider; 152 } 153 154 @Override getCollatorProvider()155 public CollatorProvider getCollatorProvider() { 156 return null; 157 } 158 159 @Override getTimeZoneNameProvider()160 public TimeZoneNameProvider getTimeZoneNameProvider() { 161 if (timeZoneNameProvider == null) { 162 TimeZoneNameProvider provider = AccessController.doPrivileged( 163 (PrivilegedAction<TimeZoneNameProvider>) () -> 164 new CLDRTimeZoneNameProviderImpl( 165 getAdapterType(), 166 getLanguageTagSet("TimeZoneNames"))); 167 168 synchronized (this) { 169 if (timeZoneNameProvider == null) { 170 timeZoneNameProvider = provider; 171 } 172 } 173 } 174 return timeZoneNameProvider; 175 } 176 177 @Override getAvailableLocales()178 public Locale[] getAvailableLocales() { 179 Set<String> all = createLanguageTagSet("AvailableLocales"); 180 Locale[] locs = new Locale[all.size()]; 181 int index = 0; 182 for (String tag : all) { 183 locs[index++] = Locale.forLanguageTag(tag); 184 } 185 return locs; 186 } 187 applyAliases(Locale loc)188 private static Locale applyAliases(Locale loc) { 189 if (langAliasesMap.isEmpty()) { 190 langAliasesMap = baseMetaInfo.getLanguageAliasMap(); 191 } 192 Locale locale = langAliasesCache.get(loc); 193 if (locale == null) { 194 String locTag = loc.toLanguageTag(); 195 Locale aliasLocale = langAliasesMap.containsKey(locTag) 196 ? Locale.forLanguageTag(langAliasesMap.get(locTag)) : loc; 197 langAliasesCache.putIfAbsent(loc, aliasLocale); 198 return aliasLocale; 199 } else { 200 return locale; 201 } 202 } 203 204 @Override createLanguageTagSet(String category)205 protected Set<String> createLanguageTagSet(String category) { 206 // Assume all categories support the same set as AvailableLocales 207 // in CLDR adapter. 208 category = "AvailableLocales"; 209 210 // Directly call Base tags, as we know it's in the base module. 211 String supportedLocaleString = baseMetaInfo.availableLanguageTags(category); 212 String nonBaseTags = null; 213 214 if (nonBaseMetaInfo != null) { 215 nonBaseTags = nonBaseMetaInfo.availableLanguageTags(category); 216 } 217 if (nonBaseTags != null) { 218 if (supportedLocaleString != null) { 219 supportedLocaleString += " " + nonBaseTags; 220 } else { 221 supportedLocaleString = nonBaseTags; 222 } 223 } 224 if (supportedLocaleString == null) { 225 return Collections.emptySet(); 226 } 227 StringTokenizer tokens = new StringTokenizer(supportedLocaleString); 228 Set<String> tagset = new HashSet<>((tokens.countTokens() * 4 + 2) / 3); 229 while (tokens.hasMoreTokens()) { 230 tagset.add(tokens.nextToken()); 231 } 232 return tagset; 233 } 234 235 // Implementation of ResourceBundleBasedAdapter 236 @Override getCandidateLocales(String baseName, Locale locale)237 public List<Locale> getCandidateLocales(String baseName, Locale locale) { 238 List<Locale> candidates = super.getCandidateLocales(baseName, applyAliases(locale)); 239 return applyParentLocales(baseName, candidates); 240 } 241 applyParentLocales(String baseName, List<Locale> candidates)242 private List<Locale> applyParentLocales(String baseName, List<Locale> candidates) { 243 // check irregular parents 244 for (int i = 0; i < candidates.size(); i++) { 245 Locale l = candidates.get(i); 246 if (!l.equals(Locale.ROOT)) { 247 Locale p = getParentLocale(l); 248 if (p != null && 249 !candidates.get(i+1).equals(p)) { 250 List<Locale> applied = candidates.subList(0, i+1); 251 applied.addAll(applyParentLocales(baseName, super.getCandidateLocales(baseName, p))); 252 return applied; 253 } 254 } 255 } 256 257 return candidates; 258 } 259 getParentLocale(Locale locale)260 private static Locale getParentLocale(Locale locale) { 261 Locale parent = parentLocalesMap.get(locale); 262 263 if (parent == null) { 264 String tag = locale.toLanguageTag(); 265 for (Map.Entry<Locale, String[]> entry : baseMetaInfo.parentLocales().entrySet()) { 266 if (Arrays.binarySearch(entry.getValue(), tag) >= 0) { 267 parent = entry.getKey(); 268 break; 269 } 270 } 271 if (parent == null) { 272 parent = locale; // non existent marker 273 } 274 parentLocalesMap.putIfAbsent(locale, parent); 275 } 276 277 if (locale.equals(parent)) { 278 // means no irregular parent. 279 parent = null; 280 } 281 282 return parent; 283 } 284 285 /** 286 * This method returns equivalent CLDR supported locale 287 * for no, no-NO locales so that COMPAT locales do not precede 288 * those locales during ResourceBundle search path, also if an alias exists for a locale, 289 * it returns equivalent locale, e.g for zh_HK it returns zh_Hant-HK. 290 */ getEquivalentLoc(Locale locale)291 private static Locale getEquivalentLoc(Locale locale) { 292 switch (locale.toString()) { 293 case "no": 294 case "no_NO": 295 return Locale.forLanguageTag("nb"); 296 } 297 return applyAliases(locale); 298 } 299 300 @Override isSupportedProviderLocale(Locale locale, Set<String> langtags)301 public boolean isSupportedProviderLocale(Locale locale, Set<String> langtags) { 302 return Locale.ROOT.equals(locale) 303 || langtags.contains(locale.stripExtensions().toLanguageTag()) 304 || langtags.contains(getEquivalentLoc(locale).toLanguageTag()); 305 } 306 307 /** 308 * Returns the canonical ID for the given ID 309 */ canonicalTZID(String id)310 public Optional<String> canonicalTZID(String id) { 311 return Optional.ofNullable(baseMetaInfo.tzCanonicalIDs().get(id)); 312 } 313 } 314