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) 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