// © 2016 and later: Unicode, Inc. and others. // License & terms of use: http://www.unicode.org/copyright.html /* ******************************************************************************* * Copyright (C) 2009-2014, International Business Machines Corporation and * others. All Rights Reserved. ******************************************************************************* */ #include "unicode/currpinf.h" #if !UCONFIG_NO_FORMATTING //#define CURRENCY_PLURAL_INFO_DEBUG 1 #ifdef CURRENCY_PLURAL_INFO_DEBUG #include <iostream> #endif #include "unicode/locid.h" #include "unicode/plurrule.h" #include "unicode/strenum.h" #include "unicode/ures.h" #include "unicode/numsys.h" #include "cstring.h" #include "hash.h" #include "uresimp.h" #include "ureslocs.h" U_NAMESPACE_BEGIN static const UChar gNumberPatternSeparator = 0x3B; // ; U_CDECL_BEGIN /** * @internal ICU 4.2 */ static UBool U_CALLCONV ValueComparator(UHashTok val1, UHashTok val2); UBool U_CALLCONV ValueComparator(UHashTok val1, UHashTok val2) { const UnicodeString* affix_1 = (UnicodeString*)val1.pointer; const UnicodeString* affix_2 = (UnicodeString*)val2.pointer; return *affix_1 == *affix_2; } U_CDECL_END UOBJECT_DEFINE_RTTI_IMPLEMENTATION(CurrencyPluralInfo) static const UChar gDefaultCurrencyPluralPattern[] = {'0', '.', '#', '#', ' ', 0xA4, 0xA4, 0xA4, 0}; static const UChar gTripleCurrencySign[] = {0xA4, 0xA4, 0xA4, 0}; static const UChar gPluralCountOther[] = {0x6F, 0x74, 0x68, 0x65, 0x72, 0}; static const UChar gPart0[] = {0x7B, 0x30, 0x7D, 0}; static const UChar gPart1[] = {0x7B, 0x31, 0x7D, 0}; static const char gNumberElementsTag[]="NumberElements"; static const char gLatnTag[]="latn"; static const char gPatternsTag[]="patterns"; static const char gDecimalFormatTag[]="decimalFormat"; static const char gCurrUnitPtnTag[]="CurrencyUnitPatterns"; CurrencyPluralInfo::CurrencyPluralInfo(UErrorCode& status) : fPluralCountToCurrencyUnitPattern(nullptr), fPluralRules(nullptr), fLocale(nullptr), fInternalStatus(U_ZERO_ERROR) { initialize(Locale::getDefault(), status); } CurrencyPluralInfo::CurrencyPluralInfo(const Locale& locale, UErrorCode& status) : fPluralCountToCurrencyUnitPattern(nullptr), fPluralRules(nullptr), fLocale(nullptr), fInternalStatus(U_ZERO_ERROR) { initialize(locale, status); } CurrencyPluralInfo::CurrencyPluralInfo(const CurrencyPluralInfo& info) : UObject(info), fPluralCountToCurrencyUnitPattern(nullptr), fPluralRules(nullptr), fLocale(nullptr), fInternalStatus(U_ZERO_ERROR) { *this = info; } CurrencyPluralInfo& CurrencyPluralInfo::operator=(const CurrencyPluralInfo& info) { if (this == &info) { return *this; } fInternalStatus = info.fInternalStatus; if (U_FAILURE(fInternalStatus)) { // bail out early if the object we were copying from was already 'invalid'. return *this; } deleteHash(fPluralCountToCurrencyUnitPattern); fPluralCountToCurrencyUnitPattern = initHash(fInternalStatus); copyHash(info.fPluralCountToCurrencyUnitPattern, fPluralCountToCurrencyUnitPattern, fInternalStatus); if ( U_FAILURE(fInternalStatus) ) { return *this; } delete fPluralRules; fPluralRules = nullptr; delete fLocale; fLocale = nullptr; if (info.fPluralRules != nullptr) { fPluralRules = info.fPluralRules->clone(); if (fPluralRules == nullptr) { fInternalStatus = U_MEMORY_ALLOCATION_ERROR; return *this; } } if (info.fLocale != nullptr) { fLocale = info.fLocale->clone(); if (fLocale == nullptr) { // Note: If clone had an error parameter, then we could check/set that instead. fInternalStatus = U_MEMORY_ALLOCATION_ERROR; return *this; } // If the other locale wasn't bogus, but our clone'd locale is bogus, then OOM happened // during the call to clone(). if (!info.fLocale->isBogus() && fLocale->isBogus()) { fInternalStatus = U_MEMORY_ALLOCATION_ERROR; return *this; } } return *this; } CurrencyPluralInfo::~CurrencyPluralInfo() { deleteHash(fPluralCountToCurrencyUnitPattern); fPluralCountToCurrencyUnitPattern = nullptr; delete fPluralRules; delete fLocale; fPluralRules = nullptr; fLocale = nullptr; } UBool CurrencyPluralInfo::operator==(const CurrencyPluralInfo& info) const { #ifdef CURRENCY_PLURAL_INFO_DEBUG if (*fPluralRules == *info.fPluralRules) { std::cout << "same plural rules\n"; } if (*fLocale == *info.fLocale) { std::cout << "same locale\n"; } if (fPluralCountToCurrencyUnitPattern->equals(*info.fPluralCountToCurrencyUnitPattern)) { std::cout << "same pattern\n"; } #endif return *fPluralRules == *info.fPluralRules && *fLocale == *info.fLocale && fPluralCountToCurrencyUnitPattern->equals(*info.fPluralCountToCurrencyUnitPattern); } CurrencyPluralInfo* CurrencyPluralInfo::clone() const { CurrencyPluralInfo* newObj = new CurrencyPluralInfo(*this); // Since clone doesn't have a 'status' parameter, the best we can do is return nullptr // if the new object was not full constructed properly (an error occurred). if (newObj != nullptr && U_FAILURE(newObj->fInternalStatus)) { delete newObj; newObj = nullptr; } return newObj; } const PluralRules* CurrencyPluralInfo::getPluralRules() const { return fPluralRules; } UnicodeString& CurrencyPluralInfo::getCurrencyPluralPattern(const UnicodeString& pluralCount, UnicodeString& result) const { const UnicodeString* currencyPluralPattern = (UnicodeString*)fPluralCountToCurrencyUnitPattern->get(pluralCount); if (currencyPluralPattern == nullptr) { // fall back to "other" if (pluralCount.compare(gPluralCountOther, 5)) { currencyPluralPattern = (UnicodeString*)fPluralCountToCurrencyUnitPattern->get(UnicodeString(TRUE, gPluralCountOther, 5)); } if (currencyPluralPattern == nullptr) { // no currencyUnitPatterns defined, // fallback to predefined default. // This should never happen when ICU resource files are // available, since currencyUnitPattern of "other" is always // defined in root. result = UnicodeString(gDefaultCurrencyPluralPattern); return result; } } result = *currencyPluralPattern; return result; } const Locale& CurrencyPluralInfo::getLocale() const { return *fLocale; } void CurrencyPluralInfo::setPluralRules(const UnicodeString& ruleDescription, UErrorCode& status) { if (U_SUCCESS(status)) { delete fPluralRules; fPluralRules = PluralRules::createRules(ruleDescription, status); } } void CurrencyPluralInfo::setCurrencyPluralPattern(const UnicodeString& pluralCount, const UnicodeString& pattern, UErrorCode& status) { if (U_SUCCESS(status)) { UnicodeString* oldValue = static_cast<UnicodeString*>( fPluralCountToCurrencyUnitPattern->get(pluralCount)); delete oldValue; LocalPointer<UnicodeString> p(new UnicodeString(pattern), status); if (U_SUCCESS(status)) { // the p object allocated above will be owned by fPluralCountToCurrencyUnitPattern // after the call to put(), even if the method returns failure. fPluralCountToCurrencyUnitPattern->put(pluralCount, p.orphan(), status); } } } void CurrencyPluralInfo::setLocale(const Locale& loc, UErrorCode& status) { initialize(loc, status); } void CurrencyPluralInfo::initialize(const Locale& loc, UErrorCode& status) { if (U_FAILURE(status)) { return; } delete fLocale; fLocale = nullptr; delete fPluralRules; fPluralRules = nullptr; fLocale = loc.clone(); if (fLocale == nullptr) { status = U_MEMORY_ALLOCATION_ERROR; return; } // If the locale passed in wasn't bogus, but our clone'd locale is bogus, then OOM happened // during the call to loc.clone(). if (!loc.isBogus() && fLocale->isBogus()) { status = U_MEMORY_ALLOCATION_ERROR; return; } fPluralRules = PluralRules::forLocale(loc, status); setupCurrencyPluralPattern(loc, status); } void CurrencyPluralInfo::setupCurrencyPluralPattern(const Locale& loc, UErrorCode& status) { if (U_FAILURE(status)) { return; } deleteHash(fPluralCountToCurrencyUnitPattern); fPluralCountToCurrencyUnitPattern = initHash(status); if (U_FAILURE(status)) { return; } LocalPointer<NumberingSystem> ns(NumberingSystem::createInstance(loc, status), status); if (U_FAILURE(status)) { return; } UErrorCode ec = U_ZERO_ERROR; LocalUResourceBundlePointer rb(ures_open(nullptr, loc.getName(), &ec)); LocalUResourceBundlePointer numElements(ures_getByKeyWithFallback(rb.getAlias(), gNumberElementsTag, nullptr, &ec)); ures_getByKeyWithFallback(numElements.getAlias(), ns->getName(), rb.getAlias(), &ec); ures_getByKeyWithFallback(rb.getAlias(), gPatternsTag, rb.getAlias(), &ec); int32_t ptnLen; const UChar* numberStylePattern = ures_getStringByKeyWithFallback(rb.getAlias(), gDecimalFormatTag, &ptnLen, &ec); // Fall back to "latn" if num sys specific pattern isn't there. if ( ec == U_MISSING_RESOURCE_ERROR && (uprv_strcmp(ns->getName(), gLatnTag) != 0)) { ec = U_ZERO_ERROR; ures_getByKeyWithFallback(numElements.getAlias(), gLatnTag, rb.getAlias(), &ec); ures_getByKeyWithFallback(rb.getAlias(), gPatternsTag, rb.getAlias(), &ec); numberStylePattern = ures_getStringByKeyWithFallback(rb.getAlias(), gDecimalFormatTag, &ptnLen, &ec); } int32_t numberStylePatternLen = ptnLen; const UChar* negNumberStylePattern = nullptr; int32_t negNumberStylePatternLen = 0; // TODO: Java // parse to check whether there is ";" separator in the numberStylePattern UBool hasSeparator = false; if (U_SUCCESS(ec)) { for (int32_t styleCharIndex = 0; styleCharIndex < ptnLen; ++styleCharIndex) { if (numberStylePattern[styleCharIndex] == gNumberPatternSeparator) { hasSeparator = true; // split the number style pattern into positive and negative negNumberStylePattern = numberStylePattern + styleCharIndex + 1; negNumberStylePatternLen = ptnLen - styleCharIndex - 1; numberStylePatternLen = styleCharIndex; } } } if (U_FAILURE(ec)) { // If OOM occurred during the above code, then we want to report that back to the caller. if (ec == U_MEMORY_ALLOCATION_ERROR) { status = ec; } return; } LocalUResourceBundlePointer currRb(ures_open(U_ICUDATA_CURR, loc.getName(), &ec)); LocalUResourceBundlePointer currencyRes(ures_getByKeyWithFallback(currRb.getAlias(), gCurrUnitPtnTag, nullptr, &ec)); #ifdef CURRENCY_PLURAL_INFO_DEBUG std::cout << "in set up\n"; #endif LocalPointer<StringEnumeration> keywords(fPluralRules->getKeywords(ec), ec); if (U_SUCCESS(ec)) { const char* pluralCount; while (((pluralCount = keywords->next(nullptr, ec)) != nullptr) && U_SUCCESS(ec)) { int32_t ptnLength; UErrorCode err = U_ZERO_ERROR; const UChar* patternChars = ures_getStringByKeyWithFallback(currencyRes.getAlias(), pluralCount, &ptnLength, &err); if (err == U_MEMORY_ALLOCATION_ERROR || patternChars == nullptr) { ec = err; break; } if (U_SUCCESS(err) && ptnLength > 0) { UnicodeString* pattern = new UnicodeString(patternChars, ptnLength); if (pattern == nullptr) { ec = U_MEMORY_ALLOCATION_ERROR; break; } #ifdef CURRENCY_PLURAL_INFO_DEBUG char result_1[1000]; pattern->extract(0, pattern->length(), result_1, "UTF-8"); std::cout << "pluralCount: " << pluralCount << "; pattern: " << result_1 << "\n"; #endif pattern->findAndReplace(UnicodeString(TRUE, gPart0, 3), UnicodeString(numberStylePattern, numberStylePatternLen)); pattern->findAndReplace(UnicodeString(TRUE, gPart1, 3), UnicodeString(TRUE, gTripleCurrencySign, 3)); if (hasSeparator) { UnicodeString negPattern(patternChars, ptnLength); negPattern.findAndReplace(UnicodeString(TRUE, gPart0, 3), UnicodeString(negNumberStylePattern, negNumberStylePatternLen)); negPattern.findAndReplace(UnicodeString(TRUE, gPart1, 3), UnicodeString(TRUE, gTripleCurrencySign, 3)); pattern->append(gNumberPatternSeparator); pattern->append(negPattern); } #ifdef CURRENCY_PLURAL_INFO_DEBUG pattern->extract(0, pattern->length(), result_1, "UTF-8"); std::cout << "pluralCount: " << pluralCount << "; pattern: " << result_1 << "\n"; #endif // the 'pattern' object allocated above will be owned by the fPluralCountToCurrencyUnitPattern after the call to // put(), even if the method returns failure. fPluralCountToCurrencyUnitPattern->put(UnicodeString(pluralCount, -1, US_INV), pattern, status); } } } // If OOM occurred during the above code, then we want to report that back to the caller. if (ec == U_MEMORY_ALLOCATION_ERROR) { status = ec; } } void CurrencyPluralInfo::deleteHash(Hashtable* hTable) { if ( hTable == nullptr ) { return; } int32_t pos = UHASH_FIRST; const UHashElement* element = nullptr; while ( (element = hTable->nextElement(pos)) != nullptr ) { const UHashTok valueTok = element->value; const UnicodeString* value = (UnicodeString*)valueTok.pointer; delete value; } delete hTable; hTable = nullptr; } Hashtable* CurrencyPluralInfo::initHash(UErrorCode& status) { if (U_FAILURE(status)) { return nullptr; } LocalPointer<Hashtable> hTable(new Hashtable(TRUE, status), status); if (U_FAILURE(status)) { return nullptr; } hTable->setValueComparator(ValueComparator); return hTable.orphan(); } void CurrencyPluralInfo::copyHash(const Hashtable* source, Hashtable* target, UErrorCode& status) { if (U_FAILURE(status)) { return; } int32_t pos = UHASH_FIRST; const UHashElement* element = nullptr; if (source) { while ( (element = source->nextElement(pos)) != nullptr ) { const UHashTok keyTok = element->key; const UnicodeString* key = (UnicodeString*)keyTok.pointer; const UHashTok valueTok = element->value; const UnicodeString* value = (UnicodeString*)valueTok.pointer; LocalPointer<UnicodeString> copy(new UnicodeString(*value), status); if (U_FAILURE(status)) { return; } // The HashTable owns the 'copy' object after the call to put(). target->put(UnicodeString(*key), copy.orphan(), status); if (U_FAILURE(status)) { return; } } } } U_NAMESPACE_END #endif