From c81d1e94a0d3be9386793736b703bc617476828a Mon Sep 17 00:00:00 2001 From: Shane Carr Date: Fri, 15 Feb 2019 17:32:50 -0800 Subject: [PATCH] ICU-13591 Refactoring ICU4C MeasureFormat to call NumberFormatter directly. - Removes redundant data loading in MeasureFormat --- icu4c/source/i18n/currfmt.cpp | 11 +- icu4c/source/i18n/currfmt.h | 4 - icu4c/source/i18n/measfmt.cpp | 491 +++--------------- icu4c/source/i18n/number_longnames.cpp | 47 +- icu4c/source/i18n/number_longnames.h | 6 + icu4c/source/i18n/quantityformatter.h | 1 + icu4c/source/i18n/tmutfmt.cpp | 2 +- icu4c/source/i18n/unicode/decimfmt.h | 3 + icu4c/source/i18n/unicode/measfmt.h | 30 +- icu4c/source/test/intltest/numbertest_api.cpp | 9 + .../test/number/NumberFormatterApiTest.java | 9 + 11 files changed, 143 insertions(+), 470 deletions(-) diff --git a/icu4c/source/i18n/currfmt.cpp b/icu4c/source/i18n/currfmt.cpp index 06bdad042aa..8f20f783d25 100644 --- a/icu4c/source/i18n/currfmt.cpp +++ b/icu4c/source/i18n/currfmt.cpp @@ -21,19 +21,16 @@ U_NAMESPACE_BEGIN CurrencyFormat::CurrencyFormat(const Locale& locale, UErrorCode& ec) : - MeasureFormat(locale, UMEASFMT_WIDTH_WIDE, ec), fmt(NULL) + MeasureFormat(locale, UMEASFMT_WIDTH_WIDE, ec) { - fmt = NumberFormat::createCurrencyInstance(locale, ec); } CurrencyFormat::CurrencyFormat(const CurrencyFormat& other) : - MeasureFormat(other), fmt(NULL) + MeasureFormat(other) { - fmt = (NumberFormat*) other.fmt->clone(); } CurrencyFormat::~CurrencyFormat() { - delete fmt; } Format* CurrencyFormat::clone() const { @@ -45,14 +42,14 @@ UnicodeString& CurrencyFormat::format(const Formattable& obj, FieldPosition& pos, UErrorCode& ec) const { - return fmt->format(obj, appendTo, pos, ec); + return getCurrencyFormatInternal().format(obj, appendTo, pos, ec); } void CurrencyFormat::parseObject(const UnicodeString& source, Formattable& result, ParsePosition& pos) const { - CurrencyAmount* currAmt = fmt->parseCurrency(source, pos); + CurrencyAmount* currAmt = getCurrencyFormatInternal().parseCurrency(source, pos); if (currAmt != NULL) { result.adoptObject(currAmt); } diff --git a/icu4c/source/i18n/currfmt.h b/icu4c/source/i18n/currfmt.h index 97d44cbb1d1..cc9bb3c1bac 100644 --- a/icu4c/source/i18n/currfmt.h +++ b/icu4c/source/i18n/currfmt.h @@ -86,10 +86,6 @@ class CurrencyFormat : public MeasureFormat { * Returns the class ID for this class. */ static UClassID U_EXPORT2 getStaticClassID(); - - private: - - NumberFormat* fmt; }; U_NAMESPACE_END diff --git a/icu4c/source/i18n/measfmt.cpp b/icu4c/source/i18n/measfmt.cpp index 477341b4469..afa7f7a0726 100644 --- a/icu4c/source/i18n/measfmt.cpp +++ b/icu4c/source/i18n/measfmt.cpp @@ -36,6 +36,8 @@ #include "unicode/putil.h" #include "unicode/smpdtfmt.h" #include "uassert.h" +#include "unicode/numberformatter.h" +#include "number_longnames.h" #include "sharednumberformat.h" #include "sharedpluralrules.h" @@ -45,8 +47,6 @@ U_NAMESPACE_BEGIN -static constexpr int32_t PER_UNIT_INDEX = StandardPlural::COUNT; -static constexpr int32_t PATTERN_COUNT = PER_UNIT_INDEX + 1; static constexpr int32_t MEAS_UNIT_COUNT = 146; // see assertion in MeasureFormatCacheData constructor static constexpr int32_t WIDTH_INDEX_COUNT = UMEASFMT_WIDTH_NARROW + 1; @@ -91,6 +91,19 @@ static UMeasureFormatWidth getRegularWidth(UMeasureFormatWidth width) { return width; } +static UNumberUnitWidth getUnitWidth(UMeasureFormatWidth width) { + switch (width) { + case UMEASFMT_WIDTH_WIDE: + return UNUM_UNIT_WIDTH_FULL_NAME; + case UMEASFMT_WIDTH_NARROW: + case UMEASFMT_WIDTH_NUMERIC: + return UNUM_UNIT_WIDTH_NARROW; + case UMEASFMT_WIDTH_SHORT: + default: + return UNUM_UNIT_WIDTH_SHORT; + } +} + /** * Instances contain all MeasureFormat specific data for a particular locale. * This data is cached. It is never copied, but is shared via shared pointers. @@ -110,20 +123,10 @@ public: * - UMEASFMT_WIDTH_WIDE/SHORT/NARROW: sideways alias for missing data */ UMeasureFormatWidth widthFallback[WIDTH_INDEX_COUNT]; - /** Measure unit -> format width -> array of patterns ("{0} meters") (plurals + PER_UNIT_INDEX) */ - SimpleFormatter* patterns[MEAS_UNIT_COUNT][WIDTH_INDEX_COUNT][PATTERN_COUNT]; - const UChar* dnams[MEAS_UNIT_COUNT][WIDTH_INDEX_COUNT]; - SimpleFormatter perFormatters[WIDTH_INDEX_COUNT]; MeasureFormatCacheData(); virtual ~MeasureFormatCacheData(); - UBool hasPerFormatter(int32_t width) const { - // TODO: Create a more obvious way to test if the per-formatter has been set? - // Use pointers, check for NULL? Or add an isValid() method? - return perFormatters[width].getArgumentLimit() == 2; - } - void adoptCurrencyFormat(int32_t widthIndex, NumberFormat *nfToAdopt) { delete currencyFormats[widthIndex]; currencyFormats[widthIndex] = nfToAdopt; @@ -163,8 +166,6 @@ MeasureFormatCacheData::MeasureFormatCacheData() for (int32_t i = 0; i < WIDTH_INDEX_COUNT; ++i) { widthFallback[i] = UMEASFMT_WIDTH_COUNT; } - memset(&patterns[0][0][0], 0, sizeof(patterns)); - memset(&dnams[0][0], 0, sizeof(dnams)); memset(currencyFormats, 0, sizeof(currencyFormats)); } @@ -172,13 +173,6 @@ MeasureFormatCacheData::~MeasureFormatCacheData() { for (int32_t i = 0; i < UPRV_LENGTHOF(currencyFormats); ++i) { delete currencyFormats[i]; } - for (int32_t i = 0; i < MEAS_UNIT_COUNT; ++i) { - for (int32_t j = 0; j < WIDTH_INDEX_COUNT; ++j) { - for (int32_t k = 0; k < PATTERN_COUNT; ++k) { - delete patterns[i][j][k]; - } - } - } // Note: the contents of 'dnams' are pointers into the resource bundle delete integerFormat; delete numericDateFormatters; @@ -201,250 +195,6 @@ static UBool getString( return TRUE; } -namespace { - -static const UChar g_LOCALE_units[] = { - 0x2F, 0x4C, 0x4F, 0x43, 0x41, 0x4C, 0x45, 0x2F, - 0x75, 0x6E, 0x69, 0x74, 0x73 -}; -static const UChar gShort[] = { 0x53, 0x68, 0x6F, 0x72, 0x74 }; -static const UChar gNarrow[] = { 0x4E, 0x61, 0x72, 0x72, 0x6F, 0x77 }; - -/** - * Sink for enumerating all of the measurement unit display names. - * Contains inner sink classes, each one corresponding to a type of resource table. - * The outer sink handles the top-level units, unitsNarrow, and unitsShort tables. - * - * More specific bundles (en_GB) are enumerated before their parents (en_001, en, root): - * Only store a value if it is still missing, that is, it has not been overridden. - * - * C++: Each inner sink class has a reference to the main outer sink. - * Java: Use non-static inner classes instead. - */ -struct UnitDataSink : public ResourceSink { - - // Output data. - MeasureFormatCacheData &cacheData; - - // Path to current data. - UMeasureFormatWidth width; - const char *type; - int32_t unitIndex; - - UnitDataSink(MeasureFormatCacheData &outputData) - : cacheData(outputData), - width(UMEASFMT_WIDTH_COUNT), type(NULL), unitIndex(0) {} - ~UnitDataSink(); - - void setFormatterIfAbsent(int32_t index, const ResourceValue &value, - int32_t minPlaceholders, UErrorCode &errorCode) { - U_ASSERT(unitIndex < MEAS_UNIT_COUNT); - U_ASSERT(width < WIDTH_INDEX_COUNT); - U_ASSERT(index < PATTERN_COUNT); - SimpleFormatter **patterns = &cacheData.patterns[unitIndex][width][0]; - if (U_SUCCESS(errorCode) && patterns[index] == NULL) { - if (minPlaceholders >= 0) { - patterns[index] = new SimpleFormatter( - value.getUnicodeString(errorCode), minPlaceholders, 1, errorCode); - } - if (U_SUCCESS(errorCode) && patterns[index] == NULL) { - errorCode = U_MEMORY_ALLOCATION_ERROR; - } - } - } - - void setDnamIfAbsent(const ResourceValue &value, UErrorCode& errorCode) { - U_ASSERT(unitIndex < MEAS_UNIT_COUNT); - U_ASSERT(width < WIDTH_INDEX_COUNT); - if (cacheData.dnams[unitIndex][width] == NULL) { - int32_t length; - cacheData.dnams[unitIndex][width] = value.getString(length, errorCode); - } - } - - /** - * Consume a display pattern. For example, - * unitsShort/duration/hour contains other{"{0} hrs"}. - */ - void consumePattern(const char *key, const ResourceValue &value, UErrorCode &errorCode) { - if (U_FAILURE(errorCode)) { return; } - if (uprv_strcmp(key, "dnam") == 0) { - // The display name for the unit in the current width. - setDnamIfAbsent(value, errorCode); - } else if (uprv_strcmp(key, "per") == 0) { - // For example, "{0}/h". - setFormatterIfAbsent(PER_UNIT_INDEX, value, 1, errorCode); - } else { - // The key must be one of the plural form strings. For example: - // one{"{0} hr"} - // other{"{0} hrs"} - setFormatterIfAbsent(StandardPlural::indexFromString(key, errorCode), value, 0, - errorCode); - } - } - - /** - * Consume a table of per-unit tables. For example, - * unitsShort/duration contains tables for duration-unit subtypes day & hour. - */ - void consumeSubtypeTable(const char *key, ResourceValue &value, UErrorCode &errorCode) { - if (U_FAILURE(errorCode)) { return; } - unitIndex = MeasureUnit::internalGetIndexForTypeAndSubtype(type, key); - if (unitIndex < 0) { - // TODO: How to handle unexpected data? - // See http://bugs.icu-project.org/trac/ticket/12597 - return; - } - - // We no longer handle units like "coordinate" here (which do not have plural variants) - if (value.getType() == URES_TABLE) { - // Units that have plural variants - ResourceTable patternTableTable = value.getTable(errorCode); - if (U_FAILURE(errorCode)) { return; } - for (int i = 0; patternTableTable.getKeyAndValue(i, key, value); ++i) { - consumePattern(key, value, errorCode); - } - } else { - // TODO: How to handle unexpected data? - // See http://bugs.icu-project.org/trac/ticket/12597 - return; - } - } - - /** - * Consume compound x-per-y display pattern. For example, - * unitsShort/compound/per may be "{0}/{1}". - */ - void consumeCompoundPattern(const char *key, const ResourceValue &value, UErrorCode &errorCode) { - if (U_SUCCESS(errorCode) && uprv_strcmp(key, "per") == 0) { - cacheData.perFormatters[width]. - applyPatternMinMaxArguments(value.getUnicodeString(errorCode), 2, 2, errorCode); - } - } - - /** - * Consume a table of unit type tables. For example, - * unitsShort contains tables for area & duration. - * It also contains a table for the compound/per pattern. - */ - void consumeUnitTypesTable(const char *key, ResourceValue &value, UErrorCode &errorCode) { - if (U_FAILURE(errorCode)) { return; } - if (uprv_strcmp(key, "currency") == 0) { - // Skip. - } else if (uprv_strcmp(key, "compound") == 0) { - if (!cacheData.hasPerFormatter(width)) { - ResourceTable compoundTable = value.getTable(errorCode); - if (U_FAILURE(errorCode)) { return; } - for (int i = 0; compoundTable.getKeyAndValue(i, key, value); ++i) { - consumeCompoundPattern(key, value, errorCode); - } - } - } else if (uprv_strcmp(key, "coordinate") == 0) { - // special handling but we need to determine what that is - } else { - type = key; - ResourceTable subtypeTable = value.getTable(errorCode); - if (U_FAILURE(errorCode)) { return; } - for (int i = 0; subtypeTable.getKeyAndValue(i, key, value); ++i) { - consumeSubtypeTable(key, value, errorCode); - } - } - } - - void consumeAlias(const char *key, const ResourceValue &value, UErrorCode &errorCode) { - // Handle aliases like - // units:alias{"/LOCALE/unitsShort"} - // which should only occur in the root bundle. - UMeasureFormatWidth sourceWidth = widthFromKey(key); - if (sourceWidth == UMEASFMT_WIDTH_COUNT) { - // Alias from something we don't care about. - return; - } - UMeasureFormatWidth targetWidth = widthFromAlias(value, errorCode); - if (targetWidth == UMEASFMT_WIDTH_COUNT) { - // We do not recognize what to fall back to. - errorCode = U_INVALID_FORMAT_ERROR; - return; - } - // Check that we do not fall back to another fallback. - if (cacheData.widthFallback[targetWidth] != UMEASFMT_WIDTH_COUNT) { - errorCode = U_INVALID_FORMAT_ERROR; - return; - } - cacheData.widthFallback[sourceWidth] = targetWidth; - } - - void consumeTable(const char *key, ResourceValue &value, UErrorCode &errorCode) { - if (U_SUCCESS(errorCode) && (width = widthFromKey(key)) != UMEASFMT_WIDTH_COUNT) { - ResourceTable unitTypesTable = value.getTable(errorCode); - if (U_FAILURE(errorCode)) { return; } - for (int i = 0; unitTypesTable.getKeyAndValue(i, key, value); ++i) { - consumeUnitTypesTable(key, value, errorCode); - } - } - } - - static UMeasureFormatWidth widthFromKey(const char *key) { - if (uprv_strncmp(key, "units", 5) == 0) { - key += 5; - if (*key == 0) { - return UMEASFMT_WIDTH_WIDE; - } else if (uprv_strcmp(key, "Short") == 0) { - return UMEASFMT_WIDTH_SHORT; - } else if (uprv_strcmp(key, "Narrow") == 0) { - return UMEASFMT_WIDTH_NARROW; - } - } - return UMEASFMT_WIDTH_COUNT; - } - - static UMeasureFormatWidth widthFromAlias(const ResourceValue &value, UErrorCode &errorCode) { - int32_t length; - const UChar *s = value.getAliasString(length, errorCode); - // For example: "/LOCALE/unitsShort" - if (U_SUCCESS(errorCode) && length >= 13 && u_memcmp(s, g_LOCALE_units, 13) == 0) { - s += 13; - length -= 13; - if (*s == 0) { - return UMEASFMT_WIDTH_WIDE; - } else if (u_strCompare(s, length, gShort, 5, FALSE) == 0) { - return UMEASFMT_WIDTH_SHORT; - } else if (u_strCompare(s, length, gNarrow, 6, FALSE) == 0) { - return UMEASFMT_WIDTH_NARROW; - } - } - return UMEASFMT_WIDTH_COUNT; - } - - virtual void put(const char *key, ResourceValue &value, UBool /*noFallback*/, - UErrorCode &errorCode) { - // Main entry point to sink - ResourceTable widthsTable = value.getTable(errorCode); - if (U_FAILURE(errorCode)) { return; } - for (int i = 0; widthsTable.getKeyAndValue(i, key, value); ++i) { - if (value.getType() == URES_ALIAS) { - consumeAlias(key, value, errorCode); - } else { - consumeTable(key, value, errorCode); - } - } - } -}; - -// Virtual destructors must be defined out of line. -UnitDataSink::~UnitDataSink() {} - -} // namespace - -static UBool loadMeasureUnitData( - const UResourceBundle *resource, - MeasureFormatCacheData &cacheData, - UErrorCode &status) { - UnitDataSink sink(cacheData); - ures_getAllItemsWithFallback(resource, "", sink, status); - return U_SUCCESS(status); -} - static UnicodeString loadNumericDateFormatterPattern( const UResourceBundle *resource, const char *pattern, @@ -507,12 +257,6 @@ const MeasureFormatCacheData *LocaleCacheKey::createObje if (U_FAILURE(status)) { return NULL; } - if (!loadMeasureUnitData( - unitsBundle.getAlias(), - *result, - status)) { - return NULL; - } result->adoptNumericDateFormatters(loadNumericDateFormatters( unitsBundle.getAlias(), status)); if (U_FAILURE(status)) { @@ -764,29 +508,21 @@ UnicodeString &MeasureFormat::formatMeasurePerUnit( if (U_FAILURE(status)) { return appendTo; } - bool isResolved = false; - MeasureUnit resolvedUnit = - MeasureUnit::resolveUnitPerUnit(measure.getUnit(), perUnit, &isResolved); - if (isResolved) { - Measure newMeasure(measure.getNumber(), new MeasureUnit(resolvedUnit), status); - return formatMeasure( - newMeasure, **numberFormat, appendTo, pos, status); - } - FieldPosition fpos(pos.getField()); - UnicodeString result; - int32_t offset = withPerUnitAndAppend( - formatMeasure( - measure, **numberFormat, result, fpos, status), - perUnit, - appendTo, - status); - if (U_FAILURE(status)) { + auto* df = dynamic_cast(&getNumberFormatInternal()); + if (df == nullptr) { + // Don't know how to handle other types of NumberFormat + status = U_UNSUPPORTED_ERROR; return appendTo; } - if (fpos.getBeginIndex() != 0 || fpos.getEndIndex() != 0) { - pos.setBeginIndex(fpos.getBeginIndex() + offset); - pos.setEndIndex(fpos.getEndIndex() + offset); + number::FormattedNumber result; + if (auto* lnf = df->toNumberFormatter(status)) { + result = lnf->unit(measure.getUnit()) + .perUnit(perUnit) + .unitWidth(getUnitWidth(fWidth)) + .formatDouble(measure.getNumber().getDouble(status), status); } + DecimalFormat::fieldPositionHelper(result, pos, appendTo.length(), status); + appendTo.append(result.toTempString(status)); return appendTo; } @@ -838,22 +574,12 @@ UnicodeString &MeasureFormat::formatMeasures( return appendTo; } -UnicodeString MeasureFormat::getUnitDisplayName(const MeasureUnit& unit, UErrorCode& /*status*/) const { - UMeasureFormatWidth width = getRegularWidth(fWidth); - const UChar* const* styleToDnam = cache->dnams[unit.getIndex()]; - const UChar* dnam = styleToDnam[width]; - if (dnam == NULL) { - int32_t fallbackWidth = cache->widthFallback[width]; - dnam = styleToDnam[fallbackWidth]; - } - - UnicodeString result; - if (dnam == NULL) { - result.setToBogus(); - } else { - result.setTo(dnam, -1); - } - return result; +UnicodeString MeasureFormat::getUnitDisplayName(const MeasureUnit& unit, UErrorCode& status) const { + return number::impl::LongNameHandler::getUnitDisplayName( + getLocale(status), + unit, + getUnitWidth(fWidth), + status); } void MeasureFormat::initMeasureFormat( @@ -882,6 +608,7 @@ void MeasureFormat::initMeasureFormat( SharedObject::copyPtr(pr, pluralRules); pr->removeRef(); if (nf.isNull()) { + // TODO: Clean this up const SharedNumberFormat *shared = NumberFormat::createSharedInstance( locale, UNUM_DECIMAL, status); if (U_FAILURE(status)) { @@ -926,10 +653,14 @@ UBool MeasureFormat::setMeasureFormatLocale(const Locale &locale, UErrorCode &st return U_SUCCESS(status); } -const NumberFormat &MeasureFormat::getNumberFormat() const { +const NumberFormat &MeasureFormat::getNumberFormatInternal() const { return **numberFormat; } +const NumberFormat &MeasureFormat::getCurrencyFormatInternal() const { + return *cache->getCurrencyFormat(UMEASFMT_WIDTH_NARROW); +} + const PluralRules &MeasureFormat::getPluralRules() const { return **pluralRules; } @@ -962,11 +693,21 @@ UnicodeString &MeasureFormat::formatMeasure( pos, status); } - UnicodeString formattedNumber; - StandardPlural::Form pluralForm = QuantityFormatter::selectPlural( - amtNumber, nf, **pluralRules, formattedNumber, pos, status); - const SimpleFormatter *formatter = getPluralFormatter(amtUnit, fWidth, pluralForm, status); - return QuantityFormatter::format(*formatter, formattedNumber, appendTo, pos, status); + auto* df = dynamic_cast(&nf); + if (df == nullptr) { + // Don't know how to handle other types of NumberFormat + status = U_UNSUPPORTED_ERROR; + return appendTo; + } + number::FormattedNumber result; + if (auto* lnf = df->toNumberFormatter(status)) { + result = lnf->unit(amtUnit) + .unitWidth(getUnitWidth(fWidth)) + .formatDouble(amtNumber.getDouble(status), status); + } + DecimalFormat::fieldPositionHelper(result, pos, appendTo.length(), status); + appendTo.append(result.toTempString(status)); + return appendTo; } // Formats hours-minutes-seconds as 5:37:23 or similar. @@ -1101,108 +842,6 @@ UnicodeString &MeasureFormat::formatNumeric( return appendTo; } -const SimpleFormatter *MeasureFormat::getFormatterOrNull( - const MeasureUnit &unit, UMeasureFormatWidth width, int32_t index) const { - width = getRegularWidth(width); - SimpleFormatter *const (*unitPatterns)[PATTERN_COUNT] = &cache->patterns[unit.getIndex()][0]; - if (unitPatterns[width][index] != NULL) { - return unitPatterns[width][index]; - } - int32_t fallbackWidth = cache->widthFallback[width]; - if (fallbackWidth != UMEASFMT_WIDTH_COUNT && unitPatterns[fallbackWidth][index] != NULL) { - return unitPatterns[fallbackWidth][index]; - } - return NULL; -} - -const SimpleFormatter *MeasureFormat::getFormatter( - const MeasureUnit &unit, UMeasureFormatWidth width, int32_t index, - UErrorCode &errorCode) const { - if (U_FAILURE(errorCode)) { - return NULL; - } - const SimpleFormatter *pattern = getFormatterOrNull(unit, width, index); - if (pattern == NULL) { - errorCode = U_MISSING_RESOURCE_ERROR; - } - return pattern; -} - -const SimpleFormatter *MeasureFormat::getPluralFormatter( - const MeasureUnit &unit, UMeasureFormatWidth width, int32_t index, - UErrorCode &errorCode) const { - if (U_FAILURE(errorCode)) { - return NULL; - } - if (index != StandardPlural::OTHER) { - const SimpleFormatter *pattern = getFormatterOrNull(unit, width, index); - if (pattern != NULL) { - return pattern; - } - } - return getFormatter(unit, width, StandardPlural::OTHER, errorCode); -} - -const SimpleFormatter *MeasureFormat::getPerFormatter( - UMeasureFormatWidth width, - UErrorCode &status) const { - if (U_FAILURE(status)) { - return NULL; - } - width = getRegularWidth(width); - const SimpleFormatter * perFormatters = cache->perFormatters; - if (perFormatters[width].getArgumentLimit() == 2) { - return &perFormatters[width]; - } - int32_t fallbackWidth = cache->widthFallback[width]; - if (fallbackWidth != UMEASFMT_WIDTH_COUNT && - perFormatters[fallbackWidth].getArgumentLimit() == 2) { - return &perFormatters[fallbackWidth]; - } - status = U_MISSING_RESOURCE_ERROR; - return NULL; -} - -int32_t MeasureFormat::withPerUnitAndAppend( - const UnicodeString &formatted, - const MeasureUnit &perUnit, - UnicodeString &appendTo, - UErrorCode &status) const { - int32_t offset = -1; - if (U_FAILURE(status)) { - return offset; - } - const SimpleFormatter *perUnitFormatter = getFormatterOrNull(perUnit, fWidth, PER_UNIT_INDEX); - if (perUnitFormatter != NULL) { - const UnicodeString *params[] = {&formatted}; - perUnitFormatter->formatAndAppend( - params, - UPRV_LENGTHOF(params), - appendTo, - &offset, - 1, - status); - return offset; - } - const SimpleFormatter *perFormatter = getPerFormatter(fWidth, status); - const SimpleFormatter *pattern = - getPluralFormatter(perUnit, fWidth, StandardPlural::ONE, status); - if (U_FAILURE(status)) { - return offset; - } - UnicodeString perUnitString = pattern->getTextWithNoArguments(); - perUnitString.trim(); - const UnicodeString *params[] = {&formatted, &perUnitString}; - perFormatter->formatAndAppend( - params, - UPRV_LENGTHOF(params), - appendTo, - &offset, - 1, - status); - return offset; -} - UnicodeString &MeasureFormat::formatMeasuresSlowTrack( const Measure *measures, int32_t measureCount, @@ -1214,7 +853,7 @@ UnicodeString &MeasureFormat::formatMeasuresSlowTrack( } FieldPosition dontCare(FieldPosition::DONT_CARE); FieldPosition fpos(pos.getField()); - UnicodeString *results = new UnicodeString[measureCount]; + LocalArray results(new UnicodeString[measureCount], status); int32_t fieldPositionFoundIndex = -1; for (int32_t i = 0; i < measureCount; ++i) { const NumberFormat *nf = cache->getIntegerFormat(); @@ -1224,7 +863,6 @@ UnicodeString &MeasureFormat::formatMeasuresSlowTrack( if (fieldPositionFoundIndex == -1) { formatMeasure(measures[i], *nf, results[i], fpos, status); if (U_FAILURE(status)) { - delete [] results; return appendTo; } if (fpos.getBeginIndex() != 0 || fpos.getEndIndex() != 0) { @@ -1236,40 +874,35 @@ UnicodeString &MeasureFormat::formatMeasuresSlowTrack( } int32_t offset; listFormatter->format( - results, + results.getAlias(), measureCount, appendTo, fieldPositionFoundIndex, offset, status); if (U_FAILURE(status)) { - delete [] results; return appendTo; } + // Fix up FieldPosition indexes if our field is found. if (offset != -1) { pos.setBeginIndex(fpos.getBeginIndex() + offset); pos.setEndIndex(fpos.getEndIndex() + offset); } - delete [] results; return appendTo; } MeasureFormat* U_EXPORT2 MeasureFormat::createCurrencyFormat(const Locale& locale, UErrorCode& ec) { - CurrencyFormat* fmt = NULL; - if (U_SUCCESS(ec)) { - fmt = new CurrencyFormat(locale, ec); - if (U_FAILURE(ec)) { - delete fmt; - fmt = NULL; - } + if (U_FAILURE(ec)) { + return nullptr; } - return fmt; + LocalPointer fmt(new CurrencyFormat(locale, ec), ec); + return fmt.orphan(); } MeasureFormat* U_EXPORT2 MeasureFormat::createCurrencyFormat(UErrorCode& ec) { if (U_FAILURE(ec)) { - return NULL; + return nullptr; } return MeasureFormat::createCurrencyFormat(Locale::getDefault(), ec); } diff --git a/icu4c/source/i18n/number_longnames.cpp b/icu4c/source/i18n/number_longnames.cpp index fc4517916ba..cfea339ef4d 100644 --- a/icu4c/source/i18n/number_longnames.cpp +++ b/icu4c/source/i18n/number_longnames.cpp @@ -14,6 +14,7 @@ #include "number_microprops.h" #include #include "cstring.h" +#include "util.h" using namespace icu; using namespace icu::number; @@ -91,6 +92,17 @@ void getMeasureData(const Locale &locale, const MeasureUnit &unit, const UNumber PluralTableSink sink(outArray); LocalUResourceBundlePointer unitsBundle(ures_open(U_ICUDATA_UNIT, locale.getName(), &status)); if (U_FAILURE(status)) { return; } + + // Map duration-year-person, duration-week-person, etc. to duration-year, duration-week, ... + // TODO(ICU-20400): Get duration-*-person data properly with aliases. + StringPiece subtypeForResource; + int32_t subtypeLen = static_cast(uprv_strlen(unit.getSubtype())); + if (subtypeLen > 7 && uprv_strcmp(unit.getSubtype() + subtypeLen - 7, "-person") == 0) { + subtypeForResource = {unit.getSubtype(), subtypeLen - 7}; + } else { + subtypeForResource = unit.getSubtype(); + } + CharString key; key.append("units", status); if (width == UNUM_UNIT_WIDTH_NARROW) { @@ -101,16 +113,24 @@ void getMeasureData(const Locale &locale, const MeasureUnit &unit, const UNumber key.append("/", status); key.append(unit.getType(), status); key.append("/", status); + key.append(subtypeForResource, status); - // Map duration-year-person, duration-week-person, etc. to duration-year, duration-week, ... - // TODO(ICU-20400): Get duration-*-person data properly with aliases. - int32_t subtypeLen = static_cast(uprv_strlen(unit.getSubtype())); - if (subtypeLen > 7 && uprv_strcmp(unit.getSubtype() + subtypeLen - 7, "-person") == 0) { - key.append({unit.getSubtype(), subtypeLen - 7}, status); - } else { - key.append(unit.getSubtype(), status); + UErrorCode localStatus = U_ZERO_ERROR; + ures_getAllItemsWithFallback(unitsBundle.getAlias(), key.data(), sink, localStatus); + if (width == UNUM_UNIT_WIDTH_SHORT) { + if (U_FAILURE(localStatus)) { + status = localStatus; + } + return; } + // TODO(ICU-13353): The fallback to short does not work in ICU4C. + // Manually fall back to short (this is done automatically in Java). + key.clear(); + key.append("unitsShort/", status); + key.append(unit.getType(), status); + key.append("/", status); + key.append(subtypeForResource, status); ures_getAllItemsWithFallback(unitsBundle.getAlias(), key.data(), sink, status); } @@ -232,6 +252,19 @@ LongNameHandler::forCompoundUnit(const Locale &loc, const MeasureUnit &unit, con return result; } +UnicodeString LongNameHandler::getUnitDisplayName( + const Locale& loc, + const MeasureUnit& unit, + UNumberUnitWidth width, + UErrorCode& status) { + if (U_FAILURE(status)) { + return ICU_Utility::makeBogusString(); + } + UnicodeString simpleFormats[ARRAY_LENGTH]; + getMeasureData(loc, unit, width, simpleFormats, status); + return simpleFormats[DNAM_INDEX]; +} + LongNameHandler* LongNameHandler::forCurrencyLongNames(const Locale &loc, const CurrencyUnit ¤cy, const PluralRules *rules, const MicroPropsGenerator *parent, diff --git a/icu4c/source/i18n/number_longnames.h b/icu4c/source/i18n/number_longnames.h index a71d0caadf1..76fb82d744b 100644 --- a/icu4c/source/i18n/number_longnames.h +++ b/icu4c/source/i18n/number_longnames.h @@ -16,6 +16,12 @@ namespace impl { class LongNameHandler : public MicroPropsGenerator, public ModifierStore, public UMemory { public: + static UnicodeString getUnitDisplayName( + const Locale& loc, + const MeasureUnit& unit, + UNumberUnitWidth width, + UErrorCode& status); + static LongNameHandler* forCurrencyLongNames(const Locale &loc, const CurrencyUnit ¤cy, const PluralRules *rules, const MicroPropsGenerator *parent, UErrorCode &status); diff --git a/icu4c/source/i18n/quantityformatter.h b/icu4c/source/i18n/quantityformatter.h index 0069cb2b8de..046eec7509e 100644 --- a/icu4c/source/i18n/quantityformatter.h +++ b/icu4c/source/i18n/quantityformatter.h @@ -150,6 +150,7 @@ public: /** * Formats the pattern with the value and adjusts the FieldPosition. + * TODO: Remove? */ static UnicodeString &format( const SimpleFormatter &pattern, diff --git a/icu4c/source/i18n/tmutfmt.cpp b/icu4c/source/i18n/tmutfmt.cpp index 50dac8b7cef..dad8825e70f 100644 --- a/icu4c/source/i18n/tmutfmt.cpp +++ b/icu4c/source/i18n/tmutfmt.cpp @@ -224,7 +224,7 @@ TimeUnitFormat::parseObject(const UnicodeString& source, if (temp.getType() == Formattable::kString) { UnicodeString tmpString; UErrorCode pStatus = U_ZERO_ERROR; - getNumberFormat().parse(temp.getString(tmpString), tmpNumber, pStatus); + getNumberFormatInternal().parse(temp.getString(tmpString), tmpNumber, pStatus); if (U_FAILURE(pStatus)) { continue; } diff --git a/icu4c/source/i18n/unicode/decimfmt.h b/icu4c/source/i18n/unicode/decimfmt.h index b95c63a06a3..e53a71bba65 100644 --- a/icu4c/source/i18n/unicode/decimfmt.h +++ b/icu4c/source/i18n/unicode/decimfmt.h @@ -2205,6 +2205,9 @@ class U_I18N_API DecimalFormat : public NumberFormat { // Allow child class CompactDecimalFormat to access fProperties: friend class CompactDecimalFormat; + // Allow MeasureFormat to use fieldPositionHelper: + friend class MeasureFormat; + }; U_NAMESPACE_END diff --git a/icu4c/source/i18n/unicode/measfmt.h b/icu4c/source/i18n/unicode/measfmt.h index 3167a76c33a..12101d6e28b 100644 --- a/icu4c/source/i18n/unicode/measfmt.h +++ b/icu4c/source/i18n/unicode/measfmt.h @@ -322,7 +322,14 @@ class U_I18N_API MeasureFormat : public Format { * ICU use only. * @internal. */ - const NumberFormat &getNumberFormat() const; + const NumberFormat &getNumberFormatInternal() const; + + /** + * ICU use only. + * Always returns the short form currency formatter. + * @internal. + */ + const NumberFormat& getCurrencyFormatInternal() const; /** * ICU use only. @@ -355,27 +362,6 @@ class U_I18N_API MeasureFormat : public Format { // shared across instances. ListFormatter *listFormatter; - const SimpleFormatter *getFormatterOrNull( - const MeasureUnit &unit, UMeasureFormatWidth width, int32_t index) const; - - const SimpleFormatter *getFormatter( - const MeasureUnit &unit, UMeasureFormatWidth width, int32_t index, - UErrorCode &errorCode) const; - - const SimpleFormatter *getPluralFormatter( - const MeasureUnit &unit, UMeasureFormatWidth width, int32_t index, - UErrorCode &errorCode) const; - - const SimpleFormatter *getPerFormatter( - UMeasureFormatWidth width, - UErrorCode &status) const; - - int32_t withPerUnitAndAppend( - const UnicodeString &formatted, - const MeasureUnit &perUnit, - UnicodeString &appendTo, - UErrorCode &status) const; - UnicodeString &formatMeasure( const Measure &measure, const NumberFormat &nf, diff --git a/icu4c/source/test/intltest/numbertest_api.cpp b/icu4c/source/test/intltest/numbertest_api.cpp index b95807e6228..4e6f7f5d741 100644 --- a/icu4c/source/test/intltest/numbertest_api.cpp +++ b/icu4c/source/test/intltest/numbertest_api.cpp @@ -527,6 +527,15 @@ void NumberFormatterApiTest::unitMeasure() { 5.43, u"5.43 m²"); + // Try accessing a narrow unit directly from root. + assertFormatSingle( + u"Interesting Data Fallback 4", + u"measure-unit/area-square-meter unit-width-narrow", + NumberFormatter::with().unit(SQUARE_METER).unitWidth(UNumberUnitWidth::UNUM_UNIT_WIDTH_NARROW), + Locale::createFromName("root"), + 5.43, + u"5.43 m²"); + // es_US has "{0}°" for unitsNarrow/temperature/FAHRENHEIT. // NOTE: This example is in the documentation. assertFormatSingle( diff --git a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/NumberFormatterApiTest.java b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/NumberFormatterApiTest.java index eb0dc6cfe91..69f3e6b89ea 100644 --- a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/NumberFormatterApiTest.java +++ b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/NumberFormatterApiTest.java @@ -499,6 +499,15 @@ public class NumberFormatterApiTest { 5.43, "5.43 m²"); + // Try accessing a narrow unit directly from root. + assertFormatSingle( + "Interesting Data Fallback 4", + "measure-unit/area-square-meter unit-width-narrow", + NumberFormatter.with().unit(MeasureUnit.SQUARE_METER).unitWidth(UnitWidth.NARROW), + ULocale.forLanguageTag("root"), + 5.43, + "5.43 m²"); + // es_US has "{0}°" for unitsNarrow/temperature/FAHRENHEIT. // NOTE: This example is in the documentation. assertFormatSingle(