All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.ibm.icu.impl.ICUCurrencyDisplayInfoProvider Maven / Gradle / Ivy

There is a newer version: 2.12.15
Show newest version
// © 2016 and later: Unicode, Inc. and others.
// License & terms of use: http://www.unicode.org/copyright.html#License
/*
 *******************************************************************************
 * Copyright (C) 2009-2016, International Business Machines Corporation and
 * others. All Rights Reserved.
 *******************************************************************************
 */
package com.ibm.icu.impl;

import java.lang.ref.SoftReference;
import java.util.HashMap;
import java.util.Map;
import java.util.MissingResourceException;

import com.ibm.icu.impl.CurrencyData.CurrencyDisplayInfo;
import com.ibm.icu.impl.CurrencyData.CurrencyDisplayInfoProvider;
import com.ibm.icu.impl.CurrencyData.CurrencyFormatInfo;
import com.ibm.icu.impl.CurrencyData.CurrencySpacingInfo;
import com.ibm.icu.impl.ICUResourceBundle.OpenType;
import com.ibm.icu.util.ICUException;
import com.ibm.icu.util.ULocale;
import com.ibm.icu.util.UResourceBundle;

public class ICUCurrencyDisplayInfoProvider implements CurrencyDisplayInfoProvider {
    public ICUCurrencyDisplayInfoProvider() {
    }

    /**
     * Single-item cache for ICUCurrencyDisplayInfo keyed by locale.
     */
    private volatile ICUCurrencyDisplayInfo currencyDisplayInfoCache = null;

    @Override
    public CurrencyDisplayInfo getInstance(ULocale locale, boolean withFallback) {
        // Make sure the locale is non-null (this can happen during deserialization):
        if (locale == null) { locale = ULocale.ROOT; }
        ICUCurrencyDisplayInfo instance = currencyDisplayInfoCache;
        if (instance == null || !instance.locale.equals(locale) || instance.fallback != withFallback) {
            ICUResourceBundle rb;
            if (withFallback) {
                rb = ICUResourceBundle.getBundleInstance(
                        ICUData.ICU_CURR_BASE_NAME, locale, OpenType.LOCALE_DEFAULT_ROOT);
            } else {
                try {
                    rb = ICUResourceBundle.getBundleInstance(
                            ICUData.ICU_CURR_BASE_NAME, locale, OpenType.LOCALE_ONLY);
                } catch (MissingResourceException e) {
                    return null;
                }
            }
            instance = new ICUCurrencyDisplayInfo(locale, rb, withFallback);
            currencyDisplayInfoCache = instance;
        }
        return instance;
    }

    @Override
    public boolean hasData() {
        return true;
    }

    /**
     * This class performs data loading for currencies and keeps data in lightweight cache.
     */
    static class ICUCurrencyDisplayInfo extends CurrencyDisplayInfo {
        final ULocale locale;
        final boolean fallback;
        private final ICUResourceBundle rb;

        /**
         * Single-item cache for getName(), getSymbol(), and getFormatInfo().
         * Holds data for only one currency. If another currency is requested, the old cache item is overwritten.
         */
        private volatile FormattingData formattingDataCache = null;

        /**
         * Single-item cache for getNarrowSymbol().
         * Holds data for only one currency. If another currency is requested, the old cache item is overwritten.
         */
        private volatile NarrowSymbol narrowSymbolCache = null;

