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