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