1 // © 2016 and later: Unicode, Inc. and others.
2 // License & terms of use: http://www.unicode.org/copyright.html
3 /*
4 *******************************************************************************
5 * Copyright (C) 2008-2015, Google, International Business Machines Corporation
6 * and others. All Rights Reserved.
7 *******************************************************************************
8 */
9
10 #include "unicode/tmutfmt.h"
11
12 #if !UCONFIG_NO_FORMATTING
13
14 #include <utility>
15
16 #include "unicode/decimfmt.h"
17 #include "unicode/localpointer.h"
18 #include "plurrule_impl.h"
19 #include "uvector.h"
20 #include "bytesinkutil.h"
21 #include "charstr.h"
22 #include "cmemory.h"
23 #include "cstring.h"
24 #include "hash.h"
25 #include "ulocimp.h"
26 #include "uresimp.h"
27 #include "ureslocs.h"
28 #include "unicode/msgfmt.h"
29 #include "uassert.h"
30
31 #define LEFT_CURLY_BRACKET ((char16_t)0x007B)
32 #define RIGHT_CURLY_BRACKET ((char16_t)0x007D)
33 #define SPACE ((char16_t)0x0020)
34 #define DIGIT_ZERO ((char16_t)0x0030)
35 #define LOW_S ((char16_t)0x0073)
36 #define LOW_M ((char16_t)0x006D)
37 #define LOW_I ((char16_t)0x0069)
38 #define LOW_N ((char16_t)0x006E)
39 #define LOW_H ((char16_t)0x0068)
40 #define LOW_W ((char16_t)0x0077)
41 #define LOW_D ((char16_t)0x0064)
42 #define LOW_Y ((char16_t)0x0079)
43 #define LOW_Z ((char16_t)0x007A)
44 #define LOW_E ((char16_t)0x0065)
45 #define LOW_R ((char16_t)0x0072)
46 #define LOW_O ((char16_t)0x006F)
47 #define LOW_N ((char16_t)0x006E)
48 #define LOW_T ((char16_t)0x0074)
49
50
51 //TODO: define in compile time
52 //#define TMUTFMT_DEBUG 1
53
54 #ifdef TMUTFMT_DEBUG
55 #include <iostream>
56 #endif
57
58 U_NAMESPACE_BEGIN
59
60
61
62 UOBJECT_DEFINE_RTTI_IMPLEMENTATION(TimeUnitFormat)
63
64 static const char gUnitsTag[] = "units";
65 static const char gShortUnitsTag[] = "unitsShort";
66 static const char gTimeUnitYear[] = "year";
67 static const char gTimeUnitMonth[] = "month";
68 static const char gTimeUnitDay[] = "day";
69 static const char gTimeUnitWeek[] = "week";
70 static const char gTimeUnitHour[] = "hour";
71 static const char gTimeUnitMinute[] = "minute";
72 static const char gTimeUnitSecond[] = "second";
73 static const char gPluralCountOther[] = "other";
74
75 static const char16_t DEFAULT_PATTERN_FOR_SECOND[] = {LEFT_CURLY_BRACKET, DIGIT_ZERO, RIGHT_CURLY_BRACKET, SPACE, LOW_S, 0};
76 static const char16_t DEFAULT_PATTERN_FOR_MINUTE[] = {LEFT_CURLY_BRACKET, DIGIT_ZERO, RIGHT_CURLY_BRACKET, SPACE, LOW_M, LOW_I, LOW_N, 0};
77 static const char16_t DEFAULT_PATTERN_FOR_HOUR[] = {LEFT_CURLY_BRACKET, DIGIT_ZERO, RIGHT_CURLY_BRACKET, SPACE, LOW_H, 0};
78 static const char16_t DEFAULT_PATTERN_FOR_WEEK[] = {LEFT_CURLY_BRACKET, DIGIT_ZERO, RIGHT_CURLY_BRACKET, SPACE, LOW_W, 0};
79 static const char16_t DEFAULT_PATTERN_FOR_DAY[] = {LEFT_CURLY_BRACKET, DIGIT_ZERO, RIGHT_CURLY_BRACKET, SPACE, LOW_D, 0};
80 static const char16_t DEFAULT_PATTERN_FOR_MONTH[] = {LEFT_CURLY_BRACKET, DIGIT_ZERO, RIGHT_CURLY_BRACKET, SPACE, LOW_M, 0};
81 static const char16_t DEFAULT_PATTERN_FOR_YEAR[] = {LEFT_CURLY_BRACKET, DIGIT_ZERO, RIGHT_CURLY_BRACKET, SPACE, LOW_Y, 0};
82
83 static const char16_t PLURAL_COUNT_ZERO[] = {LOW_Z, LOW_E, LOW_R, LOW_O, 0};
84 static const char16_t PLURAL_COUNT_ONE[] = {LOW_O, LOW_N, LOW_E, 0};
85 static const char16_t PLURAL_COUNT_TWO[] = {LOW_T, LOW_W, LOW_O, 0};
86
TimeUnitFormat(UErrorCode & status)87 TimeUnitFormat::TimeUnitFormat(UErrorCode& status) {
88 initMeasureFormat(Locale::getDefault(), UMEASFMT_WIDTH_WIDE, nullptr, status);
89 create(UTMUTFMT_FULL_STYLE, status);
90 }
91
92
TimeUnitFormat(const Locale & locale,UErrorCode & status)93 TimeUnitFormat::TimeUnitFormat(const Locale& locale, UErrorCode& status) {
94 initMeasureFormat(locale, UMEASFMT_WIDTH_WIDE, nullptr, status);
95 create(UTMUTFMT_FULL_STYLE, status);
96 }
97
98
TimeUnitFormat(const Locale & locale,UTimeUnitFormatStyle style,UErrorCode & status)99 TimeUnitFormat::TimeUnitFormat(const Locale& locale, UTimeUnitFormatStyle style, UErrorCode& status) {
100 switch (style) {
101 case UTMUTFMT_FULL_STYLE:
102 initMeasureFormat(locale, UMEASFMT_WIDTH_WIDE, nullptr, status);
103 break;
104 case UTMUTFMT_ABBREVIATED_STYLE:
105 initMeasureFormat(locale, UMEASFMT_WIDTH_SHORT, nullptr, status);
106 break;
107 default:
108 initMeasureFormat(locale, UMEASFMT_WIDTH_WIDE, nullptr, status);
109 break;
110 }
111 create(style, status);
112 }
113
TimeUnitFormat(const TimeUnitFormat & other)114 TimeUnitFormat::TimeUnitFormat(const TimeUnitFormat& other)
115 : MeasureFormat(other),
116 fStyle(other.fStyle)
117 {
118 for (TimeUnit::UTimeUnitFields i = TimeUnit::UTIMEUNIT_YEAR;
119 i < TimeUnit::UTIMEUNIT_FIELD_COUNT;
120 i = (TimeUnit::UTimeUnitFields)(i+1)) {
121 UErrorCode status = U_ZERO_ERROR;
122 fTimeUnitToCountToPatterns[i] = initHash(status);
123 if (U_SUCCESS(status)) {
124 copyHash(other.fTimeUnitToCountToPatterns[i], fTimeUnitToCountToPatterns[i], status);
125 } else {
126 delete fTimeUnitToCountToPatterns[i];
127 fTimeUnitToCountToPatterns[i] = nullptr;
128 }
129 }
130 }
131
132
~TimeUnitFormat()133 TimeUnitFormat::~TimeUnitFormat() {
134 for (TimeUnit::UTimeUnitFields i = TimeUnit::UTIMEUNIT_YEAR;
135 i < TimeUnit::UTIMEUNIT_FIELD_COUNT;
136 i = (TimeUnit::UTimeUnitFields)(i+1)) {
137 deleteHash(fTimeUnitToCountToPatterns[i]);
138 fTimeUnitToCountToPatterns[i] = nullptr;
139 }
140 }
141
142
143 TimeUnitFormat*
clone() const144 TimeUnitFormat::clone() const {
145 return new TimeUnitFormat(*this);
146 }
147
148
149 TimeUnitFormat&
operator =(const TimeUnitFormat & other)150 TimeUnitFormat::operator=(const TimeUnitFormat& other) {
151 if (this == &other) {
152 return *this;
153 }
154 MeasureFormat::operator=(other);
155 for (TimeUnit::UTimeUnitFields i = TimeUnit::UTIMEUNIT_YEAR;
156 i < TimeUnit::UTIMEUNIT_FIELD_COUNT;
157 i = (TimeUnit::UTimeUnitFields)(i+1)) {
158 deleteHash(fTimeUnitToCountToPatterns[i]);
159 fTimeUnitToCountToPatterns[i] = nullptr;
160 }
161 for (TimeUnit::UTimeUnitFields i = TimeUnit::UTIMEUNIT_YEAR;
162 i < TimeUnit::UTIMEUNIT_FIELD_COUNT;
163 i = (TimeUnit::UTimeUnitFields)(i+1)) {
164 UErrorCode status = U_ZERO_ERROR;
165 fTimeUnitToCountToPatterns[i] = initHash(status);
166 if (U_SUCCESS(status)) {
167 copyHash(other.fTimeUnitToCountToPatterns[i], fTimeUnitToCountToPatterns[i], status);
168 } else {
169 delete fTimeUnitToCountToPatterns[i];
170 fTimeUnitToCountToPatterns[i] = nullptr;
171 }
172 }
173 fStyle = other.fStyle;
174 return *this;
175 }
176
177 void
parseObject(const UnicodeString & source,Formattable & result,ParsePosition & pos) const178 TimeUnitFormat::parseObject(const UnicodeString& source,
179 Formattable& result,
180 ParsePosition& pos) const {
181 Formattable resultNumber(0.0);
182 UBool withNumberFormat = false;
183 TimeUnit::UTimeUnitFields resultTimeUnit = TimeUnit::UTIMEUNIT_FIELD_COUNT;
184 int32_t oldPos = pos.getIndex();
185 int32_t newPos = -1;
186 int32_t longestParseDistance = 0;
187 UnicodeString* countOfLongestMatch = nullptr;
188 #ifdef TMUTFMT_DEBUG
189 char res[1000];
190 source.extract(0, source.length(), res, "UTF-8");
191 std::cout << "parse source: " << res << "\n";
192 #endif
193 // parse by iterating through all available patterns
194 // and looking for the longest match.
195 for (TimeUnit::UTimeUnitFields i = TimeUnit::UTIMEUNIT_YEAR;
196 i < TimeUnit::UTIMEUNIT_FIELD_COUNT;
197 i = (TimeUnit::UTimeUnitFields)(i+1)) {
198 Hashtable* countToPatterns = fTimeUnitToCountToPatterns[i];
199 int32_t elemPos = UHASH_FIRST;
200 const UHashElement* elem = nullptr;
201 while ((elem = countToPatterns->nextElement(elemPos)) != nullptr){
202 const UHashTok keyTok = elem->key;
203 UnicodeString* count = (UnicodeString*)keyTok.pointer;
204 #ifdef TMUTFMT_DEBUG
205 count->extract(0, count->length(), res, "UTF-8");
206 std::cout << "parse plural count: " << res << "\n";
207 #endif
208 const UHashTok valueTok = elem->value;
209 // the value is a pair of MessageFormat*
210 MessageFormat** patterns = (MessageFormat**)valueTok.pointer;
211 for (UTimeUnitFormatStyle style = UTMUTFMT_FULL_STYLE; style < UTMUTFMT_FORMAT_STYLE_COUNT;
212 style = (UTimeUnitFormatStyle)(style + 1)) {
213 MessageFormat* pattern = patterns[style];
214 pos.setErrorIndex(-1);
215 pos.setIndex(oldPos);
216 // see if we can parse
217 Formattable parsed;
218 pattern->parseObject(source, parsed, pos);
219 if (pos.getErrorIndex() != -1 || pos.getIndex() == oldPos) {
220 continue;
221 }
222 #ifdef TMUTFMT_DEBUG
223 std::cout << "parsed.getType: " << parsed.getType() << "\n";
224 #endif
225 Formattable tmpNumber(0.0);
226 if (pattern->getArgTypeCount() != 0) {
227 Formattable& temp = parsed[0];
228 if (temp.getType() == Formattable::kString) {
229 UnicodeString tmpString;
230 UErrorCode pStatus = U_ZERO_ERROR;
231 getNumberFormatInternal().parse(temp.getString(tmpString), tmpNumber, pStatus);
232 if (U_FAILURE(pStatus)) {
233 continue;
234 }
235 } else if (temp.isNumeric()) {
236 tmpNumber = temp;
237 } else {
238 continue;
239 }
240 }
241 int32_t parseDistance = pos.getIndex() - oldPos;
242 if (parseDistance > longestParseDistance) {
243 if (pattern->getArgTypeCount() != 0) {
244 resultNumber = tmpNumber;
245 withNumberFormat = true;
246 } else {
247 withNumberFormat = false;
248 }
249 resultTimeUnit = i;
250 newPos = pos.getIndex();
251 longestParseDistance = parseDistance;
252 countOfLongestMatch = count;
253 }
254 }
255 }
256 }
257 /* After find the longest match, parse the number.
258 * Result number could be null for the pattern without number pattern.
259 * such as unit pattern in Arabic.
260 * When result number is null, use plural rule to set the number.
261 */
262 if (withNumberFormat == false && longestParseDistance != 0) {
263 // set the number using plurrual count
264 if (0 == countOfLongestMatch->compare(PLURAL_COUNT_ZERO, 4)) {
265 resultNumber = Formattable(0.0);
266 } else if (0 == countOfLongestMatch->compare(PLURAL_COUNT_ONE, 3)) {
267 resultNumber = Formattable(1.0);
268 } else if (0 == countOfLongestMatch->compare(PLURAL_COUNT_TWO, 3)) {
269 resultNumber = Formattable(2.0);
270 } else {
271 // should not happen.
272 // TODO: how to handle?
273 resultNumber = Formattable(3.0);
274 }
275 }
276 if (longestParseDistance == 0) {
277 pos.setIndex(oldPos);
278 pos.setErrorIndex(0);
279 } else {
280 UErrorCode status = U_ZERO_ERROR;
281 LocalPointer<TimeUnitAmount> tmutamt(new TimeUnitAmount(resultNumber, resultTimeUnit, status), status);
282 if (U_SUCCESS(status)) {
283 result.adoptObject(tmutamt.orphan());
284 pos.setIndex(newPos);
285 pos.setErrorIndex(-1);
286 } else {
287 pos.setIndex(oldPos);
288 pos.setErrorIndex(0);
289 }
290 }
291 }
292
293 void
create(UTimeUnitFormatStyle style,UErrorCode & status)294 TimeUnitFormat::create(UTimeUnitFormatStyle style, UErrorCode& status) {
295 // fTimeUnitToCountToPatterns[] must have its elements initialized to nullptr first
296 // before checking for failure status.
297 for (TimeUnit::UTimeUnitFields i = TimeUnit::UTIMEUNIT_YEAR;
298 i < TimeUnit::UTIMEUNIT_FIELD_COUNT;
299 i = (TimeUnit::UTimeUnitFields)(i+1)) {
300 fTimeUnitToCountToPatterns[i] = nullptr;
301 }
302
303 if (U_FAILURE(status)) {
304 return;
305 }
306 if (style < UTMUTFMT_FULL_STYLE || style >= UTMUTFMT_FORMAT_STYLE_COUNT) {
307 status = U_ILLEGAL_ARGUMENT_ERROR;
308 return;
309 }
310 fStyle = style;
311
312 //TODO: format() and parseObj() are const member functions,
313 //so, can not do lazy initialization in C++.
314 //setup has to be done in constructors.
315 //and here, the behavior is not consistent with Java.
316 //In Java, create an empty instance does not setup locale as
317 //default locale. If it followed by setNumberFormat(),
318 //in format(), the locale will set up as the locale in fNumberFormat.
319 //But in C++, this sets the locale as the default locale.
320 setup(status);
321 }
322
323 void
setup(UErrorCode & err)324 TimeUnitFormat::setup(UErrorCode& err) {
325 initDataMembers(err);
326
327 UVector pluralCounts(nullptr, uhash_compareUnicodeString, 6, err);
328 LocalPointer<StringEnumeration> keywords(getPluralRules().getKeywords(err), err);
329 if (U_FAILURE(err)) {
330 return;
331 }
332 UnicodeString* pluralCount;
333 while ((pluralCount = const_cast<UnicodeString*>(keywords->snext(err))) != nullptr) {
334 pluralCounts.addElement(pluralCount, err);
335 }
336 readFromCurrentLocale(UTMUTFMT_FULL_STYLE, gUnitsTag, pluralCounts, err);
337 checkConsistency(UTMUTFMT_FULL_STYLE, gUnitsTag, err);
338 readFromCurrentLocale(UTMUTFMT_ABBREVIATED_STYLE, gShortUnitsTag, pluralCounts, err);
339 checkConsistency(UTMUTFMT_ABBREVIATED_STYLE, gShortUnitsTag, err);
340 }
341
342
343 void
initDataMembers(UErrorCode & err)344 TimeUnitFormat::initDataMembers(UErrorCode& err){
345 if (U_FAILURE(err)) {
346 return;
347 }
348 for (TimeUnit::UTimeUnitFields i = TimeUnit::UTIMEUNIT_YEAR;
349 i < TimeUnit::UTIMEUNIT_FIELD_COUNT;
350 i = (TimeUnit::UTimeUnitFields)(i+1)) {
351 deleteHash(fTimeUnitToCountToPatterns[i]);
352 fTimeUnitToCountToPatterns[i] = nullptr;
353 }
354 }
355
356 struct TimeUnitFormatReadSink : public ResourceSink {
357 TimeUnitFormat *timeUnitFormatObj;
358 const UVector &pluralCounts;
359 UTimeUnitFormatStyle style;
360 UBool beenHere;
361
TimeUnitFormatReadSinkTimeUnitFormatReadSink362 TimeUnitFormatReadSink(TimeUnitFormat *timeUnitFormatObj,
363 const UVector &pluralCounts, UTimeUnitFormatStyle style) :
364 timeUnitFormatObj(timeUnitFormatObj), pluralCounts(pluralCounts),
365 style(style), beenHere(false){}
366
367 virtual ~TimeUnitFormatReadSink();
368
putTimeUnitFormatReadSink369 virtual void put(const char *key, ResourceValue &value, UBool, UErrorCode &errorCode) override {
370 // Skip all put() calls except the first one -- discard all fallback data.
371 if (beenHere) {
372 return;
373 } else {
374 beenHere = true;
375 }
376
377 ResourceTable units = value.getTable(errorCode);
378 if (U_FAILURE(errorCode)) { return; }
379
380 for (int32_t i = 0; units.getKeyAndValue(i, key, value); ++i) {
381 const char* timeUnitName = key;
382 if (timeUnitName == nullptr) {
383 continue;
384 }
385
386 TimeUnit::UTimeUnitFields timeUnitField = TimeUnit::UTIMEUNIT_FIELD_COUNT;
387 if ( uprv_strcmp(timeUnitName, gTimeUnitYear) == 0 ) {
388 timeUnitField = TimeUnit::UTIMEUNIT_YEAR;
389 } else if ( uprv_strcmp(timeUnitName, gTimeUnitMonth) == 0 ) {
390 timeUnitField = TimeUnit::UTIMEUNIT_MONTH;
391 } else if ( uprv_strcmp(timeUnitName, gTimeUnitDay) == 0 ) {
392 timeUnitField = TimeUnit::UTIMEUNIT_DAY;
393 } else if ( uprv_strcmp(timeUnitName, gTimeUnitHour) == 0 ) {
394 timeUnitField = TimeUnit::UTIMEUNIT_HOUR;
395 } else if ( uprv_strcmp(timeUnitName, gTimeUnitMinute) == 0 ) {
396 timeUnitField = TimeUnit::UTIMEUNIT_MINUTE;
397 } else if ( uprv_strcmp(timeUnitName, gTimeUnitSecond) == 0 ) {
398 timeUnitField = TimeUnit::UTIMEUNIT_SECOND;
399 } else if ( uprv_strcmp(timeUnitName, gTimeUnitWeek) == 0 ) {
400 timeUnitField = TimeUnit::UTIMEUNIT_WEEK;
401 } else {
402 continue;
403 }
404 LocalPointer<Hashtable> localCountToPatterns;
405 Hashtable *countToPatterns =
406 timeUnitFormatObj->fTimeUnitToCountToPatterns[timeUnitField];
407 if (countToPatterns == nullptr) {
408 localCountToPatterns.adoptInsteadAndCheckErrorCode(
409 timeUnitFormatObj->initHash(errorCode), errorCode);
410 countToPatterns = localCountToPatterns.getAlias();
411 if (U_FAILURE(errorCode)) {
412 return;
413 }
414 }
415
416 ResourceTable countsToPatternTable = value.getTable(errorCode);
417 if (U_FAILURE(errorCode)) {
418 continue;
419 }
420 for (int32_t j = 0; countsToPatternTable.getKeyAndValue(j, key, value); ++j) {
421 errorCode = U_ZERO_ERROR;
422 UnicodeString pattern = value.getUnicodeString(errorCode);
423 if (U_FAILURE(errorCode)) {
424 continue;
425 }
426 UnicodeString pluralCountUniStr(key, -1, US_INV);
427 if (!pluralCounts.contains(&pluralCountUniStr)) {
428 continue;
429 }
430 LocalPointer<MessageFormat> messageFormat(new MessageFormat(
431 pattern, timeUnitFormatObj->getLocale(errorCode), errorCode), errorCode);
432 if (U_FAILURE(errorCode)) {
433 return;
434 }
435 MessageFormat** formatters =
436 (MessageFormat**)countToPatterns->get(pluralCountUniStr);
437 if (formatters == nullptr) {
438 LocalMemory<MessageFormat *> localFormatters(
439 (MessageFormat **)uprv_malloc(UTMUTFMT_FORMAT_STYLE_COUNT*sizeof(MessageFormat*)));
440 if (localFormatters.isNull()) {
441 errorCode = U_MEMORY_ALLOCATION_ERROR;
442 return;
443 }
444 localFormatters[UTMUTFMT_FULL_STYLE] = nullptr;
445 localFormatters[UTMUTFMT_ABBREVIATED_STYLE] = nullptr;
446 countToPatterns->put(pluralCountUniStr, localFormatters.getAlias(), errorCode);
447 if (U_FAILURE(errorCode)) {
448 return;
449 }
450 formatters = localFormatters.orphan();
451 }
452 formatters[style] = messageFormat.orphan();
453 }
454
455 if (timeUnitFormatObj->fTimeUnitToCountToPatterns[timeUnitField] == nullptr) {
456 timeUnitFormatObj->fTimeUnitToCountToPatterns[timeUnitField] = localCountToPatterns.orphan();
457 }
458 }
459 }
460
461 };
462
~TimeUnitFormatReadSink()463 TimeUnitFormatReadSink::~TimeUnitFormatReadSink() {}
464
465 void
readFromCurrentLocale(UTimeUnitFormatStyle style,const char * key,const UVector & pluralCounts,UErrorCode & err)466 TimeUnitFormat::readFromCurrentLocale(UTimeUnitFormatStyle style, const char* key,
467 const UVector& pluralCounts, UErrorCode& err) {
468 if (U_FAILURE(err)) {
469 return;
470 }
471 // fill timeUnitToCountToPatterns from resource file
472 // err is used to indicate wrong status except missing resource.
473 // status is an error code used in resource lookup.
474 // status does not affect "err".
475 UErrorCode status = U_ZERO_ERROR;
476 LocalUResourceBundlePointer rb(ures_open(U_ICUDATA_UNIT, getLocaleID(status), &status));
477
478 LocalUResourceBundlePointer unitsRes(ures_getByKey(rb.getAlias(), key, nullptr, &status));
479 ures_getByKey(unitsRes.getAlias(), "duration", unitsRes.getAlias(), &status);
480 if (U_FAILURE(status)) {
481 return;
482 }
483
484 TimeUnitFormatReadSink sink(this, pluralCounts, style);
485 ures_getAllItemsWithFallback(unitsRes.getAlias(), "", sink, status);
486 }
487
488 void
checkConsistency(UTimeUnitFormatStyle style,const char * key,UErrorCode & err)489 TimeUnitFormat::checkConsistency(UTimeUnitFormatStyle style, const char* key, UErrorCode& err) {
490 if (U_FAILURE(err)) {
491 return;
492 }
493 // there should be patterns for each plural rule in each time unit.
494 // For each time unit,
495 // for each plural rule, following is unit pattern fall-back rule:
496 // ( for example: "one" hour )
497 // look for its unit pattern in its locale tree.
498 // if pattern is not found in its own locale, such as de_DE,
499 // look for the pattern in its parent, such as de,
500 // keep looking till found or till root.
501 // if the pattern is not found in root either,
502 // fallback to plural count "other",
503 // look for the pattern of "other" in the locale tree:
504 // "de_DE" to "de" to "root".
505 // If not found, fall back to value of
506 // static variable DEFAULT_PATTERN_FOR_xxx, such as "{0} h".
507 //
508 // Following is consistency check to create pattern for each
509 // plural rule in each time unit using above fall-back rule.
510 //
511 LocalPointer<StringEnumeration> keywords(
512 getPluralRules().getKeywords(err), err);
513 const UnicodeString* pluralCount;
514 while (U_SUCCESS(err) && (pluralCount = keywords->snext(err)) != nullptr) {
515 for (int32_t i = 0; i < TimeUnit::UTIMEUNIT_FIELD_COUNT; ++i) {
516 // for each time unit,
517 // get all the patterns for each plural rule in this locale.
518 Hashtable* countToPatterns = fTimeUnitToCountToPatterns[i];
519 if ( countToPatterns == nullptr ) {
520 fTimeUnitToCountToPatterns[i] = countToPatterns = initHash(err);
521 if (U_FAILURE(err)) {
522 return;
523 }
524 }
525 MessageFormat** formatters = (MessageFormat**)countToPatterns->get(*pluralCount);
526 if( formatters == nullptr || formatters[style] == nullptr ) {
527 // look through parents
528 const char* localeName = getLocaleID(err);
529 CharString pluralCountChars;
530 pluralCountChars.appendInvariantChars(*pluralCount, err);
531 searchInLocaleChain(style, key, localeName,
532 (TimeUnit::UTimeUnitFields)i,
533 *pluralCount, pluralCountChars.data(),
534 countToPatterns, err);
535 }
536 // TODO: what to do with U_FAILURE(err) at this point.
537 // As is, the outer loop continues to run, but does nothing.
538 }
539 }
540 }
541
542
543
544 // srcPluralCount is the original plural count on which the pattern is
545 // searched for.
546 // searchPluralCount is the fallback plural count.
547 // For example, to search for pattern for ""one" hour",
548 // "one" is the srcPluralCount,
549 // if the pattern is not found even in root, fallback to
550 // using patterns of plural count "other",
551 // then, "other" is the searchPluralCount.
552 void
searchInLocaleChain(UTimeUnitFormatStyle style,const char * key,const char * localeName,TimeUnit::UTimeUnitFields srcTimeUnitField,const UnicodeString & srcPluralCount,const char * searchPluralCount,Hashtable * countToPatterns,UErrorCode & err)553 TimeUnitFormat::searchInLocaleChain(UTimeUnitFormatStyle style, const char* key, const char* localeName,
554 TimeUnit::UTimeUnitFields srcTimeUnitField,
555 const UnicodeString& srcPluralCount,
556 const char* searchPluralCount,
557 Hashtable* countToPatterns,
558 UErrorCode& err) {
559 if (U_FAILURE(err)) {
560 return;
561 }
562 UErrorCode status = U_ZERO_ERROR;
563 CharString parentLocale(localeName, status);
564 U_ASSERT(countToPatterns != nullptr);
565 for (;;) {
566 {
567 CharString tmp;
568 CharStringByteSink sink(&tmp);
569 ulocimp_getParent(parentLocale.data(), sink, &status);
570 parentLocale = std::move(tmp);
571 }
572 // look for pattern for srcPluralCount in locale tree
573 LocalUResourceBundlePointer rb(ures_open(U_ICUDATA_UNIT, parentLocale.data(), &status));
574 LocalUResourceBundlePointer unitsRes(ures_getByKey(rb.getAlias(), key, nullptr, &status));
575 const char* timeUnitName = getTimeUnitName(srcTimeUnitField, status);
576 LocalUResourceBundlePointer countsToPatternRB(ures_getByKey(unitsRes.getAlias(), timeUnitName, nullptr, &status));
577 const char16_t* pattern;
578 int32_t ptLength;
579 pattern = ures_getStringByKeyWithFallback(countsToPatternRB.getAlias(), searchPluralCount, &ptLength, &status);
580 if (U_SUCCESS(status)) {
581 //found
582 LocalPointer<MessageFormat> messageFormat(
583 new MessageFormat(UnicodeString(true, pattern, ptLength), getLocale(err), err), err);
584 if (U_FAILURE(err)) {
585 return;
586 }
587 MessageFormat** formatters = (MessageFormat**)countToPatterns->get(srcPluralCount);
588 if (formatters == nullptr) {
589 LocalMemory<MessageFormat *> localFormatters(
590 (MessageFormat**)uprv_malloc(UTMUTFMT_FORMAT_STYLE_COUNT*sizeof(MessageFormat*)));
591 formatters = localFormatters.getAlias();
592 localFormatters[UTMUTFMT_FULL_STYLE] = nullptr;
593 localFormatters[UTMUTFMT_ABBREVIATED_STYLE] = nullptr;
594 countToPatterns->put(srcPluralCount, localFormatters.orphan(), err);
595 if (U_FAILURE(err)) {
596 return;
597 }
598 }
599 //delete formatters[style];
600 formatters[style] = messageFormat.orphan();
601 return;
602 }
603 status = U_ZERO_ERROR;
604 if (parentLocale.isEmpty()) {
605 break;
606 }
607 }
608
609 // if no unitsShort resource was found even after fallback to root locale
610 // then search the units resource fallback from the current level to root
611 if ( parentLocale.isEmpty() && uprv_strcmp(key, gShortUnitsTag) == 0) {
612 #ifdef TMUTFMT_DEBUG
613 std::cout << "loop into searchInLocaleChain since Short-Long-Alternative \n";
614 #endif
615 CharString pLocale(localeName, -1, err);
616 // Add an underscore at the tail of locale name,
617 // so that searchInLocaleChain will check the current locale before falling back
618 pLocale.append('_', err);
619 searchInLocaleChain(style, gUnitsTag, pLocale.data(), srcTimeUnitField, srcPluralCount,
620 searchPluralCount, countToPatterns, err);
621 if (U_FAILURE(err)) {
622 return;
623 }
624 MessageFormat** formatters = (MessageFormat**)countToPatterns->get(srcPluralCount);
625 if (formatters != nullptr && formatters[style] != nullptr) {
626 return;
627 }
628 }
629
630 // if not found the pattern for this plural count at all,
631 // fall-back to plural count "other"
632 if ( uprv_strcmp(searchPluralCount, gPluralCountOther) == 0 ) {
633 // set default fall back the same as the resource in root
634 LocalPointer<MessageFormat> messageFormat;
635 const char16_t *pattern = nullptr;
636 if ( srcTimeUnitField == TimeUnit::UTIMEUNIT_SECOND ) {
637 pattern = DEFAULT_PATTERN_FOR_SECOND;
638 } else if ( srcTimeUnitField == TimeUnit::UTIMEUNIT_MINUTE ) {
639 pattern = DEFAULT_PATTERN_FOR_MINUTE;
640 } else if ( srcTimeUnitField == TimeUnit::UTIMEUNIT_HOUR ) {
641 pattern = DEFAULT_PATTERN_FOR_HOUR;
642 } else if ( srcTimeUnitField == TimeUnit::UTIMEUNIT_WEEK ) {
643 pattern = DEFAULT_PATTERN_FOR_WEEK;
644 } else if ( srcTimeUnitField == TimeUnit::UTIMEUNIT_DAY ) {
645 pattern = DEFAULT_PATTERN_FOR_DAY;
646 } else if ( srcTimeUnitField == TimeUnit::UTIMEUNIT_MONTH ) {
647 pattern = DEFAULT_PATTERN_FOR_MONTH;
648 } else if ( srcTimeUnitField == TimeUnit::UTIMEUNIT_YEAR ) {
649 pattern = DEFAULT_PATTERN_FOR_YEAR;
650 }
651 if (pattern != nullptr) {
652 messageFormat.adoptInsteadAndCheckErrorCode(
653 new MessageFormat(UnicodeString(true, pattern, -1), getLocale(err), err), err);
654 }
655 if (U_FAILURE(err)) {
656 return;
657 }
658 MessageFormat** formatters = (MessageFormat**)countToPatterns->get(srcPluralCount);
659 if (formatters == nullptr) {
660 LocalMemory<MessageFormat *> localFormatters (
661 (MessageFormat**)uprv_malloc(UTMUTFMT_FORMAT_STYLE_COUNT*sizeof(MessageFormat*)));
662 if (localFormatters.isNull()) {
663 err = U_MEMORY_ALLOCATION_ERROR;
664 return;
665 }
666 formatters = localFormatters.getAlias();
667 formatters[UTMUTFMT_FULL_STYLE] = nullptr;
668 formatters[UTMUTFMT_ABBREVIATED_STYLE] = nullptr;
669 countToPatterns->put(srcPluralCount, localFormatters.orphan(), err);
670 }
671 if (U_SUCCESS(err)) {
672 //delete formatters[style];
673 formatters[style] = messageFormat.orphan();
674 }
675 } else {
676 // fall back to rule "other", and search in parents
677 searchInLocaleChain(style, key, localeName, srcTimeUnitField, srcPluralCount,
678 gPluralCountOther, countToPatterns, err);
679 }
680 }
681
682 void
setLocale(const Locale & locale,UErrorCode & status)683 TimeUnitFormat::setLocale(const Locale& locale, UErrorCode& status) {
684 if (setMeasureFormatLocale(locale, status)) {
685 setup(status);
686 }
687 }
688
689
690 void
setNumberFormat(const NumberFormat & format,UErrorCode & status)691 TimeUnitFormat::setNumberFormat(const NumberFormat& format, UErrorCode& status){
692 if (U_FAILURE(status)) {
693 return;
694 }
695 adoptNumberFormat(format.clone(), status);
696 }
697
698
699 void
deleteHash(Hashtable * htable)700 TimeUnitFormat::deleteHash(Hashtable* htable) {
701 int32_t pos = UHASH_FIRST;
702 const UHashElement* element = nullptr;
703 if ( htable ) {
704 while ( (element = htable->nextElement(pos)) != nullptr ) {
705 const UHashTok valueTok = element->value;
706 const MessageFormat** value = (const MessageFormat**)valueTok.pointer;
707 delete value[UTMUTFMT_FULL_STYLE];
708 delete value[UTMUTFMT_ABBREVIATED_STYLE];
709 //delete[] value;
710 uprv_free(value);
711 }
712 }
713 delete htable;
714 }
715
716
717 void
copyHash(const Hashtable * source,Hashtable * target,UErrorCode & status)718 TimeUnitFormat::copyHash(const Hashtable* source, Hashtable* target, UErrorCode& status) {
719 if ( U_FAILURE(status) ) {
720 return;
721 }
722 int32_t pos = UHASH_FIRST;
723 const UHashElement* element = nullptr;
724 if ( source ) {
725 while ( (element = source->nextElement(pos)) != nullptr ) {
726 const UHashTok keyTok = element->key;
727 const UnicodeString* key = (UnicodeString*)keyTok.pointer;
728 const UHashTok valueTok = element->value;
729 const MessageFormat** value = (const MessageFormat**)valueTok.pointer;
730 MessageFormat** newVal = (MessageFormat**)uprv_malloc(UTMUTFMT_FORMAT_STYLE_COUNT*sizeof(MessageFormat*));
731 newVal[0] = value[0]->clone();
732 newVal[1] = value[1]->clone();
733 target->put(UnicodeString(*key), newVal, status);
734 if ( U_FAILURE(status) ) {
735 delete newVal[0];
736 delete newVal[1];
737 uprv_free(newVal);
738 return;
739 }
740 }
741 }
742 }
743
744
745 U_CDECL_BEGIN
746
747 /**
748 * set hash table value comparator
749 *
750 * @param val1 one value in comparison
751 * @param val2 the other value in comparison
752 * @return true if 2 values are the same, false otherwise
753 */
754 static UBool U_CALLCONV tmutfmtHashTableValueComparator(UHashTok val1, UHashTok val2);
755
756 static UBool
tmutfmtHashTableValueComparator(UHashTok val1,UHashTok val2)757 U_CALLCONV tmutfmtHashTableValueComparator(UHashTok val1, UHashTok val2) {
758 const MessageFormat** pattern1 = (const MessageFormat**)val1.pointer;
759 const MessageFormat** pattern2 = (const MessageFormat**)val2.pointer;
760 return *pattern1[0] == *pattern2[0] && *pattern1[1] == *pattern2[1];
761 }
762
763 U_CDECL_END
764
765 Hashtable*
initHash(UErrorCode & status)766 TimeUnitFormat::initHash(UErrorCode& status) {
767 if ( U_FAILURE(status) ) {
768 return nullptr;
769 }
770 Hashtable* hTable;
771 if ( (hTable = new Hashtable(true, status)) == nullptr ) {
772 status = U_MEMORY_ALLOCATION_ERROR;
773 return nullptr;
774 }
775 if ( U_FAILURE(status) ) {
776 delete hTable;
777 return nullptr;
778 }
779 hTable->setValueComparator(tmutfmtHashTableValueComparator);
780 return hTable;
781 }
782
783
784 const char*
getTimeUnitName(TimeUnit::UTimeUnitFields unitField,UErrorCode & status)785 TimeUnitFormat::getTimeUnitName(TimeUnit::UTimeUnitFields unitField,
786 UErrorCode& status) {
787 if (U_FAILURE(status)) {
788 return nullptr;
789 }
790 switch (unitField) {
791 case TimeUnit::UTIMEUNIT_YEAR:
792 return gTimeUnitYear;
793 case TimeUnit::UTIMEUNIT_MONTH:
794 return gTimeUnitMonth;
795 case TimeUnit::UTIMEUNIT_DAY:
796 return gTimeUnitDay;
797 case TimeUnit::UTIMEUNIT_WEEK:
798 return gTimeUnitWeek;
799 case TimeUnit::UTIMEUNIT_HOUR:
800 return gTimeUnitHour;
801 case TimeUnit::UTIMEUNIT_MINUTE:
802 return gTimeUnitMinute;
803 case TimeUnit::UTIMEUNIT_SECOND:
804 return gTimeUnitSecond;
805 default:
806 status = U_ILLEGAL_ARGUMENT_ERROR;
807 return nullptr;
808 }
809 }
810
811 U_NAMESPACE_END
812
813 #endif
814