Home | History | Annotate | Download | only in impl
      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-2016, International Business Machines Corporation and
      6  * others. All Rights Reserved.
      7  *******************************************************************************
      8  */
      9 package com.ibm.icu.impl;
     10 
     11 import java.text.ParseException;
     12 import java.util.Collections;
     13 import java.util.HashMap;
     14 import java.util.Iterator;
     15 import java.util.Map;
     16 import java.util.MissingResourceException;
     17 import java.util.Set;
     18 import java.util.TreeMap;
     19 
     20 import com.ibm.icu.text.PluralRanges;
     21 import com.ibm.icu.text.PluralRules;
     22 import com.ibm.icu.text.PluralRules.PluralType;
     23 import com.ibm.icu.util.ULocale;
     24 import com.ibm.icu.util.UResourceBundle;
     25 
     26 /**
     27  * Loader for plural rules data.
     28  */
     29 public class PluralRulesLoader extends PluralRules.Factory {
     30     private final Map<String, PluralRules> rulesIdToRules;
     31     // lazy init, use getLocaleIdToRulesIdMap to access
     32     private Map<String, String> localeIdToCardinalRulesId;
     33     private Map<String, String> localeIdToOrdinalRulesId;
     34     private Map<String, ULocale> rulesIdToEquivalentULocale;
     35     private static Map<String, PluralRanges> localeIdToPluralRanges;
     36 
     37 
     38     /**
     39      * Access through singleton.
     40      */
     41     private PluralRulesLoader() {
     42         rulesIdToRules = new HashMap<String, PluralRules>();
     43     }
     44 
     45     /**
     46      * Returns the locales for which we have plurals data. Utility for testing.
     47      */
     48     public ULocale[] getAvailableULocales() {
     49         Set<String> keys = getLocaleIdToRulesIdMap(PluralType.CARDINAL).keySet();
     50         ULocale[] locales = new ULocale[keys.size()];
     51         int n = 0;
     52         for (Iterator<String> iter = keys.iterator(); iter.hasNext();) {
     53             locales[n++] = ULocale.createCanonical(iter.next());
     54         }
     55         return locales;
     56     }
     57 
     58     /**
     59      * Returns the functionally equivalent locale.
     60      */
     61     public ULocale getFunctionalEquivalent(ULocale locale, boolean[] isAvailable) {
     62         if (isAvailable != null && isAvailable.length > 0) {
     63             String localeId = ULocale.canonicalize(locale.getBaseName());
     64             Map<String, String> idMap = getLocaleIdToRulesIdMap(PluralType.CARDINAL);
     65             isAvailable[0] = idMap.containsKey(localeId);
     66         }
     67 
     68         String rulesId = getRulesIdForLocale(locale, PluralType.CARDINAL);
     69         if (rulesId == null || rulesId.trim().length() == 0) {
     70             return ULocale.ROOT; // ultimate fallback
     71         }
     72 
     73         ULocale result = getRulesIdToEquivalentULocaleMap().get(
     74                 rulesId);
     75         if (result == null) {
     76             return ULocale.ROOT; // ultimate fallback
     77         }
     78 
     79         return result;
     80     }
     81 
     82     /**
     83      * Returns the lazily-constructed map.
     84      */
     85     private Map<String, String> getLocaleIdToRulesIdMap(PluralType type) {
     86         checkBuildRulesIdMaps();
     87         return (type == PluralType.CARDINAL) ? localeIdToCardinalRulesId : localeIdToOrdinalRulesId;
     88     }
     89 
     90     /**
     91      * Returns the lazily-constructed map.
     92      */
     93     private Map<String, ULocale> getRulesIdToEquivalentULocaleMap() {
     94         checkBuildRulesIdMaps();
     95         return rulesIdToEquivalentULocale;
     96     }
     97 
     98     /**
     99      * Lazily constructs the localeIdToRulesId and rulesIdToEquivalentULocale
    100      * maps if necessary. These exactly reflect the contents of the locales
    101      * resource in plurals.res.
    102      */
    103     private void checkBuildRulesIdMaps() {
    104         boolean haveMap;
    105         synchronized (this) {
    106             haveMap = localeIdToCardinalRulesId != null;
    107         }
    108         if (!haveMap) {
    109             Map<String, String> tempLocaleIdToCardinalRulesId;
    110             Map<String, String> tempLocaleIdToOrdinalRulesId;
    111             Map<String, ULocale> tempRulesIdToEquivalentULocale;
    112             try {
    113                 UResourceBundle pluralb = getPluralBundle();
    114                 // Read cardinal-number rules.
    115                 UResourceBundle localeb = pluralb.get("locales");
    116 
    117                 // sort for convenience of getAvailableULocales
    118                 tempLocaleIdToCardinalRulesId = new TreeMap<String, String>();
    119                 // not visible
    120                 tempRulesIdToEquivalentULocale = new HashMap<String, ULocale>();
    121 
    122                 for (int i = 0; i < localeb.getSize(); ++i) {
    123                     UResourceBundle b = localeb.get(i);
    124                     String id = b.getKey();
    125                     String value = b.getString().intern();
    126                     tempLocaleIdToCardinalRulesId.put(id, value);
    127 
    128                     if (!tempRulesIdToEquivalentULocale.containsKey(value)) {
    129                         tempRulesIdToEquivalentULocale.put(value, new ULocale(id));
    130                     }
    131                 }
    132 
    133                 // Read ordinal-number rules.
    134                 localeb = pluralb.get("locales_ordinals");
    135                 tempLocaleIdToOrdinalRulesId = new TreeMap<String, String>();
    136                 for (int i = 0; i < localeb.getSize(); ++i) {
    137                     UResourceBundle b = localeb.get(i);
    138                     String id = b.getKey();
    139                     String value = b.getString().intern();
    140                     tempLocaleIdToOrdinalRulesId.put(id, value);
    141                 }
    142             } catch (MissingResourceException e) {
    143                 // dummy so we don't try again
    144                 tempLocaleIdToCardinalRulesId = Collections.emptyMap();
    145                 tempLocaleIdToOrdinalRulesId = Collections.emptyMap();
    146                 tempRulesIdToEquivalentULocale = Collections.emptyMap();
    147             }
    148 
    149             synchronized(this) {
    150                 if (localeIdToCardinalRulesId == null) {
    151                     localeIdToCardinalRulesId = tempLocaleIdToCardinalRulesId;
    152                     localeIdToOrdinalRulesId = tempLocaleIdToOrdinalRulesId;
    153                     rulesIdToEquivalentULocale = tempRulesIdToEquivalentULocale;
    154                 }
    155             }
    156         }
    157     }
    158 
    159     /**
    160      * Gets the rulesId from the locale,with locale fallback. If there is no
    161      * rulesId, return null. The rulesId might be the empty string if the rule
    162      * is the default rule.
    163      */
    164     public String getRulesIdForLocale(ULocale locale, PluralType type) {
    165         Map<String, String> idMap = getLocaleIdToRulesIdMap(type);
    166         String localeId = ULocale.canonicalize(locale.getBaseName());
    167         String rulesId = null;
    168         while (null == (rulesId = idMap.get(localeId))) {
    169             int ix = localeId.lastIndexOf("_");
    170             if (ix == -1) {
    171                 break;
    172             }
    173             localeId = localeId.substring(0, ix);
    174         }
    175         return rulesId;
    176     }
    177 
    178     /**
    179      * Gets the rule from the rulesId. If there is no rule for this rulesId,
    180      * return null.
    181      */
    182     public PluralRules getRulesForRulesId(String rulesId) {
    183         // synchronize on the map.  release the lock temporarily while we build the rules.
    184         PluralRules rules = null;
    185         boolean hasRules;  // Separate boolean because stored rules can be null.
    186         synchronized (rulesIdToRules) {
    187             hasRules = rulesIdToRules.containsKey(rulesId);
    188             if (hasRules) {
    189                 rules = rulesIdToRules.get(rulesId);  // can be null
    190             }
    191         }
    192         if (!hasRules) {
    193             try {
    194                 UResourceBundle pluralb = getPluralBundle();
    195                 UResourceBundle rulesb = pluralb.get("rules");
    196                 UResourceBundle setb = rulesb.get(rulesId);
    197 
    198                 StringBuilder sb = new StringBuilder();
    199                 for (int i = 0; i < setb.getSize(); ++i) {
    200                     UResourceBundle b = setb.get(i);
    201                     if (i > 0) {
    202                         sb.append("; ");
    203                     }
    204                     sb.append(b.getKey());
    205                     sb.append(": ");
    206                     sb.append(b.getString());
    207                 }
    208                 rules = PluralRules.parseDescription(sb.toString());
    209             } catch (ParseException e) {
    210             } catch (MissingResourceException e) {
    211             }
    212             synchronized (rulesIdToRules) {
    213                 if (rulesIdToRules.containsKey(rulesId)) {
    214                     rules = rulesIdToRules.get(rulesId);
    215                 } else {
    216                     rulesIdToRules.put(rulesId, rules);  // can be null
    217                 }
    218             }
    219         }
    220         return rules;
    221     }
    222 
    223     /**
    224      * Return the plurals resource. Note MissingResourceException is unchecked,
    225      * listed here for clarity. Callers should handle this exception.
    226      */
    227     public UResourceBundle getPluralBundle() throws MissingResourceException {
    228         return ICUResourceBundle.getBundleInstance(
    229                 ICUData.ICU_BASE_NAME, "plurals",
    230                 ICUResourceBundle.ICU_DATA_CLASS_LOADER, true);
    231     }
    232 
    233     /**
    234      * Returns the plural rules for the the locale. If we don't have data,
    235      * com.ibm.icu.text.PluralRules.DEFAULT is returned.
    236      */
    237     public PluralRules forLocale(ULocale locale, PluralRules.PluralType type) {
    238         String rulesId = getRulesIdForLocale(locale, type);
    239         if (rulesId == null || rulesId.trim().length() == 0) {
    240             return PluralRules.DEFAULT;
    241         }
    242         PluralRules rules = getRulesForRulesId(rulesId);
    243         if (rules == null) {
    244             rules = PluralRules.DEFAULT;
    245         }
    246         return rules;
    247     }
    248 
    249     /**
    250      * The only instance of the loader.
    251      */
    252     public static final PluralRulesLoader loader = new PluralRulesLoader();
    253 
    254     /* (non-Javadoc)
    255      * @see com.ibm.icu.text.PluralRules.Factory#hasOverride(com.ibm.icu.util.ULocale)
    256      */
    257     @Override
    258     public boolean hasOverride(ULocale locale) {
    259         return false;
    260     }
    261 
    262     private static final PluralRanges UNKNOWN_RANGE = new PluralRanges().freeze();
    263 
    264     public PluralRanges getPluralRanges(ULocale locale) {
    265         // TODO markdavis Fix the bad fallback, here and elsewhere in this file.
    266         String localeId = ULocale.canonicalize(locale.getBaseName());
    267         PluralRanges result;
    268         while (null == (result = localeIdToPluralRanges.get(localeId))) {
    269             int ix = localeId.lastIndexOf("_");
    270             if (ix == -1) {
    271                 result = UNKNOWN_RANGE;
    272                 break;
    273             }
    274             localeId = localeId.substring(0, ix);
    275         }
    276         return result;
    277     }
    278 
    279     public boolean isPluralRangesAvailable(ULocale locale) {
    280         return getPluralRanges(locale) == UNKNOWN_RANGE;
    281     }
    282 
    283     // TODO markdavis FIX HARD-CODED HACK once we have data from CLDR in the bundles
    284     static {
    285         String[][] pluralRangeData = {
    286                 {"locales", "id ja km ko lo ms my th vi zh"},
    287                 {"other", "other", "other"},
    288 
    289                 {"locales", "am bn fr gu hi hy kn mr pa zu"},
    290                 {"one", "one", "one"},
    291                 {"one", "other", "other"},
    292                 {"other", "other", "other"},
    293 
    294                 {"locales", "fa"},
    295                 {"one", "one", "other"},
    296                 {"one", "other", "other"},
    297                 {"other", "other", "other"},
    298 
    299                 {"locales", "ka"},
    300                 {"one", "other", "one"},
    301                 {"other", "one", "other"},
    302                 {"other", "other", "other"},
    303 
    304                 {"locales", "az de el gl hu it kk ky ml mn ne nl pt sq sw ta te tr ug uz"},
    305                 {"one", "other", "other"},
    306                 {"other", "one", "one"},
    307                 {"other", "other", "other"},
    308 
    309                 {"locales", "af bg ca en es et eu fi nb sv ur"},
    310                 {"one", "other", "other"},
    311                 {"other", "one", "other"},
    312                 {"other", "other", "other"},
    313 
    314                 {"locales", "da fil is"},
    315                 {"one", "one", "one"},
    316                 {"one", "other", "other"},
    317                 {"other", "one", "one"},
    318                 {"other", "other", "other"},
    319 
    320                 {"locales", "si"},
    321                 {"one", "one", "one"},
    322                 {"one", "other", "other"},
    323                 {"other", "one", "other"},
    324                 {"other", "other", "other"},
    325 
    326                 {"locales", "mk"},
    327                 {"one", "one", "other"},
    328                 {"one", "other", "other"},
    329                 {"other", "one", "other"},
    330                 {"other", "other", "other"},
    331 
    332                 {"locales", "lv"},
    333                 {"zero", "zero", "other"},
    334                 {"zero", "one", "one"},
    335                 {"zero", "other", "other"},
    336                 {"one", "zero", "other"},
    337                 {"one", "one", "one"},
    338                 {"one", "other", "other"},
    339                 {"other", "zero", "other"},
    340                 {"other", "one", "one"},
    341                 {"other", "other", "other"},
    342 
    343                 {"locales", "ro"},
    344                 {"one", "few", "few"},
    345                 {"one", "other", "other"},
    346                 {"few", "one", "few"},
    347                 {"few", "few", "few"},
    348                 {"few", "other", "other"},
    349                 {"other", "few", "few"},
    350                 {"other", "other", "other"},
    351 
    352                 {"locales", "hr sr bs"},
    353                 {"one", "one", "one"},
    354                 {"one", "few", "few"},
    355                 {"one", "other", "other"},
    356                 {"few", "one", "one"},
    357                 {"few", "few", "few"},
    358                 {"few", "other", "other"},
    359                 {"other", "one", "one"},
    360                 {"other", "few", "few"},
    361                 {"other", "other", "other"},
    362 
    363                 {"locales", "sl"},
    364                 {"one", "one", "few"},
    365                 {"one", "two", "two"},
    366                 {"one", "few", "few"},
    367                 {"one", "other", "other"},
    368                 {"two", "one", "few"},
    369                 {"two", "two", "two"},
    370                 {"two", "few", "few"},
    371                 {"two", "other", "other"},
    372                 {"few", "one", "few"},
    373                 {"few", "two", "two"},
    374                 {"few", "few", "few"},
    375                 {"few", "other", "other"},
    376                 {"other", "one", "few"},
    377                 {"other", "two", "two"},
    378                 {"other", "few", "few"},
    379                 {"other", "other", "other"},
    380 
    381                 {"locales", "he"},
    382                 {"one", "two", "other"},
    383                 {"one", "many", "many"},
    384                 {"one", "other", "other"},
    385                 {"two", "many", "other"},
    386                 {"two", "other", "other"},
    387                 {"many", "many", "many"},
    388                 {"many", "other", "many"},
    389                 {"other", "one", "other"},
    390                 {"other", "two", "other"},
    391                 {"other", "many", "many"},
    392                 {"other", "other", "other"},
    393 
    394                 {"locales", "cs pl sk"},
    395                 {"one", "few", "few"},
    396                 {"one", "many", "many"},
    397                 {"one", "other", "other"},
    398                 {"few", "few", "few"},
    399                 {"few", "many", "many"},
    400                 {"few", "other", "other"},
    401                 {"many", "one", "one"},
    402                 {"many", "few", "few"},
    403                 {"many", "many", "many"},
    404                 {"many", "other", "other"},
    405                 {"other", "one", "one"},
    406                 {"other", "few", "few"},
    407                 {"other", "many", "many"},
    408                 {"other", "other", "other"},
    409 
    410                 {"locales", "lt ru uk"},
    411                 {"one", "one", "one"},
    412                 {"one", "few", "few"},
    413                 {"one", "many", "many"},
    414                 {"one", "other", "other"},
    415                 {"few", "one", "one"},
    416                 {"few", "few", "few"},
    417                 {"few", "many", "many"},
    418                 {"few", "other", "other"},
    419                 {"many", "one", "one"},
    420                 {"many", "few", "few"},
    421                 {"many", "many", "many"},
    422                 {"many", "other", "other"},
    423                 {"other", "one", "one"},
    424                 {"other", "few", "few"},
    425                 {"other", "many", "many"},
    426                 {"other", "other", "other"},
    427 
    428                 {"locales", "cy"},
    429                 {"zero", "one", "one"},
    430                 {"zero", "two", "two"},
    431                 {"zero", "few", "few"},
    432                 {"zero", "many", "many"},
    433                 {"zero", "other", "other"},
    434                 {"one", "two", "two"},
    435                 {"one", "few", "few"},
    436                 {"one", "many", "many"},
    437                 {"one", "other", "other"},
    438                 {"two", "few", "few"},
    439                 {"two", "many", "many"},
    440                 {"two", "other", "other"},
    441                 {"few", "many", "many"},
    442                 {"few", "other", "other"},
    443                 {"many", "other", "other"},
    444                 {"other", "one", "one"},
    445                 {"other", "two", "two"},
    446                 {"other", "few", "few"},
    447                 {"other", "many", "many"},
    448                 {"other", "other", "other"},
    449 
    450                 {"locales", "ar"},
    451                 {"zero", "one", "zero"},
    452                 {"zero", "two", "zero"},
    453                 {"zero", "few", "few"},
    454                 {"zero", "many", "many"},
    455                 {"zero", "other", "other"},
    456                 {"one", "two", "other"},
    457                 {"one", "few", "few"},
    458                 {"one", "many", "many"},
    459                 {"one", "other", "other"},
    460                 {"two", "few", "few"},
    461                 {"two", "many", "many"},
    462                 {"two", "other", "other"},
    463                 {"few", "few", "few"},
    464                 {"few", "many", "many"},
    465                 {"few", "other", "other"},
    466                 {"many", "few", "few"},
    467                 {"many", "many", "many"},
    468                 {"many", "other", "other"},
    469                 {"other", "one", "other"},
    470                 {"other", "two", "other"},
    471                 {"other", "few", "few"},
    472                 {"other", "many", "many"},
    473                 {"other", "other", "other"},
    474         };
    475         PluralRanges pr = null;
    476         String[] locales = null;
    477         HashMap<String, PluralRanges> tempLocaleIdToPluralRanges = new HashMap<String, PluralRanges>();
    478         for (String[] row : pluralRangeData) {
    479             if (row[0].equals("locales")) {
    480                 if (pr != null) {
    481                     pr.freeze();
    482                     for (String locale : locales) {
    483                         tempLocaleIdToPluralRanges.put(locale, pr);
    484                     }
    485                 }
    486                 locales = row[1].split(" ");
    487                 pr = new PluralRanges();
    488             } else {
    489                 pr.add(
    490                         StandardPlural.fromString(row[0]),
    491                         StandardPlural.fromString(row[1]),
    492                         StandardPlural.fromString(row[2]));
    493             }
    494         }
    495         // do last one
    496         for (String locale : locales) {
    497             tempLocaleIdToPluralRanges.put(locale, pr);
    498         }
    499         // now make whole thing immutable
    500         localeIdToPluralRanges = Collections.unmodifiableMap(tempLocaleIdToPluralRanges);
    501     }
    502 }