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