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 }