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