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
ValueComparator(UHashTok val1,UHashTok val2)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
CurrencyPluralInfo(UErrorCode & status)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
CurrencyPluralInfo(const Locale & locale,UErrorCode & status)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
CurrencyPluralInfo(const CurrencyPluralInfo & info)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&
operator =(const CurrencyPluralInfo & info)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
~CurrencyPluralInfo()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
operator ==(const CurrencyPluralInfo & info) const149 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*
clone() const168 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*
getPluralRules() const180 CurrencyPluralInfo::getPluralRules() const {
181 return fPluralRules;
182 }
183
184 UnicodeString&
getCurrencyPluralPattern(const UnicodeString & pluralCount,UnicodeString & result) const185 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&
getLocale() const210 CurrencyPluralInfo::getLocale() const {
211 return *fLocale;
212 }
213
214 void
setPluralRules(const UnicodeString & ruleDescription,UErrorCode & status)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
setCurrencyPluralPattern(const UnicodeString & pluralCount,const UnicodeString & pattern,UErrorCode & status)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
setLocale(const Locale & loc,UErrorCode & status)241 CurrencyPluralInfo::setLocale(const Locale& loc, UErrorCode& status) {
242 initialize(loc, status);
243 }
244
245 void
initialize(const Locale & loc,UErrorCode & status)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
setupCurrencyPluralPattern(const Locale & loc,UErrorCode & status)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
deleteHash(Hashtable * hTable)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*
initHash(UErrorCode & status)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
copyHash(const Hashtable * source,Hashtable * target,UErrorCode & status)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