• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // © 2016 and later: Unicode, Inc. and others.
2 // License & terms of use: http://www.unicode.org/copyright.html
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