1 // © 2017 and later: Unicode, Inc. and others.
2 // License & terms of use: http://www.unicode.org/copyright.html
3 
4 #include "unicode/utypes.h"
5 
6 #if !UCONFIG_NO_FORMATTING
7 
8 #include "unicode/simpleformatter.h"
9 #include "unicode/ures.h"
10 #include "ureslocs.h"
11 #include "charstr.h"
12 #include "uresimp.h"
13 #include "measunit_impl.h"
14 #include "number_longnames.h"
15 #include "number_microprops.h"
16 #include <algorithm>
17 #include "cstring.h"
18 #include "util.h"
19 
20 using namespace icu;
21 using namespace icu::number;
22 using namespace icu::number::impl;
23 
24 namespace {
25 
26 /**
27  * Display Name (this format has no placeholder).
28  *
29  * Used as an index into the LongNameHandler::simpleFormats array. Units
30  * resources cover the normal set of PluralRules keys, as well as `dnam` and
31  * `per` forms.
32  */
33 constexpr int32_t DNAM_INDEX = StandardPlural::Form::COUNT;
34 /**
35  * "per" form (e.g. "{0} per day" is day's "per" form).
36  *
37  * Used as an index into the LongNameHandler::simpleFormats array. Units
38  * resources cover the normal set of PluralRules keys, as well as `dnam` and
39  * `per` forms.
40  */
41 constexpr int32_t PER_INDEX = StandardPlural::Form::COUNT + 1;
42 // Number of keys in the array populated by PluralTableSink.
43 constexpr int32_t ARRAY_LENGTH = StandardPlural::Form::COUNT + 2;
44 
getIndex(const char * pluralKeyword,UErrorCode & status)45 static int32_t getIndex(const char* pluralKeyword, UErrorCode& status) {
46     // pluralKeyword can also be "dnam" or "per"
47     if (uprv_strcmp(pluralKeyword, "dnam") == 0) {
48         return DNAM_INDEX;
49     } else if (uprv_strcmp(pluralKeyword, "per") == 0) {
50         return PER_INDEX;
51     } else {
52         StandardPlural::Form plural = StandardPlural::fromString(pluralKeyword, status);
53         return plural;
54     }
55 }
56 
57 // Selects a string out of the `strings` array which corresponds to the
58 // specified plural form, with fallback to the OTHER form.
59 //
60 // The `strings` array must have ARRAY_LENGTH items: one corresponding to each
61 // of the plural forms, plus a display name ("dnam") and a "per" form.
getWithPlural(const UnicodeString * strings,StandardPlural::Form plural,UErrorCode & status)62 static UnicodeString getWithPlural(
63         const UnicodeString* strings,
64         StandardPlural::Form plural,
65         UErrorCode& status) {
66     UnicodeString result = strings[plural];
67     if (result.isBogus()) {
68         result = strings[StandardPlural::Form::OTHER];
69     }
70     if (result.isBogus()) {
71         // There should always be data in the "other" plural variant.
72         status = U_INTERNAL_PROGRAM_ERROR;
73     }
74     return result;
75 }
76 
77 
78 //////////////////////////
79 /// BEGIN DATA LOADING ///
80 //////////////////////////
81 
82 class PluralTableSink : public ResourceSink {
83   public:
PluralTableSink(UnicodeString * outArray)84     explicit PluralTableSink(UnicodeString *outArray) : outArray(outArray) {
85         // Initialize the array to bogus strings.
86         for (int32_t i = 0; i < ARRAY_LENGTH; i++) {
87             outArray[i].setToBogus();
88         }
89     }
90 
put(const char * key,ResourceValue & value,UBool,UErrorCode & status)91     void put(const char *key, ResourceValue &value, UBool /*noFallback*/, UErrorCode &status) U_OVERRIDE {
92         ResourceTable pluralsTable = value.getTable(status);
93         if (U_FAILURE(status)) { return; }
94         for (int32_t i = 0; pluralsTable.getKeyAndValue(i, key, value); ++i) {
95             int32_t index = getIndex(key, status);
96             if (U_FAILURE(status)) { return; }
97             if (!outArray[index].isBogus()) {
98                 continue;
99             }
100             outArray[index] = value.getUnicodeString(status);
101             if (U_FAILURE(status)) { return; }
102         }
103     }
104 
105   private:
106     UnicodeString *outArray;
107 };
108 
109 // NOTE: outArray MUST have room for all StandardPlural values.  No bounds checking is performed.
110 
111 /**
112  * Populates outArray with `locale`-specific values for `unit` through use of
113  * PluralTableSink. Only the set of basic units are supported!
114  *
115  * Reading from resources *unitsNarrow* and *unitsShort* (for width
116  * UNUM_UNIT_WIDTH_NARROW), or just *unitsShort* (for width
117  * UNUM_UNIT_WIDTH_SHORT). For other widths, it reads just "units".
118  *
119  * @param unit must have a type and subtype (i.e. it must be a unit listed in
120  *     gTypes and gSubTypes in measunit.cpp).
121  * @param outArray must be of fixed length ARRAY_LENGTH.
122  */
getMeasureData(const Locale & locale,const MeasureUnit & unit,const UNumberUnitWidth & width,UnicodeString * outArray,UErrorCode & status)123 void getMeasureData(const Locale &locale, const MeasureUnit &unit, const UNumberUnitWidth &width,
124                     UnicodeString *outArray, UErrorCode &status) {
125     PluralTableSink sink(outArray);
126     LocalUResourceBundlePointer unitsBundle(ures_open(U_ICUDATA_UNIT, locale.getName(), &status));
127     if (U_FAILURE(status)) { return; }
128 
129     // Map duration-year-person, duration-week-person, etc. to duration-year, duration-week, ...
130     // TODO(ICU-20400): Get duration-*-person data properly with aliases.
131     StringPiece subtypeForResource;
132     int32_t subtypeLen = static_cast<int32_t>(uprv_strlen(unit.getSubtype()));
133     if (subtypeLen > 7 && uprv_strcmp(unit.getSubtype() + subtypeLen - 7, "-person") == 0) {
134         subtypeForResource = {unit.getSubtype(), subtypeLen - 7};
135     } else {
136         subtypeForResource = unit.getSubtype();
137     }
138 
139     CharString key;
140     key.append("units", status);
141     if (width == UNUM_UNIT_WIDTH_NARROW) {
142         key.append("Narrow", status);
143     } else if (width == UNUM_UNIT_WIDTH_SHORT) {
144         key.append("Short", status);
145     }
146     key.append("/", status);
147     key.append(unit.getType(), status);
148     key.append("/", status);
149     key.append(subtypeForResource, status);
150 
151     UErrorCode localStatus = U_ZERO_ERROR;
152     ures_getAllItemsWithFallback(unitsBundle.getAlias(), key.data(), sink, localStatus);
153     if (width == UNUM_UNIT_WIDTH_SHORT) {
154         if (U_FAILURE(localStatus)) {
155             status = localStatus;
156         }
157         return;
158     }
159 
160     // TODO(ICU-13353): The fallback to short does not work in ICU4C.
161     // Manually fall back to short (this is done automatically in Java).
162     key.clear();
163     key.append("unitsShort/", status);
164     key.append(unit.getType(), status);
165     key.append("/", status);
166     key.append(subtypeForResource, status);
167     ures_getAllItemsWithFallback(unitsBundle.getAlias(), key.data(), sink, status);
168 }
169 
getCurrencyLongNameData(const Locale & locale,const CurrencyUnit & currency,UnicodeString * outArray,UErrorCode & status)170 void getCurrencyLongNameData(const Locale &locale, const CurrencyUnit &currency, UnicodeString *outArray,
171                              UErrorCode &status) {
172     // In ICU4J, this method gets a CurrencyData from CurrencyData.provider.
173     // TODO(ICU4J): Implement this without going through CurrencyData, like in ICU4C?
174     PluralTableSink sink(outArray);
175     LocalUResourceBundlePointer unitsBundle(ures_open(U_ICUDATA_CURR, locale.getName(), &status));
176     if (U_FAILURE(status)) { return; }
177     ures_getAllItemsWithFallback(unitsBundle.getAlias(), "CurrencyUnitPatterns", sink, status);
178     if (U_FAILURE(status)) { return; }
179     for (int32_t i = 0; i < StandardPlural::Form::COUNT; i++) {
180         UnicodeString &pattern = outArray[i];
181         if (pattern.isBogus()) {
182             continue;
183         }
184         int32_t longNameLen = 0;
185         const char16_t *longName = ucurr_getPluralName(
186                 currency.getISOCurrency(),
187                 locale.getName(),
188                 nullptr /* isChoiceFormat */,
189                 StandardPlural::getKeyword(static_cast<StandardPlural::Form>(i)),
190                 &longNameLen,
191                 &status);
192         // Example pattern from data: "{0} {1}"
193         // Example output after find-and-replace: "{0} US dollars"
194         pattern.findAndReplace(UnicodeString(u"{1}"), UnicodeString(longName, longNameLen));
195     }
196 }
197 
getPerUnitFormat(const Locale & locale,const UNumberUnitWidth & width,UErrorCode & status)198 UnicodeString getPerUnitFormat(const Locale& locale, const UNumberUnitWidth &width, UErrorCode& status) {
199     LocalUResourceBundlePointer unitsBundle(ures_open(U_ICUDATA_UNIT, locale.getName(), &status));
200     if (U_FAILURE(status)) { return {}; }
201     CharString key;
202     key.append("units", status);
203     if (width == UNUM_UNIT_WIDTH_NARROW) {
204         key.append("Narrow", status);
205     } else if (width == UNUM_UNIT_WIDTH_SHORT) {
206         key.append("Short", status);
207     }
208     key.append("/compound/per", status);
209     int32_t len = 0;
210     const UChar* ptr = ures_getStringByKeyWithFallback(unitsBundle.getAlias(), key.data(), &len, &status);
211     return UnicodeString(ptr, len);
212 }
213 
214 ////////////////////////
215 /// END DATA LOADING ///
216 ////////////////////////
217 
218 } // namespace
219 
forMeasureUnit(const Locale & loc,const MeasureUnit & unitRef,const MeasureUnit & perUnit,const UNumberUnitWidth & width,const PluralRules * rules,const MicroPropsGenerator * parent,LongNameHandler * fillIn,UErrorCode & status)220 void LongNameHandler::forMeasureUnit(const Locale &loc, const MeasureUnit &unitRef,
221                                      const MeasureUnit &perUnit, const UNumberUnitWidth &width,
222                                      const PluralRules *rules, const MicroPropsGenerator *parent,
223                                      LongNameHandler *fillIn, UErrorCode &status) {
224     // Not valid for mixed units that aren't built-in units, and there should
225     // not be any built-in mixed units!
226     U_ASSERT(uprv_strcmp(unitRef.getType(), "") != 0 ||
227              unitRef.getComplexity(status) != UMEASURE_UNIT_MIXED);
228     U_ASSERT(fillIn != nullptr);
229 
230     MeasureUnit unit = unitRef;
231     if (uprv_strcmp(perUnit.getType(), "none") != 0) {
232         // Compound unit: first try to simplify (e.g., meters per second is its own unit).
233         MeasureUnit simplified = unit.product(perUnit.reciprocal(status), status);
234         if (uprv_strcmp(simplified.getType(), "") != 0) {
235             unit = simplified;
236         } else {
237             // No simplified form is available.
238             forCompoundUnit(loc, unit, perUnit, width, rules, parent, fillIn, status);
239             return;
240         }
241     }
242 
243     if (uprv_strcmp(unit.getType(), "") == 0) {
244         // TODO(ICU-20941): Unsanctioned unit. Not yet fully supported. Set an
245         // error code. Once we support not-built-in units here, unitRef may be
246         // anything, but if not built-in, perUnit has to be "none".
247         status = U_UNSUPPORTED_ERROR;
248         return;
249     }
250 
251     UnicodeString simpleFormats[ARRAY_LENGTH];
252     getMeasureData(loc, unit, width, simpleFormats, status);
253     if (U_FAILURE(status)) {
254         return;
255     }
256     fillIn->rules = rules;
257     fillIn->parent = parent;
258     fillIn->simpleFormatsToModifiers(simpleFormats, {UFIELD_CATEGORY_NUMBER, UNUM_MEASURE_UNIT_FIELD},
259                                      status);
260 }
261 
forCompoundUnit(const Locale & loc,const MeasureUnit & unit,const MeasureUnit & perUnit,const UNumberUnitWidth & width,const PluralRules * rules,const MicroPropsGenerator * parent,LongNameHandler * fillIn,UErrorCode & status)262 void LongNameHandler::forCompoundUnit(const Locale &loc, const MeasureUnit &unit,
263                                       const MeasureUnit &perUnit, const UNumberUnitWidth &width,
264                                       const PluralRules *rules, const MicroPropsGenerator *parent,
265                                       LongNameHandler *fillIn, UErrorCode &status) {
266     if (uprv_strcmp(unit.getType(), "") == 0 || uprv_strcmp(perUnit.getType(), "") == 0) {
267         // TODO(ICU-20941): Unsanctioned unit. Not yet fully supported. Set an
268         // error code. Once we support not-built-in units here, unitRef may be
269         // anything, but if not built-in, perUnit has to be "none".
270         status = U_UNSUPPORTED_ERROR;
271         return;
272     }
273     if (fillIn == nullptr) {
274         status = U_INTERNAL_PROGRAM_ERROR;
275         return;
276     }
277     UnicodeString primaryData[ARRAY_LENGTH];
278     getMeasureData(loc, unit, width, primaryData, status);
279     if (U_FAILURE(status)) {
280         return;
281     }
282     UnicodeString secondaryData[ARRAY_LENGTH];
283     getMeasureData(loc, perUnit, width, secondaryData, status);
284     if (U_FAILURE(status)) {
285         return;
286     }
287 
288     UnicodeString perUnitFormat;
289     if (!secondaryData[PER_INDEX].isBogus()) {
290         perUnitFormat = secondaryData[PER_INDEX];
291     } else {
292         UnicodeString rawPerUnitFormat = getPerUnitFormat(loc, width, status);
293         if (U_FAILURE(status)) {
294             return;
295         }
296         // rawPerUnitFormat is something like "{0}/{1}"; we need to substitute in the secondary unit.
297         SimpleFormatter compiled(rawPerUnitFormat, 2, 2, status);
298         if (U_FAILURE(status)) {
299             return;
300         }
301         UnicodeString secondaryFormat = getWithPlural(secondaryData, StandardPlural::Form::ONE, status);
302         if (U_FAILURE(status)) {
303             return;
304         }
305         // Some "one" pattern may not contain "{0}". For example in "ar" or "ne" locale.
306         SimpleFormatter secondaryCompiled(secondaryFormat, 0, 1, status);
307         if (U_FAILURE(status)) {
308             return;
309         }
310         UnicodeString secondaryString = secondaryCompiled.getTextWithNoArguments().trim();
311         // TODO: Why does UnicodeString need to be explicit in the following line?
312         compiled.format(UnicodeString(u"{0}"), secondaryString, perUnitFormat, status);
313         if (U_FAILURE(status)) {
314             return;
315         }
316     }
317     fillIn->rules = rules;
318     fillIn->parent = parent;
319     fillIn->multiSimpleFormatsToModifiers(primaryData, perUnitFormat,
320                                           {UFIELD_CATEGORY_NUMBER, UNUM_MEASURE_UNIT_FIELD}, status);
321 }
322 
getUnitDisplayName(const Locale & loc,const MeasureUnit & unit,UNumberUnitWidth width,UErrorCode & status)323 UnicodeString LongNameHandler::getUnitDisplayName(
324         const Locale& loc,
325         const MeasureUnit& unit,
326         UNumberUnitWidth width,
327         UErrorCode& status) {
328     if (U_FAILURE(status)) {
329         return ICU_Utility::makeBogusString();
330     }
331     UnicodeString simpleFormats[ARRAY_LENGTH];
332     getMeasureData(loc, unit, width, simpleFormats, status);
333     return simpleFormats[DNAM_INDEX];
334 }
335 
getUnitPattern(const Locale & loc,const MeasureUnit & unit,UNumberUnitWidth width,StandardPlural::Form pluralForm,UErrorCode & status)336 UnicodeString LongNameHandler::getUnitPattern(
337         const Locale& loc,
338         const MeasureUnit& unit,
339         UNumberUnitWidth width,
340         StandardPlural::Form pluralForm,
341         UErrorCode& status) {
342     if (U_FAILURE(status)) {
343         return ICU_Utility::makeBogusString();
344     }
345     UnicodeString simpleFormats[ARRAY_LENGTH];
346     getMeasureData(loc, unit, width, simpleFormats, status);
347     // The above already handles fallback from other widths to short
348     if (U_FAILURE(status)) {
349         return ICU_Utility::makeBogusString();
350     }
351     // Now handle fallback from other plural forms to OTHER
352     return (!(simpleFormats[pluralForm]).isBogus())? simpleFormats[pluralForm]:
353             simpleFormats[StandardPlural::Form::OTHER];
354 }
355 
forCurrencyLongNames(const Locale & loc,const CurrencyUnit & currency,const PluralRules * rules,const MicroPropsGenerator * parent,UErrorCode & status)356 LongNameHandler* LongNameHandler::forCurrencyLongNames(const Locale &loc, const CurrencyUnit &currency,
357                                                       const PluralRules *rules,
358                                                       const MicroPropsGenerator *parent,
359                                                       UErrorCode &status) {
360     auto* result = new LongNameHandler(rules, parent);
361     if (result == nullptr) {
362         status = U_MEMORY_ALLOCATION_ERROR;
363         return nullptr;
364     }
365     UnicodeString simpleFormats[ARRAY_LENGTH];
366     getCurrencyLongNameData(loc, currency, simpleFormats, status);
367     if (U_FAILURE(status)) { return nullptr; }
368     result->simpleFormatsToModifiers(simpleFormats, {UFIELD_CATEGORY_NUMBER, UNUM_CURRENCY_FIELD}, status);
369     return result;
370 }
371 
simpleFormatsToModifiers(const UnicodeString * simpleFormats,Field field,UErrorCode & status)372 void LongNameHandler::simpleFormatsToModifiers(const UnicodeString *simpleFormats, Field field,
373                                                UErrorCode &status) {
374     for (int32_t i = 0; i < StandardPlural::Form::COUNT; i++) {
375         StandardPlural::Form plural = static_cast<StandardPlural::Form>(i);
376         UnicodeString simpleFormat = getWithPlural(simpleFormats, plural, status);
377         if (U_FAILURE(status)) { return; }
378         SimpleFormatter compiledFormatter(simpleFormat, 0, 1, status);
379         if (U_FAILURE(status)) { return; }
380         fModifiers[i] = SimpleModifier(compiledFormatter, field, false, {this, SIGNUM_POS_ZERO, plural});
381     }
382 }
383 
multiSimpleFormatsToModifiers(const UnicodeString * leadFormats,UnicodeString trailFormat,Field field,UErrorCode & status)384 void LongNameHandler::multiSimpleFormatsToModifiers(const UnicodeString *leadFormats, UnicodeString trailFormat,
385                                                     Field field, UErrorCode &status) {
386     SimpleFormatter trailCompiled(trailFormat, 1, 1, status);
387     if (U_FAILURE(status)) { return; }
388     for (int32_t i = 0; i < StandardPlural::Form::COUNT; i++) {
389         StandardPlural::Form plural = static_cast<StandardPlural::Form>(i);
390         UnicodeString leadFormat = getWithPlural(leadFormats, plural, status);
391         if (U_FAILURE(status)) { return; }
392         UnicodeString compoundFormat;
393         trailCompiled.format(leadFormat, compoundFormat, status);
394         if (U_FAILURE(status)) { return; }
395         SimpleFormatter compoundCompiled(compoundFormat, 0, 1, status);
396         if (U_FAILURE(status)) { return; }
397         fModifiers[i] = SimpleModifier(compoundCompiled, field, false, {this, SIGNUM_POS_ZERO, plural});
398     }
399 }
400 
processQuantity(DecimalQuantity & quantity,MicroProps & micros,UErrorCode & status) const401 void LongNameHandler::processQuantity(DecimalQuantity &quantity, MicroProps &micros,
402                                       UErrorCode &status) const {
403     if (parent != NULL) {
404         parent->processQuantity(quantity, micros, status);
405     }
406     StandardPlural::Form pluralForm = utils::getPluralSafe(micros.rounder, rules, quantity, status);
407     micros.modOuter = &fModifiers[pluralForm];
408 }
409 
getModifier(Signum,StandardPlural::Form plural) const410 const Modifier* LongNameHandler::getModifier(Signum /*signum*/, StandardPlural::Form plural) const {
411     return &fModifiers[plural];
412 }
413 
forMeasureUnit(const Locale & loc,const MeasureUnit & mixedUnit,const UNumberUnitWidth & width,const PluralRules * rules,const MicroPropsGenerator * parent,MixedUnitLongNameHandler * fillIn,UErrorCode & status)414 void MixedUnitLongNameHandler::forMeasureUnit(const Locale &loc, const MeasureUnit &mixedUnit,
415                                               const UNumberUnitWidth &width, const PluralRules *rules,
416                                               const MicroPropsGenerator *parent,
417                                               MixedUnitLongNameHandler *fillIn, UErrorCode &status) {
418     U_ASSERT(mixedUnit.getComplexity(status) == UMEASURE_UNIT_MIXED);
419     U_ASSERT(fillIn != nullptr);
420 
421     MeasureUnitImpl temp;
422     const MeasureUnitImpl& impl = MeasureUnitImpl::forMeasureUnit(mixedUnit, temp, status);
423     fillIn->fMixedUnitCount = impl.units.length();
424     fillIn->fMixedUnitData.adoptInstead(new UnicodeString[fillIn->fMixedUnitCount * ARRAY_LENGTH]);
425     for (int32_t i = 0; i < fillIn->fMixedUnitCount; i++) {
426         // Grab data for each of the components.
427         UnicodeString *unitData = &fillIn->fMixedUnitData[i * ARRAY_LENGTH];
428         getMeasureData(loc, impl.units[i]->build(status), width, unitData, status);
429     }
430 
431     UListFormatterWidth listWidth = ULISTFMT_WIDTH_SHORT;
432     if (width == UNUM_UNIT_WIDTH_NARROW) {
433         listWidth = ULISTFMT_WIDTH_NARROW;
434     } else if (width == UNUM_UNIT_WIDTH_FULL_NAME) {
435         // This might be the same as SHORT in most languages:
436         listWidth = ULISTFMT_WIDTH_WIDE;
437     }
438     fillIn->fListFormatter.adoptInsteadAndCheckErrorCode(
439         ListFormatter::createInstance(loc, ULISTFMT_TYPE_UNITS, listWidth, status), status);
440     fillIn->rules = rules;
441     fillIn->parent = parent;
442 
443     // We need a localised NumberFormatter for the integers of the bigger units
444     // (providing Arabic numerals, for example).
445     fillIn->fIntegerFormatter = NumberFormatter::withLocale(loc);
446 }
447 
processQuantity(DecimalQuantity & quantity,MicroProps & micros,UErrorCode & status) const448 void MixedUnitLongNameHandler::processQuantity(DecimalQuantity &quantity, MicroProps &micros,
449                                                UErrorCode &status) const {
450     U_ASSERT(fMixedUnitCount > 1);
451     if (parent != nullptr) {
452         parent->processQuantity(quantity, micros, status);
453     }
454     micros.modOuter = getMixedUnitModifier(quantity, micros, status);
455 }
456 
getMixedUnitModifier(DecimalQuantity & quantity,MicroProps & micros,UErrorCode & status) const457 const Modifier *MixedUnitLongNameHandler::getMixedUnitModifier(DecimalQuantity &quantity,
458                                                                MicroProps &micros,
459                                                                UErrorCode &status) const {
460     if (micros.mixedMeasuresCount == 0) {
461         U_ASSERT(micros.mixedMeasuresCount > 0); // Mixed unit: we must have more than one unit value
462         status = U_UNSUPPORTED_ERROR;
463         return &micros.helpers.emptyWeakModifier;
464     }
465     // If we don't have at least one mixedMeasure, the LongNameHandler would be
466     // sufficient and we shouldn't be running MixedUnitLongNameHandler code:
467     U_ASSERT(micros.mixedMeasuresCount > 0);
468     // mixedMeasures does not contain the last value:
469     U_ASSERT(fMixedUnitCount == micros.mixedMeasuresCount + 1);
470     U_ASSERT(fListFormatter.isValid());
471 
472     // Algorithm:
473     //
474     // For the mixed-units measurement of: "3 yard, 1 foot, 2.6 inch", we should
475     // find "3 yard" and "1 foot" in micros.mixedMeasures.
476     //
477     // Obtain long-names with plural forms corresponding to measure values:
478     //   * {0} yards, {0} foot, {0} inches
479     //
480     // Format the integer values appropriately and modify with the format
481     // strings:
482     //   - 3 yards, 1 foot
483     //
484     // Use ListFormatter to combine, with one placeholder:
485     //   - 3 yards, 1 foot and {0} inches
486     //
487     // Return a SimpleModifier for this pattern, letting the rest of the
488     // pipeline take care of the remaining inches.
489 
490     LocalArray<UnicodeString> outputMeasuresList(new UnicodeString[fMixedUnitCount], status);
491     if (U_FAILURE(status)) {
492         return &micros.helpers.emptyWeakModifier;
493     }
494 
495     for (int32_t i = 0; i < micros.mixedMeasuresCount; i++) {
496         DecimalQuantity fdec;
497         fdec.setToLong(micros.mixedMeasures[i]);
498         if (i > 0 && fdec.isNegative()) {
499             // If numbers are negative, only the first number needs to have its
500             // negative sign formatted.
501             fdec.negate();
502         }
503         StandardPlural::Form pluralForm = utils::getStandardPlural(rules, fdec);
504 
505         UnicodeString simpleFormat =
506             getWithPlural(&fMixedUnitData[i * ARRAY_LENGTH], pluralForm, status);
507         SimpleFormatter compiledFormatter(simpleFormat, 0, 1, status);
508 
509         UnicodeString num;
510         auto appendable = UnicodeStringAppendable(num);
511         fIntegerFormatter.formatDecimalQuantity(fdec, status).appendTo(appendable, status);
512         compiledFormatter.format(num, outputMeasuresList[i], status);
513         // TODO(icu-units#67): fix field positions
514     }
515 
516     // Reiterated: we have at least one mixedMeasure:
517     U_ASSERT(micros.mixedMeasuresCount > 0);
518     // Thus if negative, a negative has already been formatted:
519     if (quantity.isNegative()) {
520         quantity.negate();
521     }
522 
523     UnicodeString *finalSimpleFormats = &fMixedUnitData[(fMixedUnitCount - 1) * ARRAY_LENGTH];
524     StandardPlural::Form finalPlural = utils::getPluralSafe(micros.rounder, rules, quantity, status);
525     UnicodeString finalSimpleFormat = getWithPlural(finalSimpleFormats, finalPlural, status);
526     SimpleFormatter finalFormatter(finalSimpleFormat, 0, 1, status);
527     finalFormatter.format(UnicodeString(u"{0}"), outputMeasuresList[fMixedUnitCount - 1], status);
528 
529     // Combine list into a "premixed" pattern
530     UnicodeString premixedFormatPattern;
531     fListFormatter->format(outputMeasuresList.getAlias(), fMixedUnitCount, premixedFormatPattern,
532                            status);
533     SimpleFormatter premixedCompiled(premixedFormatPattern, 0, 1, status);
534     if (U_FAILURE(status)) {
535         return &micros.helpers.emptyWeakModifier;
536     }
537 
538     // TODO(icu-units#67): fix field positions
539     // Return a SimpleModifier for the "premixed" pattern
540     micros.helpers.mixedUnitModifier =
541         SimpleModifier(premixedCompiled, kUndefinedField, false, {this, SIGNUM_POS_ZERO, finalPlural});
542     return &micros.helpers.mixedUnitModifier;
543 }
544 
getModifier(Signum,StandardPlural::Form) const545 const Modifier *MixedUnitLongNameHandler::getModifier(Signum /*signum*/,
546                                                       StandardPlural::Form /*plural*/) const {
547     // TODO(units): investigate this method when investigating where
548     // LongNameHandler::getModifier() gets used. To be sure it remains
549     // unreachable:
550     UPRV_UNREACHABLE;
551     return nullptr;
552 }
553 
554 LongNameMultiplexer *
forMeasureUnits(const Locale & loc,const MaybeStackVector<MeasureUnit> & units,const UNumberUnitWidth & width,const PluralRules * rules,const MicroPropsGenerator * parent,UErrorCode & status)555 LongNameMultiplexer::forMeasureUnits(const Locale &loc, const MaybeStackVector<MeasureUnit> &units,
556                                      const UNumberUnitWidth &width, const PluralRules *rules,
557                                      const MicroPropsGenerator *parent, UErrorCode &status) {
558     LocalPointer<LongNameMultiplexer> result(new LongNameMultiplexer(parent), status);
559     if (U_FAILURE(status)) {
560         return nullptr;
561     }
562     U_ASSERT(units.length() > 0);
563     if (result->fHandlers.resize(units.length()) == nullptr) {
564         status = U_MEMORY_ALLOCATION_ERROR;
565         return nullptr;
566     }
567     result->fMeasureUnits.adoptInstead(new MeasureUnit[units.length()]);
568     for (int32_t i = 0, length = units.length(); i < length; i++) {
569         const MeasureUnit& unit = *units[i];
570         result->fMeasureUnits[i] = unit;
571         if (unit.getComplexity(status) == UMEASURE_UNIT_MIXED) {
572             MixedUnitLongNameHandler *mlnh = result->fMixedUnitHandlers.createAndCheckErrorCode(status);
573             MixedUnitLongNameHandler::forMeasureUnit(loc, unit, width, rules, NULL, mlnh, status);
574             result->fHandlers[i] = mlnh;
575         } else {
576             LongNameHandler *lnh = result->fLongNameHandlers.createAndCheckErrorCode(status);
577             LongNameHandler::forMeasureUnit(loc, unit, MeasureUnit(), width, rules, NULL, lnh, status);
578             result->fHandlers[i] = lnh;
579         }
580         if (U_FAILURE(status)) {
581             return nullptr;
582         }
583     }
584     return result.orphan();
585 }
586 
processQuantity(DecimalQuantity & quantity,MicroProps & micros,UErrorCode & status) const587 void LongNameMultiplexer::processQuantity(DecimalQuantity &quantity, MicroProps &micros,
588                                           UErrorCode &status) const {
589     // We call parent->processQuantity() from the Multiplexer, instead of
590     // letting LongNameHandler handle it: we don't know which LongNameHandler to
591     // call until we've called the parent!
592     fParent->processQuantity(quantity, micros, status);
593 
594     // Call the correct LongNameHandler based on outputUnit
595     for (int i = 0; i < fHandlers.getCapacity(); i++) {
596         if (fMeasureUnits[i] == micros.outputUnit) {
597             fHandlers[i]->processQuantity(quantity, micros, status);
598             return;
599         }
600     }
601     if (U_FAILURE(status)) {
602         return;
603     }
604     // We shouldn't receive any outputUnit for which we haven't already got a
605     // LongNameHandler:
606     status = U_INTERNAL_PROGRAM_ERROR;
607 }
608 
609 #endif /* #if !UCONFIG_NO_FORMATTING */
610