1 // 2016 and later: Unicode, Inc. and others. 2 // License & terms of use: http://www.unicode.org/copyright.html 3 /* 4 ******************************************************************************* 5 * Copyright (C) 2009-2014, International Business Machines Corporation and 6 * others. All Rights Reserved. 7 ******************************************************************************* 8 */ 9 10 #include "unicode/currpinf.h" 11 12 #if !UCONFIG_NO_FORMATTING 13 14 //#define CURRENCY_PLURAL_INFO_DEBUG 1 15 16 #ifdef CURRENCY_PLURAL_INFO_DEBUG 17 #include <iostream> 18 #endif 19 20 #include "unicode/locid.h" 21 #include "unicode/plurrule.h" 22 #include "unicode/strenum.h" 23 #include "unicode/ures.h" 24 #include "unicode/numsys.h" 25 #include "cstring.h" 26 #include "hash.h" 27 #include "uresimp.h" 28 #include "ureslocs.h" 29 30 U_NAMESPACE_BEGIN 31 32 static const UChar gNumberPatternSeparator = 0x3B; // ; 33 34 U_CDECL_BEGIN 35 36 /** 37 * @internal ICU 4.2 38 */ 39 static UBool U_CALLCONV ValueComparator(UHashTok val1, UHashTok val2); 40 41 UBool 42 U_CALLCONV ValueComparator(UHashTok val1, UHashTok val2) { 43 const UnicodeString* affix_1 = (UnicodeString*)val1.pointer; 44 const UnicodeString* affix_2 = (UnicodeString*)val2.pointer; 45 return *affix_1 == *affix_2; 46 } 47 48 U_CDECL_END 49 50 51 UOBJECT_DEFINE_RTTI_IMPLEMENTATION(CurrencyPluralInfo) 52 53 static const UChar gDefaultCurrencyPluralPattern[] = {'0', '.', '#', '#', ' ', 0xA4, 0xA4, 0xA4, 0}; 54 static const UChar gTripleCurrencySign[] = {0xA4, 0xA4, 0xA4, 0}; 55 static const UChar gPluralCountOther[] = {0x6F, 0x74, 0x68, 0x65, 0x72, 0}; 56 static const UChar gPart0[] = {0x7B, 0x30, 0x7D, 0}; 57 static const UChar gPart1[] = {0x7B, 0x31, 0x7D, 0}; 58 59 static const char gNumberElementsTag[]="NumberElements"; 60 static const char gLatnTag[]="latn"; 61 static const char gPatternsTag[]="patterns"; 62 static const char gDecimalFormatTag[]="decimalFormat"; 63 static const char gCurrUnitPtnTag[]="CurrencyUnitPatterns"; 64 65 CurrencyPluralInfo::CurrencyPluralInfo(UErrorCode& status) 66 : fPluralCountToCurrencyUnitPattern(nullptr), 67 fPluralRules(nullptr), 68 fLocale(nullptr), 69 fInternalStatus(U_ZERO_ERROR) { 70 initialize(Locale::getDefault(), status); 71 } 72 73 CurrencyPluralInfo::CurrencyPluralInfo(const Locale& locale, UErrorCode& status) 74 : fPluralCountToCurrencyUnitPattern(nullptr), 75 fPluralRules(nullptr), 76 fLocale(nullptr), 77 fInternalStatus(U_ZERO_ERROR) { 78 initialize(locale, status); 79 } 80 81 CurrencyPluralInfo::CurrencyPluralInfo(const CurrencyPluralInfo& info) 82 : UObject(info), 83 fPluralCountToCurrencyUnitPattern(nullptr), 84 fPluralRules(nullptr), 85 fLocale(nullptr), 86 fInternalStatus(U_ZERO_ERROR) { 87 *this = info; 88 } 89 90 CurrencyPluralInfo& 91 CurrencyPluralInfo::operator=(const CurrencyPluralInfo& info) { 92 if (this == &info) { 93 return *this; 94 } 95 96 fInternalStatus = info.fInternalStatus; 97 if (U_FAILURE(fInternalStatus)) { 98 // bail out early if the object we were copying from was already 'invalid'. 99 return *this; 100 } 101 102 deleteHash(fPluralCountToCurrencyUnitPattern); 103 fPluralCountToCurrencyUnitPattern = initHash(fInternalStatus); 104 copyHash(info.fPluralCountToCurrencyUnitPattern, 105 fPluralCountToCurrencyUnitPattern, fInternalStatus); 106 if ( U_FAILURE(fInternalStatus) ) { 107 return *this; 108 } 109 110 delete fPluralRules; 111 fPluralRules = nullptr; 112 delete fLocale; 113 fLocale = nullptr; 114 115 if (info.fPluralRules != nullptr) { 116 fPluralRules = info.fPluralRules->clone(); 117 if (fPluralRules == nullptr) { 118 fInternalStatus = U_MEMORY_ALLOCATION_ERROR; 119 return *this; 120 } 121 } 122 if (info.fLocale != nullptr) { 123 fLocale = info.fLocale->clone(); 124 if (fLocale == nullptr) { 125 // Note: If clone had an error parameter, then we could check/set that instead. 126 fInternalStatus = U_MEMORY_ALLOCATION_ERROR; 127 return *this; 128 } 129 // If the other locale wasn't bogus, but our clone'd locale is bogus, then OOM happened 130 // during the call to clone(). 131 if (!info.fLocale->isBogus() && fLocale->isBogus()) { 132 fInternalStatus = U_MEMORY_ALLOCATION_ERROR; 133 return *this; 134 } 135 } 136 return *this; 137 } 138 139 CurrencyPluralInfo::~CurrencyPluralInfo() { 140 deleteHash(fPluralCountToCurrencyUnitPattern); 141 fPluralCountToCurrencyUnitPattern = nullptr; 142 delete fPluralRules; 143 delete fLocale; 144 fPluralRules = nullptr; 145 fLocale = nullptr; 146 } 147 148 UBool 149 CurrencyPluralInfo::operator==(const CurrencyPluralInfo& info) const { 150 #ifdef CURRENCY_PLURAL_INFO_DEBUG 151 if (*fPluralRules == *info.fPluralRules) { 152 std::cout << "same plural rules\n"; 153 } 154 if (*fLocale == *info.fLocale) { 155 std::cout << "same locale\n"; 156 } 157 if (fPluralCountToCurrencyUnitPattern->equals(*info.fPluralCountToCurrencyUnitPattern)) { 158 std::cout << "same pattern\n"; 159 } 160 #endif 161 return *fPluralRules == *info.fPluralRules && 162 *fLocale == *info.fLocale && 163 fPluralCountToCurrencyUnitPattern->equals(*info.fPluralCountToCurrencyUnitPattern); 164 } 165 166 167 CurrencyPluralInfo* 168 CurrencyPluralInfo::clone() const { 169 CurrencyPluralInfo* newObj = new CurrencyPluralInfo(*this); 170 // Since clone doesn't have a 'status' parameter, the best we can do is return nullptr 171 // if the new object was not full constructed properly (an error occurred). 172 if (newObj != nullptr && U_FAILURE(newObj->fInternalStatus)) { 173 delete newObj; 174 newObj = nullptr; 175 } 176 return newObj; 177 } 178 179 const PluralRules* 180 CurrencyPluralInfo::getPluralRules() const { 181 return fPluralRules; 182 } 183 184 UnicodeString& 185 CurrencyPluralInfo::getCurrencyPluralPattern(const UnicodeString& pluralCount, 186 UnicodeString& result) const { 187 const UnicodeString* currencyPluralPattern = 188 (UnicodeString*)fPluralCountToCurrencyUnitPattern->get(pluralCount); 189 if (currencyPluralPattern == nullptr) { 190 // fall back to "other" 191 if (pluralCount.compare(gPluralCountOther, 5)) { 192 currencyPluralPattern = 193 (UnicodeString*)fPluralCountToCurrencyUnitPattern->get(UnicodeString(TRUE, gPluralCountOther, 5)); 194 } 195 if (currencyPluralPattern == nullptr) { 196 // no currencyUnitPatterns defined, 197 // fallback to predefined default. 198 // This should never happen when ICU resource files are 199 // available, since currencyUnitPattern of "other" is always 200 // defined in root. 201 result = UnicodeString(gDefaultCurrencyPluralPattern); 202 return result; 203 } 204 } 205 result = *currencyPluralPattern; 206 return result; 207 } 208 209 const Locale& 210 CurrencyPluralInfo::getLocale() const { 211 return *fLocale; 212 } 213 214 void 215 CurrencyPluralInfo::setPluralRules(const UnicodeString& ruleDescription, 216 UErrorCode& status) { 217 if (U_SUCCESS(status)) { 218 delete fPluralRules; 219 fPluralRules = PluralRules::createRules(ruleDescription, status); 220 } 221 } 222 223 void 224 CurrencyPluralInfo::setCurrencyPluralPattern(const UnicodeString& pluralCount, 225 const UnicodeString& pattern, 226 UErrorCode& status) { 227 if (U_SUCCESS(status)) { 228 UnicodeString* oldValue = static_cast<UnicodeString*>( 229 fPluralCountToCurrencyUnitPattern->get(pluralCount)); 230 delete oldValue; 231 LocalPointer<UnicodeString> p(new UnicodeString(pattern), status); 232 if (U_SUCCESS(status)) { 233 // the p object allocated above will be owned by fPluralCountToCurrencyUnitPattern 234 // after the call to put(), even if the method returns failure. 235 fPluralCountToCurrencyUnitPattern->put(pluralCount, p.orphan(), status); 236 } 237 } 238 } 239 240 void 241 CurrencyPluralInfo::setLocale(const Locale& loc, UErrorCode& status) { 242 initialize(loc, status); 243 } 244 245 void 246 CurrencyPluralInfo::initialize(const Locale& loc, UErrorCode& status) { 247 if (U_FAILURE(status)) { 248 return; 249 } 250 delete fLocale; 251 fLocale = nullptr; 252 delete fPluralRules; 253 fPluralRules = nullptr; 254 255 fLocale = loc.clone(); 256 if (fLocale == nullptr) { 257 status = U_MEMORY_ALLOCATION_ERROR; 258 return; 259 } 260 // If the locale passed in wasn't bogus, but our clone'd locale is bogus, then OOM happened 261 // during the call to loc.clone(). 262 if (!loc.isBogus() && fLocale->isBogus()) { 263 status = U_MEMORY_ALLOCATION_ERROR; 264 return; 265 } 266 fPluralRules = PluralRules::forLocale(loc, status); 267 setupCurrencyPluralPattern(loc, status); 268 } 269 270 void 271 CurrencyPluralInfo::setupCurrencyPluralPattern(const Locale& loc, UErrorCode& status) { 272 if (U_FAILURE(status)) { 273 return; 274 } 275 276 deleteHash(fPluralCountToCurrencyUnitPattern); 277 fPluralCountToCurrencyUnitPattern = initHash(status); 278 if (U_FAILURE(status)) { 279 return; 280 } 281 282 LocalPointer<NumberingSystem> ns(NumberingSystem::createInstance(loc, status), status); 283 if (U_FAILURE(status)) { 284 return; 285 } 286 UErrorCode ec = U_ZERO_ERROR; 287 LocalUResourceBundlePointer rb(ures_open(nullptr, loc.getName(), &ec)); 288 LocalUResourceBundlePointer numElements(ures_getByKeyWithFallback(rb.getAlias(), gNumberElementsTag, nullptr, &ec)); 289 ures_getByKeyWithFallback(numElements.getAlias(), ns->getName(), rb.getAlias(), &ec); 290 ures_getByKeyWithFallback(rb.getAlias(), gPatternsTag, rb.getAlias(), &ec); 291 int32_t ptnLen; 292 const UChar* numberStylePattern = ures_getStringByKeyWithFallback(rb.getAlias(), gDecimalFormatTag, &ptnLen, &ec); 293 // Fall back to "latn" if num sys specific pattern isn't there. 294 if ( ec == U_MISSING_RESOURCE_ERROR && (uprv_strcmp(ns->getName(), gLatnTag) != 0)) { 295 ec = U_ZERO_ERROR; 296 ures_getByKeyWithFallback(numElements.getAlias(), gLatnTag, rb.getAlias(), &ec); 297 ures_getByKeyWithFallback(rb.getAlias(), gPatternsTag, rb.getAlias(), &ec); 298 numberStylePattern = ures_getStringByKeyWithFallback(rb.getAlias(), gDecimalFormatTag, &ptnLen, &ec); 299 } 300 int32_t numberStylePatternLen = ptnLen; 301 const UChar* negNumberStylePattern = nullptr; 302 int32_t negNumberStylePatternLen = 0; 303 // TODO: Java 304 // parse to check whether there is ";" separator in the numberStylePattern 305 UBool hasSeparator = false; 306 if (U_SUCCESS(ec)) { 307 for (int32_t styleCharIndex = 0; styleCharIndex < ptnLen; ++styleCharIndex) { 308 if (numberStylePattern[styleCharIndex] == gNumberPatternSeparator) { 309 hasSeparator = true; 310 // split the number style pattern into positive and negative 311 negNumberStylePattern = numberStylePattern + styleCharIndex + 1; 312 negNumberStylePatternLen = ptnLen - styleCharIndex - 1; 313 numberStylePatternLen = styleCharIndex; 314 } 315 } 316 } 317 318 if (U_FAILURE(ec)) { 319 // If OOM occurred during the above code, then we want to report that back to the caller. 320 if (ec == U_MEMORY_ALLOCATION_ERROR) { 321 status = ec; 322 } 323 return; 324 } 325 326 LocalUResourceBundlePointer currRb(ures_open(U_ICUDATA_CURR, loc.getName(), &ec)); 327 LocalUResourceBundlePointer currencyRes(ures_getByKeyWithFallback(currRb.getAlias(), gCurrUnitPtnTag, nullptr, &ec)); 328 329 #ifdef CURRENCY_PLURAL_INFO_DEBUG 330 std::cout << "in set up\n"; 331 #endif 332 LocalPointer<StringEnumeration> keywords(fPluralRules->getKeywords(ec), ec); 333 if (U_SUCCESS(ec)) { 334 const char* pluralCount; 335 while (((pluralCount = keywords->next(nullptr, ec)) != nullptr) && U_SUCCESS(ec)) { 336 int32_t ptnLength; 337 UErrorCode err = U_ZERO_ERROR; 338 const UChar* patternChars = ures_getStringByKeyWithFallback(currencyRes.getAlias(), pluralCount, &ptnLength, &err); 339 if (err == U_MEMORY_ALLOCATION_ERROR || patternChars == nullptr) { 340 ec = err; 341 break; 342 } 343 if (U_SUCCESS(err) && ptnLength > 0) { 344 UnicodeString* pattern = new UnicodeString(patternChars, ptnLength); 345 if (pattern == nullptr) { 346 ec = U_MEMORY_ALLOCATION_ERROR; 347 break; 348 } 349 #ifdef CURRENCY_PLURAL_INFO_DEBUG 350 char result_1[1000]; 351 pattern->extract(0, pattern->length(), result_1, "UTF-8"); 352 std::cout << "pluralCount: " << pluralCount << "; pattern: " << result_1 << "\n"; 353 #endif 354 pattern->findAndReplace(UnicodeString(TRUE, gPart0, 3), 355 UnicodeString(numberStylePattern, numberStylePatternLen)); 356 pattern->findAndReplace(UnicodeString(TRUE, gPart1, 3), UnicodeString(TRUE, gTripleCurrencySign, 3)); 357 358 if (hasSeparator) { 359 UnicodeString negPattern(patternChars, ptnLength); 360 negPattern.findAndReplace(UnicodeString(TRUE, gPart0, 3), 361 UnicodeString(negNumberStylePattern, negNumberStylePatternLen)); 362 negPattern.findAndReplace(UnicodeString(TRUE, gPart1, 3), UnicodeString(TRUE, gTripleCurrencySign, 3)); 363 pattern->append(gNumberPatternSeparator); 364 pattern->append(negPattern); 365 } 366 #ifdef CURRENCY_PLURAL_INFO_DEBUG 367 pattern->extract(0, pattern->length(), result_1, "UTF-8"); 368 std::cout << "pluralCount: " << pluralCount << "; pattern: " << result_1 << "\n"; 369 #endif 370 // the 'pattern' object allocated above will be owned by the fPluralCountToCurrencyUnitPattern after the call to 371 // put(), even if the method returns failure. 372 fPluralCountToCurrencyUnitPattern->put(UnicodeString(pluralCount, -1, US_INV), pattern, status); 373 } 374 } 375 } 376 // If OOM occurred during the above code, then we want to report that back to the caller. 377 if (ec == U_MEMORY_ALLOCATION_ERROR) { 378 status = ec; 379 } 380 } 381 382 void 383 CurrencyPluralInfo::deleteHash(Hashtable* hTable) { 384 if ( hTable == nullptr ) { 385 return; 386 } 387 int32_t pos = UHASH_FIRST; 388 const UHashElement* element = nullptr; 389 while ( (element = hTable->nextElement(pos)) != nullptr ) { 390 const UHashTok valueTok = element->value; 391 const UnicodeString* value = (UnicodeString*)valueTok.pointer; 392 delete value; 393 } 394 delete hTable; 395 hTable = nullptr; 396 } 397 398 Hashtable* 399 CurrencyPluralInfo::initHash(UErrorCode& status) { 400 if (U_FAILURE(status)) { 401 return nullptr; 402 } 403 LocalPointer<Hashtable> hTable(new Hashtable(TRUE, status), status); 404 if (U_FAILURE(status)) { 405 return nullptr; 406 } 407 hTable->setValueComparator(ValueComparator); 408 return hTable.orphan(); 409 } 410 411 void 412 CurrencyPluralInfo::copyHash(const Hashtable* source, 413 Hashtable* target, 414 UErrorCode& status) { 415 if (U_FAILURE(status)) { 416 return; 417 } 418 int32_t pos = UHASH_FIRST; 419 const UHashElement* element = nullptr; 420 if (source) { 421 while ( (element = source->nextElement(pos)) != nullptr ) { 422 const UHashTok keyTok = element->key; 423 const UnicodeString* key = (UnicodeString*)keyTok.pointer; 424 const UHashTok valueTok = element->value; 425 const UnicodeString* value = (UnicodeString*)valueTok.pointer; 426 LocalPointer<UnicodeString> copy(new UnicodeString(*value), status); 427 if (U_FAILURE(status)) { 428 return; 429 } 430 // The HashTable owns the 'copy' object after the call to put(). 431 target->put(UnicodeString(*key), copy.orphan(), status); 432 if (U_FAILURE(status)) { 433 return; 434 } 435 } 436 } 437 } 438 439 U_NAMESPACE_END 440 441 #endif 442