• 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#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