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) 2009-2016, International Business Machines Corporation and 7 * others. All Rights Reserved. 8 ******************************************************************************* 9 */ 10 package android.icu.impl; 11 12 import java.lang.ref.SoftReference; 13 import java.util.HashMap; 14 import java.util.Map; 15 import java.util.MissingResourceException; 16 17 import android.icu.impl.CurrencyData.CurrencyDisplayInfo; 18 import android.icu.impl.CurrencyData.CurrencyDisplayInfoProvider; 19 import android.icu.impl.CurrencyData.CurrencyFormatInfo; 20 import android.icu.impl.CurrencyData.CurrencySpacingInfo; 21 import android.icu.impl.ICUResourceBundle.OpenType; 22 import android.icu.util.ICUException; 23 import android.icu.util.ULocale; 24 import android.icu.util.UResourceBundle; 25 26 /** 27 * @hide Only a subset of ICU is exposed in Android 28 */ 29 public class ICUCurrencyDisplayInfoProvider implements CurrencyDisplayInfoProvider { 30 public ICUCurrencyDisplayInfoProvider() { 31 } 32 33 /** 34 * Single-item cache for ICUCurrencyDisplayInfo keyed by locale. 35 */ 36 private volatile ICUCurrencyDisplayInfo currencyDisplayInfoCache = null; 37 38 @Override 39 public CurrencyDisplayInfo getInstance(ULocale locale, boolean withFallback) { 40 // Make sure the locale is non-null (this can happen during deserialization): 41 if (locale == null) { locale = ULocale.ROOT; } 42 ICUCurrencyDisplayInfo instance = currencyDisplayInfoCache; 43 if (instance == null || !instance.locale.equals(locale) || instance.fallback != withFallback) { 44 ICUResourceBundle rb; 45 if (withFallback) { 46 rb = ICUResourceBundle.getBundleInstance( 47 ICUData.ICU_CURR_BASE_NAME, locale, OpenType.LOCALE_DEFAULT_ROOT); 48 } else { 49 try { 50 rb = ICUResourceBundle.getBundleInstance( 51 ICUData.ICU_CURR_BASE_NAME, locale, OpenType.LOCALE_ONLY); 52 } catch (MissingResourceException e) { 53 return null; 54 } 55 } 56 instance = new ICUCurrencyDisplayInfo(locale, rb, withFallback); 57 currencyDisplayInfoCache = instance; 58 } 59 return instance; 60 } 61 62 @Override 63 public boolean hasData() { 64 return true; 65 } 66 67 /** 68 * This class performs data loading for currencies and keeps data in lightweight cache. 69 */ 70 static class ICUCurrencyDisplayInfo extends CurrencyDisplayInfo { 71 final ULocale locale; 72 final boolean fallback; 73 private final ICUResourceBundle rb; 74 75 /** 76 * Single-item cache for getName(), getSymbol(), and getFormatInfo(). 77 * Holds data for only one currency. If another currency is requested, the old cache item is overwritten. 78 */ 79 private volatile FormattingData formattingDataCache = null; 80 81 /** 82 * Single-item cache for getNarrowSymbol(). 83 * Holds data for only one currency. If another currency is requested, the old cache item is overwritten. 84 */ 85 private volatile NarrowSymbol narrowSymbolCache = null; 86 87 /** 88 * Single-item cache for getPluralName(). 89 * 90 * <p> 91 * array[0] is the ISO code.<br> 92 * array[1+p] is the plural name where p=standardPlural.ordinal(). 93 * 94 * <p> 95 * Holds data for only one currency. If another currency is requested, the old cache item is overwritten. 96 */ 97 private volatile String[] pluralsDataCache = null; 98 99 /** 100 * Cache for symbolMap() and nameMap(). 101 */ 102 private volatile SoftReference<ParsingData> parsingDataCache = new SoftReference<ParsingData>(null); 103 104 /** 105 * Cache for getUnitPatterns(). 106 */ 107 private volatile Map<String, String> unitPatternsCache = null; 108 109 /** 110 * Cache for getSpacingInfo(). 111 */ 112 private volatile CurrencySpacingInfo spacingInfoCache = null; 113 114 static class FormattingData { 115 final String isoCode; 116 String displayName = null; 117 String symbol = null; 118 CurrencyFormatInfo formatInfo = null; 119 120 FormattingData(String isoCode) { this.isoCode = isoCode; } 121 } 122 123 static class NarrowSymbol { 124 final String isoCode; 125 String narrowSymbol = null; 126 127 NarrowSymbol(String isoCode) { this.isoCode = isoCode; } 128 } 129 130 static class ParsingData { 131 Map<String, String> symbolToIsoCode = new HashMap<String, String>(); 132 Map<String, String> nameToIsoCode = new HashMap<String, String>(); 133 } 134 135 //////////////////////// 136 /// START PUBLIC API /// 137 //////////////////////// 138 139 public ICUCurrencyDisplayInfo(ULocale locale, ICUResourceBundle rb, boolean fallback) { 140 this.locale = locale; 141 this.fallback = fallback; 142 this.rb = rb; 143 } 144 145 @Override 146 public ULocale getULocale() { 147 return rb.getULocale(); 148 } 149 150 @Override 151 public String getName(String isoCode) { 152 FormattingData formattingData = fetchFormattingData(isoCode); 153 154 // Fall back to ISO Code 155 if (formattingData.displayName == null && fallback) { 156 return isoCode; 157 } 158 return formattingData.displayName; 159 } 160 161 @Override 162 public String getSymbol(String isoCode) { 163 FormattingData formattingData = fetchFormattingData(isoCode); 164 165 // Fall back to ISO Code 166 if (formattingData.symbol == null && fallback) { 167 return isoCode; 168 } 169 return formattingData.symbol; 170 } 171 172 @Override 173 public String getNarrowSymbol(String isoCode) { 174 NarrowSymbol narrowSymbol = fetchNarrowSymbol(isoCode); 175 176 // Fall back to ISO Code 177 // TODO: Should this fall back to the regular symbol instead of the ISO code? 178 if (narrowSymbol.narrowSymbol == null && fallback) { 179 return isoCode; 180 } 181 return narrowSymbol.narrowSymbol; 182 } 183 184 @Override 185 public String getPluralName(String isoCode, String pluralKey ) { 186 StandardPlural plural = StandardPlural.orNullFromString(pluralKey); 187 String[] pluralsData = fetchPluralsData(isoCode); 188 189 // See http://unicode.org/reports/tr35/#Currencies, especially the fallback rule. 190 String result = null; 191 if (plural != null) { 192 result = pluralsData[1 + plural.ordinal()]; 193 } 194 if (result == null && fallback) { 195 // First fall back to the "other" plural variant 196 // Note: If plural is already "other", this fallback is benign 197 result = pluralsData[1 + StandardPlural.OTHER.ordinal()]; 198 } 199 if (result == null && fallback) { 200 // If that fails, fall back to the display name 201 FormattingData formattingData = fetchFormattingData(isoCode); 202 result = formattingData.displayName; 203 } 204 if (result == null && fallback) { 205 // If all else fails, return the ISO code 206 result = isoCode; 207 } 208 return result; 209 } 210 211 @Override 212 public Map<String, String> symbolMap() { 213 ParsingData parsingData = fetchParsingData(); 214 return parsingData.symbolToIsoCode; 215 } 216 217 @Override 218 public Map<String, String> nameMap() { 219 ParsingData parsingData = fetchParsingData(); 220 return parsingData.nameToIsoCode; 221 } 222 223 @Override 224 public Map<String, String> getUnitPatterns() { 225 // Default result is the empty map. Callers who require a pattern will have to 226 // supply a default. 227 Map<String,String> unitPatterns = fetchUnitPatterns(); 228 return unitPatterns; 229 } 230 231 @Override 232 public CurrencyFormatInfo getFormatInfo(String isoCode) { 233 FormattingData formattingData = fetchFormattingData(isoCode); 234 return formattingData.formatInfo; 235 } 236 237 @Override 238 public CurrencySpacingInfo getSpacingInfo() { 239 CurrencySpacingInfo spacingInfo = fetchSpacingInfo(); 240 241 // Fall back to DEFAULT 242 if ((!spacingInfo.hasBeforeCurrency || !spacingInfo.hasAfterCurrency) && fallback) { 243 return CurrencySpacingInfo.DEFAULT; 244 } 245 return spacingInfo; 246 } 247 248 ///////////////////////////////////////////// 249 /// END PUBLIC API -- START DATA FRONTEND /// 250 ///////////////////////////////////////////// 251 252 FormattingData fetchFormattingData(String isoCode) { 253 FormattingData result = formattingDataCache; 254 if (result == null || !result.isoCode.equals(isoCode)) { 255 result = new FormattingData(isoCode); 256 CurrencySink sink = new CurrencySink(!fallback, CurrencySink.EntrypointTable.CURRENCIES); 257 sink.formattingData = result; 258 rb.getAllItemsWithFallbackNoFail("Currencies/" + isoCode, sink); 259 formattingDataCache = result; 260 } 261 return result; 262 } 263 264 NarrowSymbol fetchNarrowSymbol(String isoCode) { 265 NarrowSymbol result = narrowSymbolCache; 266 if (result == null || !result.isoCode.equals(isoCode)) { 267 result = new NarrowSymbol(isoCode); 268 CurrencySink sink = new CurrencySink(!fallback, CurrencySink.EntrypointTable.CURRENCY_NARROW); 269 sink.narrowSymbol = result; 270 rb.getAllItemsWithFallbackNoFail("Currencies%narrow/" + isoCode, sink); 271 narrowSymbolCache = result; 272 } 273 return result; 274 } 275 276 String[] fetchPluralsData(String isoCode) { 277 String[] result = pluralsDataCache; 278 if (result == null || !result[0].equals(isoCode)) { 279 result = new String[1 + StandardPlural.COUNT]; 280 result[0] = isoCode; 281 CurrencySink sink = new CurrencySink(!fallback, CurrencySink.EntrypointTable.CURRENCY_PLURALS); 282 sink.pluralsData = result; 283 rb.getAllItemsWithFallbackNoFail("CurrencyPlurals/" + isoCode, sink); 284 pluralsDataCache = result; 285 } 286 return result; 287 } 288 289 ParsingData fetchParsingData() { 290 ParsingData result = parsingDataCache.get(); 291 if (result == null) { 292 result = new ParsingData(); 293 CurrencySink sink = new CurrencySink(!fallback, CurrencySink.EntrypointTable.TOP); 294 sink.parsingData = result; 295 rb.getAllItemsWithFallback("", sink); 296 parsingDataCache = new SoftReference<ParsingData>(result); 297 } 298 return result; 299 } 300 301 Map<String, String> fetchUnitPatterns() { 302 Map<String, String> result = unitPatternsCache; 303 if (result == null) { 304 result = new HashMap<String, String>(); 305 CurrencySink sink = new CurrencySink(!fallback, CurrencySink.EntrypointTable.CURRENCY_UNIT_PATTERNS); 306 sink.unitPatterns = result; 307 rb.getAllItemsWithFallback("CurrencyUnitPatterns", sink); 308 unitPatternsCache = result; 309 } 310 return result; 311 } 312 313 CurrencySpacingInfo fetchSpacingInfo() { 314 CurrencySpacingInfo result = spacingInfoCache; 315 if (result == null) { 316 result = new CurrencySpacingInfo(); 317 CurrencySink sink = new CurrencySink(!fallback, CurrencySink.EntrypointTable.CURRENCY_SPACING); 318 sink.spacingInfo = result; 319 rb.getAllItemsWithFallback("currencySpacing", sink); 320 spacingInfoCache = result; 321 } 322 return result; 323 } 324 325 //////////////////////////////////////////// 326 /// END DATA FRONTEND -- START DATA SINK /// 327 //////////////////////////////////////////// 328 329 private static final class CurrencySink extends UResource.Sink { 330 final boolean noRoot; 331 final EntrypointTable entrypointTable; 332 333 // The fields to be populated on this run of the data sink will be non-null. 334 FormattingData formattingData = null; 335 String[] pluralsData = null; 336 ParsingData parsingData = null; 337 Map<String, String> unitPatterns = null; 338 CurrencySpacingInfo spacingInfo = null; 339 NarrowSymbol narrowSymbol = null; 340 341 enum EntrypointTable { 342 // For Parsing: 343 TOP, 344 345 // For Formatting: 346 CURRENCIES, 347 CURRENCY_PLURALS, 348 CURRENCY_NARROW, 349 CURRENCY_SPACING, 350 CURRENCY_UNIT_PATTERNS 351 } 352 353 CurrencySink(boolean noRoot, EntrypointTable entrypointTable) { 354 this.noRoot = noRoot; 355 this.entrypointTable = entrypointTable; 356 } 357 358 /** 359 * The entrypoint method delegates to helper methods for each of the types of tables 360 * found in the currency data. 361 */ 362 @Override 363 public void put(UResource.Key key, UResource.Value value, boolean isRoot) { 364 if (noRoot && isRoot) { 365 // Don't consume the root bundle 366 return; 367 } 368 369 switch (entrypointTable) { 370 case TOP: 371 consumeTopTable(key, value); 372 break; 373 case CURRENCIES: 374 consumeCurrenciesEntry(key, value); 375 break; 376 case CURRENCY_PLURALS: 377 consumeCurrencyPluralsEntry(key, value); 378 break; 379 case CURRENCY_NARROW: 380 consumeCurrenciesNarrowEntry(key, value); 381 break; 382 case CURRENCY_SPACING: 383 consumeCurrencySpacingTable(key, value); 384 break; 385 case CURRENCY_UNIT_PATTERNS: 386 consumeCurrencyUnitPatternsTable(key, value); 387 break; 388 } 389 } 390 391 private void consumeTopTable(UResource.Key key, UResource.Value value) { 392 UResource.Table table = value.getTable(); 393 for (int i = 0; table.getKeyAndValue(i, key, value); i++) { 394 if (key.contentEquals("Currencies")) { 395 consumeCurrenciesTable(key, value); 396 } else if (key.contentEquals("Currencies%variant")) { 397 consumeCurrenciesVariantTable(key, value); 398 } else if (key.contentEquals("CurrencyPlurals")) { 399 consumeCurrencyPluralsTable(key, value); 400 } 401 } 402 } 403 404 /* 405 * Currencies{ 406 * ... 407 * USD{ 408 * "US$", => symbol 409 * "US Dollar", => display name 410 * } 411 * ... 412 * ESP{ 413 * "", => symbol 414 * "pesseta espanyola", => display name 415 * { 416 * "#,##0.00", => currency-specific pattern 417 * ",", => currency-specific grouping separator 418 * ".", => currency-specific decimal separator 419 * } 420 * } 421 * ... 422 * } 423 */ 424 void consumeCurrenciesTable(UResource.Key key, UResource.Value value) { 425 // The full Currencies table is consumed for parsing only. 426 assert parsingData != null; 427 UResource.Table table = value.getTable(); 428 for (int i = 0; table.getKeyAndValue(i, key, value); i++) { 429 String isoCode = key.toString(); 430 if (value.getType() != UResourceBundle.ARRAY) { 431 throw new ICUException("Unexpected data type in Currencies table for " + isoCode); 432 } 433 UResource.Array array = value.getArray(); 434 435 parsingData.symbolToIsoCode.put(isoCode, isoCode); // Add the ISO code itself as a symbol 436 array.getValue(0, value); 437 parsingData.symbolToIsoCode.put(value.getString(), isoCode); 438 array.getValue(1, value); 439 parsingData.nameToIsoCode.put(value.getString(), isoCode); 440 } 441 } 442 443 void consumeCurrenciesEntry(UResource.Key key, UResource.Value value) { 444 assert formattingData != null; 445 String isoCode = key.toString(); 446 if (value.getType() != UResourceBundle.ARRAY) { 447 throw new ICUException("Unexpected data type in Currencies table for " + isoCode); 448 } 449 UResource.Array array = value.getArray(); 450 451 if (formattingData.symbol == null) { 452 array.getValue(0, value); 453 formattingData.symbol = value.getString(); 454 } 455 if (formattingData.displayName == null) { 456 array.getValue(1, value); 457 formattingData.displayName = value.getString(); 458 } 459 460 // If present, the third element is the currency format info. 461 // TODO: Write unit test to ensure that this data is being used by number formatting. 462 if (array.getSize() > 2 && formattingData.formatInfo == null) { 463 array.getValue(2, value); 464 UResource.Array formatArray = value.getArray(); 465 formatArray.getValue(0, value); 466 String formatPattern = value.getString(); 467 formatArray.getValue(1, value); 468 String decimalSeparator = value.getString(); 469 formatArray.getValue(2, value); 470 String groupingSeparator = value.getString(); 471 formattingData.formatInfo = new CurrencyFormatInfo( 472 isoCode, formatPattern, decimalSeparator, groupingSeparator); 473 } 474 } 475 476 /* 477 * Currencies%narrow{ 478 * AOA{"Kz"} 479 * ARS{"$"} 480 * ... 481 * } 482 */ 483 void consumeCurrenciesNarrowEntry(UResource.Key key, UResource.Value value) { 484 assert narrowSymbol != null; 485 // No extra structure to traverse. 486 if (narrowSymbol.narrowSymbol == null) { 487 narrowSymbol.narrowSymbol = value.getString(); 488 } 489 } 490 491 /* 492 * Currencies%variant{ 493 * TRY{"TL"} 494 * } 495 */ 496 void consumeCurrenciesVariantTable(UResource.Key key, UResource.Value value) { 497 // Note: This data is used for parsing but not formatting. 498 assert parsingData != null; 499 UResource.Table table = value.getTable(); 500 for (int i = 0; table.getKeyAndValue(i, key, value); i++) { 501 String isoCode = key.toString(); 502 parsingData.symbolToIsoCode.put(value.getString(), isoCode); 503 } 504 } 505 506 /* 507 * CurrencyPlurals{ 508 * BYB{ 509 * one{"Belarusian new rouble (19941999)"} 510 * other{"Belarusian new roubles (19941999)"} 511 * } 512 * ... 513 * } 514 */ 515 void consumeCurrencyPluralsTable(UResource.Key key, UResource.Value value) { 516 // The full CurrencyPlurals table is consumed for parsing only. 517 assert parsingData != null; 518 UResource.Table table = value.getTable(); 519 for (int i = 0; table.getKeyAndValue(i, key, value); i++) { 520 String isoCode = key.toString(); 521 UResource.Table pluralsTable = value.getTable(); 522 for (int j=0; pluralsTable.getKeyAndValue(j, key, value); j++) { 523 StandardPlural plural = StandardPlural.orNullFromString(key.toString()); 524 if (plural == null) { 525 throw new ICUException("Could not make StandardPlural from keyword " + key); 526 } 527 528 parsingData.nameToIsoCode.put(value.getString(), isoCode); 529 } 530 } 531 } 532 533 void consumeCurrencyPluralsEntry(UResource.Key key, UResource.Value value) { 534 assert pluralsData != null; 535 UResource.Table pluralsTable = value.getTable(); 536 for (int j=0; pluralsTable.getKeyAndValue(j, key, value); j++) { 537 StandardPlural plural = StandardPlural.orNullFromString(key.toString()); 538 if (plural == null) { 539 throw new ICUException("Could not make StandardPlural from keyword " + key); 540 } 541 542 if (pluralsData[1 + plural.ordinal()] == null) { 543 pluralsData[1 + plural.ordinal()] = value.getString(); 544 } 545 } 546 } 547 548 /* 549 * currencySpacing{ 550 * afterCurrency{ 551 * currencyMatch{"[:^S:]"} 552 * insertBetween{""} 553 * surroundingMatch{"[:digit:]"} 554 * } 555 * beforeCurrency{ 556 * currencyMatch{"[:^S:]"} 557 * insertBetween{""} 558 * surroundingMatch{"[:digit:]"} 559 * } 560 * } 561 */ 562 void consumeCurrencySpacingTable(UResource.Key key, UResource.Value value) { 563 assert spacingInfo != null; 564 UResource.Table spacingTypesTable = value.getTable(); 565 for (int i = 0; spacingTypesTable.getKeyAndValue(i, key, value); ++i) { 566 CurrencySpacingInfo.SpacingType type; 567 if (key.contentEquals("beforeCurrency")) { 568 type = CurrencySpacingInfo.SpacingType.BEFORE; 569 spacingInfo.hasBeforeCurrency = true; 570 } else if (key.contentEquals("afterCurrency")) { 571 type = CurrencySpacingInfo.SpacingType.AFTER; 572 spacingInfo.hasAfterCurrency = true; 573 } else { 574 continue; 575 } 576 577 UResource.Table patternsTable = value.getTable(); 578 for (int j = 0; patternsTable.getKeyAndValue(j, key, value); ++j) { 579 CurrencySpacingInfo.SpacingPattern pattern; 580 if (key.contentEquals("currencyMatch")) { 581 pattern = CurrencySpacingInfo.SpacingPattern.CURRENCY_MATCH; 582 } else if (key.contentEquals("surroundingMatch")) { 583 pattern = CurrencySpacingInfo.SpacingPattern.SURROUNDING_MATCH; 584 } else if (key.contentEquals("insertBetween")) { 585 pattern = CurrencySpacingInfo.SpacingPattern.INSERT_BETWEEN; 586 } else { 587 continue; 588 } 589 590 spacingInfo.setSymbolIfNull(type, pattern, value.getString()); 591 } 592 } 593 } 594 595 /* 596 * CurrencyUnitPatterns{ 597 * other{"{0} {1}"} 598 * ... 599 * } 600 */ 601 void consumeCurrencyUnitPatternsTable(UResource.Key key, UResource.Value value) { 602 assert unitPatterns != null; 603 UResource.Table table = value.getTable(); 604 for (int i = 0; table.getKeyAndValue(i, key, value); i++) { 605 String pluralKeyword = key.toString(); 606 if (unitPatterns.get(pluralKeyword) == null) { 607 unitPatterns.put(pluralKeyword, value.getString()); 608 } 609 } 610 } 611 } 612 } 613 } 614