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. 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<>(); 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<>(); 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 locales.add(uloc.toLocale()); 187 188 if (enableIcuVariants()) { 189 // Add ICU variant 190 StringBuilder var = new StringBuilder(uloc.getVariant()); 191 if (var.length() != 0) { 192 var.append("_"); 193 } 194 var.append(getIcuSuffix()); 195 196 Builder locBld = new Builder(); 197 try { 198 locBld.setLocale(uloc); 199 locBld.setVariant(var.toString()); 200 ULocale ulocWithVar = locBld.build(); 201 locales.add(ulocWithVar.toLocale()); 202 } catch (Exception ignored) { 203 // ignore 204 } 205 } 206 } 207 enableIso3Languages()208 private static boolean enableIso3Languages() { 209 return enableIso3Lang; 210 } 211 enableIcuVariants()212 private static boolean enableIcuVariants() { 213 loadConfiguration(); 214 return enableVariants; 215 } 216 getIcuSuffix()217 private static String getIcuSuffix() { 218 loadConfiguration(); 219 return suffix; 220 } 221 loadConfiguration()222 private static synchronized void loadConfiguration() { 223 if (configLoaded) { 224 return; 225 } 226 Properties spiConfigProps = new Properties(); 227 try { 228 InputStream is = ClassLoader.getSystemResourceAsStream(SPI_PROP_FILE); 229 try { 230 spiConfigProps.load(is); 231 } finally { 232 is.close(); 233 } 234 235 String val = (String)spiConfigProps.get(SUFFIX_KEY); 236 if (val != null && val.length() > 0) { 237 suffix = val; 238 } 239 enableVariants = parseBooleanString((String)spiConfigProps.get(ENABLE_VARIANTS_KEY), enableVariants); 240 enableIso3Lang = parseBooleanString((String)spiConfigProps.get(ENABLE_ISO3_LANG_KEY), enableIso3Lang); 241 useDecimalFormat = parseBooleanString((String)spiConfigProps.get(USE_DECIMALFORMAT_KEY), useDecimalFormat); 242 } catch (IOException ioe) { 243 // Any IO errors, ignore 244 } 245 configLoaded = true; 246 } 247 parseBooleanString(String str, boolean defaultVal)248 private static boolean parseBooleanString(String str, boolean defaultVal) { 249 if (str == null) { 250 return defaultVal; 251 } 252 if (str.equalsIgnoreCase("true")) { 253 return true; 254 } else if (str.equalsIgnoreCase("false")) { 255 return false; 256 } 257 return defaultVal; 258 } 259 } 260