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