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