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