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
30U_NAMESPACE_BEGIN
31
32static const UChar gNumberPatternSeparator = 0x3B; // ;
33
34U_CDECL_BEGIN
35
36/**
37 * @internal ICU 4.2
38 */
39static UBool U_CALLCONV ValueComparator(UHashTok val1, UHashTok val2);
40
41UBool
42U_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
48U_CDECL_END
49
50
51UOBJECT_DEFINE_RTTI_IMPLEMENTATION(CurrencyPluralInfo)
52
53static const UChar gDefaultCurrencyPluralPattern[] = {'0', '.', '#', '#', ' ', 0xA4, 0xA4, 0xA4, 0};
54static const UChar gTripleCurrencySign[] = {0xA4, 0xA4, 0xA4, 0};
55static const UChar gPluralCountOther[] = {0x6F, 0x74, 0x68, 0x65, 0x72, 0};
56static const UChar gPart0[] = {0x7B, 0x30, 0x7D, 0};
57static const UChar gPart1[] = {0x7B, 0x31, 0x7D, 0};
58
59static const char gNumberElementsTag[]="NumberElements";
60static const char gLatnTag[]="latn";
61static const char gPatternsTag[]="patterns";
62static const char gDecimalFormatTag[]="decimalFormat";
63static const char gCurrUnitPtnTag[]="CurrencyUnitPatterns";
64
65CurrencyPluralInfo::CurrencyPluralInfo(UErrorCode& status)
66: fPluralCountToCurrencyUnitPattern(nullptr),
67 fPluralRules(nullptr),
68 fLocale(nullptr),
69 fInternalStatus(U_ZERO_ERROR) {
70 initialize(Locale::getDefault(), status);
71}
72
73CurrencyPluralInfo::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
81CurrencyPluralInfo::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
90CurrencyPluralInfo&
91CurrencyPluralInfo::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
139CurrencyPluralInfo::~CurrencyPluralInfo() {
140 deleteHash(fPluralCountToCurrencyUnitPattern);
141 fPluralCountToCurrencyUnitPattern = nullptr;
142 delete fPluralRules;
143 delete fLocale;
144 fPluralRules = nullptr;
145 fLocale = nullptr;
146}
147
148UBool
149CurrencyPluralInfo::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
167CurrencyPluralInfo*
168CurrencyPluralInfo::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
179const PluralRules*
180CurrencyPluralInfo::getPluralRules() const {
181 return fPluralRules;
182}
183
184UnicodeString&
185CurrencyPluralInfo::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
209const Locale&
210CurrencyPluralInfo::getLocale() const {
211 return *fLocale;
212}
213
214void
215CurrencyPluralInfo::setPluralRules(const UnicodeString& ruleDescription,
216 UErrorCode& status) {
217 if (U_SUCCESS(status)) {
218 delete fPluralRules;
219 fPluralRules = PluralRules::createRules(ruleDescription, status);
220 }
221}
222
223void
224CurrencyPluralInfo::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
240void
241CurrencyPluralInfo::setLocale(const Locale& loc, UErrorCode& status) {
242 initialize(loc, status);
243}
244
245void
246CurrencyPluralInfo::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
270void
271CurrencyPluralInfo::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
382void
383CurrencyPluralInfo::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
398Hashtable*
399CurrencyPluralInfo::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
411void
412CurrencyPluralInfo::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
439U_NAMESPACE_END
440
441#endif
442