Home | History | Annotate | Download | only in javaspi
      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 
     54     public static Locale[] getAvailableLocales() {
     55         Locale[] all = getLocales();
     56         return Arrays.copyOf(all, all.length);
     57     }
     58 
     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 
    108     public static boolean useDecimalFormat() {
    109         loadConfiguration();
    110         return useDecimalFormat;
    111     }
    112 
    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 
    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 
    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 
    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 
    216     private static boolean enableIso3Languages() {
    217         return enableIso3Lang;
    218     }
    219 
    220     private static boolean enableIcuVariants() {
    221         loadConfiguration();
    222         return enableVariants;
    223     }
    224 
    225     private static String getIcuSuffix() {
    226         loadConfiguration();
    227         return suffix;
    228     }
    229 
    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 
    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