1 // © 2016 and later: Unicode, Inc. and others. 2 // License & terms of use: http://www.unicode.org/copyright.html#License 3 /* 4 ******************************************************************************* 5 * Copyright (C) 2008-2015, International Business Machines Corporation and 6 * others. All Rights Reserved. 7 ******************************************************************************* 8 */ 9 package com.ibm.icu.impl.javaspi; 10 11 import java.io.IOException; 12 import java.io.InputStream; 13 import java.util.Arrays; 14 import java.util.Collections; 15 import java.util.HashMap; 16 import java.util.HashSet; 17 import java.util.Locale; 18 import java.util.Map; 19 import java.util.Properties; 20 import java.util.Set; 21 22 import com.ibm.icu.impl.ICUResourceBundle; 23 import com.ibm.icu.util.ULocale; 24 import com.ibm.icu.util.ULocale.Builder; 25 26 public class ICULocaleServiceProvider { 27 private static final String SPI_PROP_FILE = "com/ibm/icu/impl/javaspi/ICULocaleServiceProviderConfig.properties"; 28 29 private static final String SUFFIX_KEY = "com.ibm.icu.impl.javaspi.ICULocaleServiceProvider.icuVariantSuffix"; 30 private static final String ENABLE_VARIANTS_KEY = "com.ibm.icu.impl.javaspi.ICULocaleServiceProvider.enableIcuVariants"; 31 private static final String ENABLE_ISO3_LANG_KEY = "com.ibm.icu.impl.javaspi.ICULocaleServiceProvider.enableIso3Languages"; 32 private static final String USE_DECIMALFORMAT_KEY = "com.ibm.icu.impl.javaspi.ICULocaleServiceProvider.useDecimalFormat"; 33 34 private static boolean configLoaded = false; 35 36 private static String suffix = "ICU4J"; 37 private static boolean enableVariants = true; 38 private static boolean enableIso3Lang = true; 39 private static boolean useDecimalFormat = false; 40 41 private static final Locale[] SPECIAL_LOCALES = { 42 new Locale("ja", "JP", "JP"), 43 new Locale("no"), 44 new Locale("no", "NO"), 45 new Locale("no", "NO", "NY"), 46 new Locale("sr", "CS"), 47 new Locale("th", "TH", "TH"), 48 }; 49 50 private static Map<Locale, Locale> SPECIAL_LOCALES_MAP = null; 51 52 private static Locale[] LOCALES = null; 53 getAvailableLocales()54 public static Locale[] getAvailableLocales() { 55 Locale[] all = getLocales(); 56 return Arrays.copyOf(all, all.length); 57 } 58 toULocaleNoSpecialVariant(Locale locale)59 public static ULocale toULocaleNoSpecialVariant(Locale locale) { 60 // If the given Locale has legacy ill-formed variant 61 // reserved by JDK, use the map to resolve the locale. 62 Locale spLoc = getSpecialLocalesMap().get(locale); 63 if (spLoc != null) { 64 return ULocale.forLocale(spLoc); 65 } 66 67 // The locale may have script field on Java 7+. 68 // So we once convert it to ULocale, then strip the ICU suffix off 69 // if necessary. 70 ULocale result = ULocale.forLocale(locale); 71 String variant = result.getVariant(); 72 String suffix = getIcuSuffix(); 73 String variantNoSuffix = null; 74 if (variant.equals(suffix)) { 75 variantNoSuffix = ""; 76 } else if (variant.endsWith(suffix) && variant.charAt(variant.length() - suffix.length() - 1) == '_') { 77 variantNoSuffix = variant.substring(0, variant.length() - suffix.length() - 1); 78 } 79 if (variantNoSuffix == null) { 80 return result; 81 } 82 83 // Strip off ICU's special suffix - cannot use Builder because 84 // original locale may have ill-formed variant 85 StringBuilder id = new StringBuilder(result.getLanguage()); 86 String script = result.getScript(); 87 String country = result.getCountry(); 88 if (script.length() > 0) { 89 id.append('_'); 90 id.append(script); 91 } 92 if (country.length() > 0 || variantNoSuffix.length() > 0) { 93 id.append('_'); 94 id.append(country); 95 } 96 if (variantNoSuffix.length() > 0) { 97 id.append('_'); 98 id.append(variantNoSuffix); 99 } 100 String orgID = result.getName(); 101 int kwdIdx = orgID.indexOf('@'); 102 if (kwdIdx >= 0) { 103 id.append(orgID.substring(kwdIdx)); 104 } 105 return new ULocale(id.toString()); 106 } 107 useDecimalFormat()108 public static boolean useDecimalFormat() { 109 loadConfiguration(); 110 return useDecimalFormat; 111 } 112 getSpecialLocalesMap()113 private static synchronized Map<Locale, Locale> getSpecialLocalesMap() { 114 if (SPECIAL_LOCALES_MAP != null) { 115 return SPECIAL_LOCALES_MAP; 116 } 117 118 Map<Locale, Locale> splocs = new HashMap<Locale, Locale>(); 119 for (Locale spLoc : SPECIAL_LOCALES) { 120 String var = spLoc.getVariant(); 121 if (var.length() > 0) { 122 splocs.put(new Locale(spLoc.getLanguage(), spLoc.getCountry(), var + "_" + getIcuSuffix()), spLoc); 123 } 124 } 125 SPECIAL_LOCALES_MAP = Collections.unmodifiableMap(splocs); 126 return SPECIAL_LOCALES_MAP; 127 } 128 getLocales()129 private static synchronized Locale[] getLocales() { 130 if (LOCALES != null) { 131 return LOCALES; 132 } 133 134 Set<Locale> localeSet = new HashSet<Locale>(); 135 ULocale[] icuLocales = ICUResourceBundle.getAvailableULocales(); 136 137 for (ULocale uloc : icuLocales) { 138 String language = uloc.getLanguage(); 139 if (language.length() >= 3 && !enableIso3Languages()) { 140 continue; 141 } 142 addULocale(uloc, localeSet); 143 144 if (uloc.getScript().length() > 0 && uloc.getCountry().length() > 0) { 145 // ICU's available locales do not contain language+country 146 // locales if script is available. Need to add them too. 147 Builder locBld = new Builder(); 148 try { 149 locBld.setLocale(uloc); 150 locBld.setScript(null); 151 ULocale ulocWithoutScript = locBld.build(); 152 addULocale(ulocWithoutScript, localeSet); 153 } catch (Exception e) { 154 // ignore 155 } 156 } 157 } 158 159 for (Locale l : SPECIAL_LOCALES) { 160 addLocale(l, localeSet); 161 } 162 163 LOCALES = localeSet.toArray(new Locale[0]); 164 return LOCALES; 165 } 166 addLocale(Locale loc, Set<Locale> locales)167 private static void addLocale(Locale loc, Set<Locale> locales) { 168 locales.add(loc); 169 170 if (enableIcuVariants()) { 171 // Add ICU variant 172 String language = loc.getLanguage(); 173 String country = loc.getCountry(); 174 String variant = loc.getVariant(); 175 176 StringBuilder var = new StringBuilder(variant); 177 if (var.length() != 0) { 178 var.append("_"); 179 } 180 var.append(getIcuSuffix()); 181 locales.add(new Locale(language, country, var.toString())); 182 } 183 } 184 addULocale(ULocale uloc, Set<Locale> locales)185 private static void addULocale(ULocale uloc, Set<Locale> locales) { 186 // special case - nn 187 // ULocale#toLocale on Java 6 maps "nn" to "no_NO_NY" 188 if (uloc.getLanguage().equals("nn") && uloc.getScript().length() == 0) { 189 Locale locNN = new Locale(uloc.getLanguage(), uloc.getCountry(), uloc.getVariant()); 190 addLocale(locNN, locales); 191 return; 192 } 193 194 locales.add(uloc.toLocale()); 195 196 if (enableIcuVariants()) { 197 // Add ICU variant 198 StringBuilder var = new StringBuilder(uloc.getVariant()); 199 if (var.length() != 0) { 200 var.append("_"); 201 } 202 var.append(getIcuSuffix()); 203 204 Builder locBld = new Builder(); 205 try { 206 locBld.setLocale(uloc); 207 locBld.setVariant(var.toString()); 208 ULocale ulocWithVar = locBld.build(); 209 locales.add(ulocWithVar.toLocale()); 210 } catch (Exception ignored) { 211 // ignore 212 } 213 } 214 } 215 enableIso3Languages()216 private static boolean enableIso3Languages() { 217 return enableIso3Lang; 218 } 219 enableIcuVariants()220 private static boolean enableIcuVariants() { 221 loadConfiguration(); 222 return enableVariants; 223 } 224 getIcuSuffix()225 private static String getIcuSuffix() { 226 loadConfiguration(); 227 return suffix; 228 } 229 loadConfiguration()230 private static synchronized void loadConfiguration() { 231 if (configLoaded) { 232 return; 233 } 234 Properties spiConfigProps = new Properties(); 235 try { 236 InputStream is = ClassLoader.getSystemResourceAsStream(SPI_PROP_FILE); 237 try { 238 spiConfigProps.load(is); 239 } finally { 240 is.close(); 241 } 242 243 String val = (String)spiConfigProps.get(SUFFIX_KEY); 244 if (val != null && val.length() > 0) { 245 suffix = val; 246 } 247 enableVariants = parseBooleanString((String)spiConfigProps.get(ENABLE_VARIANTS_KEY), enableVariants); 248 enableIso3Lang = parseBooleanString((String)spiConfigProps.get(ENABLE_ISO3_LANG_KEY), enableIso3Lang); 249 useDecimalFormat = parseBooleanString((String)spiConfigProps.get(USE_DECIMALFORMAT_KEY), useDecimalFormat); 250 } catch (IOException ioe) { 251 // Any IO errors, ignore 252 } 253 configLoaded = true; 254 } 255 parseBooleanString(String str, boolean defaultVal)256 private static boolean parseBooleanString(String str, boolean defaultVal) { 257 if (str == null) { 258 return defaultVal; 259 } 260 if (str.equalsIgnoreCase("true")) { 261 return true; 262 } else if (str.equalsIgnoreCase("false")) { 263 return false; 264 } 265 return defaultVal; 266 } 267 } 268