        /**
         * Single-item cache for getPluralName().
         *
         * 

* array[0] is the ISO code.
* array[1+p] is the plural name where p=standardPlural.ordinal(). * *

* Holds data for only one currency. If another currency is requested, the old cache item is overwritten. */ private volatile String[] pluralsDataCache = null; /** * Cache for symbolMap() and nameMap(). */ private volatile SoftReference parsingDataCache = new SoftReference(null); /** * Cache for getUnitPatterns(). */ private volatile Map unitPatternsCache = null; /** * Cache for getSpacingInfo(). */ private volatile CurrencySpacingInfo spacingInfoCache = null; static class FormattingData { final String isoCode; String displayName = null; String symbol = null; CurrencyFormatInfo formatInfo = null; FormattingData(String isoCode) { this.isoCode = isoCode; } } static class NarrowSymbol { final String isoCode; String narrowSymbol = null; NarrowSymbol(String isoCode) { this.isoCode = isoCode; } } static class ParsingData { Map symbolToIsoCode = new HashMap(); Map nameToIsoCode = new HashMap(); } //////////////////////// /// START PUBLIC API /// //////////////////////// public ICUCurrencyDisplayInfo(ULocale locale, ICUResourceBundle rb, boolean fallback) { this.locale = locale; this.fallback = fallback; this.rb = rb; } @Override public ULocale getULocale() { return rb.getULocale(); } @Override public String getName(String isoCode) { FormattingData formattingData = fetchFormattingData(isoCode); // Fall back to ISO Code if (formattingData.displayName == null && fallback) { return isoCode; } return formattingData.displayName; } @Override public String getSymbol(String isoCode) { FormattingData formattingData = fetchFormattingData(isoCode); // Fall back to ISO Code if (formattingData.symbol == null && fallback) { return isoCode; } return formattingData.symbol; } @Override public String getNarrowSymbol(String isoCode) { NarrowSymbol narrowSymbol = fetchNarrowSymbol(isoCode); // Fall back to ISO Code // TODO: Should this fall back to the regular symbol instead of the ISO code? if (narrowSymbol.narrowSymbol == null && fallback) { return isoCode; } return narrowSymbol.narrowSymbol; } @Override public String getPluralName(String isoCode, String pluralKey ) { StandardPlural plural = StandardPlural.orNullFromString(pluralKey); String[] pluralsData = fetchPluralsData(isoCode); // See http://unicode.org/reports/tr35/#Currencies, especially the fallback rule. String result = null; if (plural != null) { result = pluralsData[1 + plural.ordinal()]; } if (result == null && fallback) { // First fall back to the "other" plural variant // Note: If plural is already "other", this fallback is benign result = pluralsData[1 + StandardPlural.OTHER.ordinal()]; } if (result == null && fallback) { // If that fails, fall back to the display name FormattingData formattingData = fetchFormattingData(isoCode); result = formattingData.displayName; } if (result == null && fallback) { // If all else fails, return the ISO code result = isoCode; } return result; } @Override public Map symbolMap() { ParsingData parsingData = fetchParsingData(); return parsingData.symbolToIsoCode; } @Override public Map nameMap() { ParsingData parsingData = fetchParsingData(); return parsingData.nameToIsoCode; } @Override public Map getUnitPatterns() { // Default result is the empty map. Callers who require a pattern will have to // supply a default. Map unitPatterns = fetchUnitPatterns(); return unitPatterns; } @Override public CurrencyFormatInfo getFormatInfo(String isoCode) { FormattingData formattingData = fetchFormattingData(isoCode); return formattingData.formatInfo; } @Override public CurrencySpacingInfo getSpacingInfo() { CurrencySpacingInfo spacingInfo = fetchSpacingInfo(); // Fall back to DEFAULT if ((!spacingInfo.hasBeforeCurrency || !spacingInfo.hasAfterCurrency) && fallback) { return CurrencySpacingInfo.DEFAULT; } return spacingInfo; } ///////////////////////////////////////////// /// END PUBLIC API -- START DATA FRONTEND /// ///////////////////////////////////////////// FormattingData fetchFormattingData(String isoCode) { FormattingData result = formattingDataCache; if (result == null || !result.isoCode.equals(isoCode)) { result = new FormattingData(isoCode); CurrencySink sink = new CurrencySink(!fallback, CurrencySink.EntrypointTable.CURRENCIES); sink.formattingData = result; rb.getAllItemsWithFallbackNoFail("Currencies/" + isoCode, sink); formattingDataCache = result; } return result; } NarrowSymbol fetchNarrowSymbol(String isoCode) { NarrowSymbol result = narrowSymbolCache; if (result == null || !result.isoCode.equals(isoCode)) { result = new NarrowSymbol(isoCode); CurrencySink sink = new CurrencySink(!fallback, CurrencySink.EntrypointTable.CURRENCY_NARROW); sink.narrowSymbol = result; rb.getAllItemsWithFallbackNoFail("Currencies%narrow/" + isoCode, sink); narrowSymbolCache = result; } return result; } String[] fetchPluralsData(String isoCode) { String[] result = pluralsDataCache; if (result == null || !result[0].equals(isoCode)) { result = new String[1 + StandardPlural.COUNT]; result[0] = isoCode; CurrencySink sink = new CurrencySink(!fallback, CurrencySink.EntrypointTable.CURRENCY_PLURALS); sink.pluralsData = result; rb.getAllItemsWithFallbackNoFail("CurrencyPlurals/" + isoCode, sink); pluralsDataCache = result; } return result; } ParsingData fetchParsingData() { ParsingData result = parsingDataCache.get(); if (result == null) { result = new ParsingData(); CurrencySink sink = new CurrencySink(!fallback, CurrencySink.EntrypointTable.TOP); sink.parsingData = result; rb.getAllItemsWithFallback("", sink); parsingDataCache = new SoftReference(result); } return result; } Map fetchUnitPatterns() { Map result = unitPatternsCache; if (result == null) { result = new HashMap(); CurrencySink sink = new CurrencySink(!fallback, CurrencySink.EntrypointTable.CURRENCY_UNIT_PATTERNS); sink.unitPatterns = result; rb.getAllItemsWithFallback("CurrencyUnitPatterns", sink); unitPatternsCache = result; } return result; } CurrencySpacingInfo fetchSpacingInfo() { CurrencySpacingInfo result = spacingInfoCache; if (result == null) { result = new CurrencySpacingInfo(); CurrencySink sink = new CurrencySink(!fallback, CurrencySink.EntrypointTable.CURRENCY_SPACING); sink.spacingInfo = result; rb.getAllItemsWithFallback("currencySpacing", sink); spacingInfoCache = result; } return result; } //////////////////////////////////////////// /// END DATA FRONTEND -- START DATA SINK /// //////////////////////////////////////////// private static final class CurrencySink extends UResource.Sink { final boolean noRoot; final EntrypointTable entrypointTable; // The fields to be populated on this run of the data sink will be non-null. FormattingData formattingData = null; String[] pluralsData = null; ParsingData parsingData = null; Map unitPatterns = null; CurrencySpacingInfo spacingInfo = null; NarrowSymbol narrowSymbol = null; enum EntrypointTable { // For Parsing: TOP, // For Formatting: CURRENCIES, CURRENCY_PLURALS, CURRENCY_NARROW, CURRENCY_SPACING, CURRENCY_UNIT_PATTERNS } CurrencySink(boolean noRoot, EntrypointTable entrypointTable) { this.noRoot = noRoot; this.entrypointTable = entrypointTable; } /** * The entrypoint method delegates to helper methods for each of the types of tables * found in the currency data. */ @Override public void put(UResource.Key key, UResource.Value value, boolean isRoot) { if (noRoot && isRoot) { // Don't consume the root bundle return; } switch (entrypointTable) { case TOP: consumeTopTable(key, value); break; case CURRENCIES: consumeCurrenciesEntry(key, value); break; case CURRENCY_PLURALS: consumeCurrencyPluralsEntry(key, value); break; case CURRENCY_NARROW: consumeCurrenciesNarrowEntry(key, value); break; case CURRENCY_SPACING: consumeCurrencySpacingTable(key, value); break; case CURRENCY_UNIT_PATTERNS: consumeCurrencyUnitPatternsTable(key, value); break; } } private void consumeTopTable(UResource.Key key, UResource.Value value) { UResource.Table table = value.getTable(); for (int i = 0; table.getKeyAndValue(i, key, value); i++) { if (key.contentEquals("Currencies")) { consumeCurrenciesTable(key, value); } else if (key.contentEquals("Currencies%variant")) { consumeCurrenciesVariantTable(key, value); } else if (key.contentEquals("CurrencyPlurals")) { consumeCurrencyPluralsTable(key, value); } } } /* * Currencies{ * ... * USD{ * "US$", => symbol * "US Dollar", => display name * } * ... * ESP{ * "₧", => symbol * "pesseta espanyola", => display name * { * "¤ #,##0.00", => currency-specific pattern * ",", => currency-specific grouping separator * ".", => currency-specific decimal separator * } * } * ... * } */ void consumeCurrenciesTable(UResource.Key key, UResource.Value value) { // The full Currencies table is consumed for parsing only. assert parsingData != null; UResource.Table table = value.getTable(); for (int i = 0; table.getKeyAndValue(i, key, value); i++) { String isoCode = key.toString(); if (value.getType() != UResourceBundle.ARRAY) { throw new ICUException("Unexpected data type in Currencies table for " + isoCode); } UResource.Array array = value.getArray(); parsingData.symbolToIsoCode.put(isoCode, isoCode); // Add the ISO code itself as a symbol array.getValue(0, value); parsingData.symbolToIsoCode.put(value.getString(), isoCode); array.getValue(1, value); parsingData.nameToIsoCode.put(value.getString(), isoCode); } } void consumeCurrenciesEntry(UResource.Key key, UResource.Value value) { assert formattingData != null; String isoCode = key.toString(); if (value.getType() != UResourceBundle.ARRAY) { throw new ICUException("Unexpected data type in Currencies table for " + isoCode); } UResource.Array array = value.getArray(); if (formattingData.symbol == null) { array.getValue(0, value); formattingData.symbol = value.getString(); } if (formattingData.displayName == null) { array.getValue(1, value); formattingData.displayName = value.getString(); } // If present, the third element is the currency format info. // TODO: Write unit test to ensure that this data is being used by number formatting. if (array.getSize() > 2 && formattingData.formatInfo == null) { array.getValue(2, value); UResource.Array formatArray = value.getArray(); formatArray.getValue(0, value); String formatPattern = value.getString(); formatArray.getValue(1, value); String decimalSeparator = value.getString(); formatArray.getValue(2, value); String groupingSeparator = value.getString(); formattingData.formatInfo = new CurrencyFormatInfo( isoCode, formatPattern, decimalSeparator, groupingSeparator); } } /* * Currencies%narrow{ * AOA{"Kz"} * ARS{"$"} * ... * } */ void consumeCurrenciesNarrowEntry(UResource.Key key, UResource.Value value) { assert narrowSymbol != null; // No extra structure to traverse. if (narrowSymbol.narrowSymbol == null) { narrowSymbol.narrowSymbol = value.getString(); } } /* * Currencies%variant{ * TRY{"TL"} * } */ void consumeCurrenciesVariantTable(UResource.Key key, UResource.Value value) { // Note: This data is used for parsing but not formatting. assert parsingData != null; UResource.Table table = value.getTable(); for (int i = 0; table.getKeyAndValue(i, key, value); i++) { String isoCode = key.toString(); parsingData.symbolToIsoCode.put(value.getString(), isoCode); } } /* * CurrencyPlurals{ * BYB{ * one{"Belarusian new rouble (1994–1999)"} * other{"Belarusian new roubles (1994–1999)"} * } * ... * } */ void consumeCurrencyPluralsTable(UResource.Key key, UResource.Value value) { // The full CurrencyPlurals table is consumed for parsing only. assert parsingData != null; UResource.Table table = value.getTable(); for (int i = 0; table.getKeyAndValue(i, key, value); i++) { String isoCode = key.toString(); UResource.Table pluralsTable = value.getTable(); for (int j=0; pluralsTable.getKeyAndValue(j, key, value); j++) { StandardPlural plural = StandardPlural.orNullFromString(key.toString()); if (plural == null) { throw new ICUException("Could not make StandardPlural from keyword " + key); } parsingData.nameToIsoCode.put(value.getString(), isoCode); } } } void consumeCurrencyPluralsEntry(UResource.Key key, UResource.Value value) { assert pluralsData != null; UResource.Table pluralsTable = value.getTable(); for (int j=0; pluralsTable.getKeyAndValue(j, key, value); j++) { StandardPlural plural = StandardPlural.orNullFromString(key.toString()); if (plural == null) { throw new ICUException("Could not make StandardPlural from keyword " + key); } if (pluralsData[1 + plural.ordinal()] == null) { pluralsData[1 + plural.ordinal()] = value.getString(); } } } /* * currencySpacing{ * afterCurrency{ * currencyMatch{"[:^S:]"} * insertBetween{" "} * surroundingMatch{"[:digit:]"} * } * beforeCurrency{ * currencyMatch{"[:^S:]"} * insertBetween{" "} * surroundingMatch{"[:digit:]"} * } * } */ void consumeCurrencySpacingTable(UResource.Key key, UResource.Value value) { assert spacingInfo != null; UResource.Table spacingTypesTable = value.getTable(); for (int i = 0; spacingTypesTable.getKeyAndValue(i, key, value); ++i) { CurrencySpacingInfo.SpacingType type; if (key.contentEquals("beforeCurrency")) { type = CurrencySpacingInfo.SpacingType.BEFORE; spacingInfo.hasBeforeCurrency = true; } else if (key.contentEquals("afterCurrency")) { type = CurrencySpacingInfo.SpacingType.AFTER; spacingInfo.hasAfterCurrency = true; } else { continue; } UResource.Table patternsTable = value.getTable(); for (int j = 0; patternsTable.getKeyAndValue(j, key, value); ++j) { CurrencySpacingInfo.SpacingPattern pattern; if (key.contentEquals("currencyMatch")) { pattern = CurrencySpacingInfo.SpacingPattern.CURRENCY_MATCH; } else if (key.contentEquals("surroundingMatch")) { pattern = CurrencySpacingInfo.SpacingPattern.SURROUNDING_MATCH; } else if (key.contentEquals("insertBetween")) { pattern = CurrencySpacingInfo.SpacingPattern.INSERT_BETWEEN; } else { continue; } spacingInfo.setSymbolIfNull(type, pattern, value.getString()); } } } /* * CurrencyUnitPatterns{ * other{"{0} {1}"} * ... * } */ void consumeCurrencyUnitPatternsTable(UResource.Key key, UResource.Value value) { assert unitPatterns != null; UResource.Table table = value.getTable(); for (int i = 0; table.getKeyAndValue(i, key, value); i++) { String pluralKeyword = key.toString(); if (unitPatterns.get(pluralKeyword) == null) { unitPatterns.put(pluralKeyword, value.getString()); } } } } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy