1 // © 2020 and later: Unicode, Inc. and others.
2 // License & terms of use: http://www.unicode.org/copyright.html
3 
4 // Extra functions for MeasureUnit not needed for all clients.
5 // Separate .o file so that it can be removed for modularity.
6 
7 #include "unicode/utypes.h"
8 
9 #if !UCONFIG_NO_FORMATTING
10 
11 // Allow implicit conversion from char16_t* to UnicodeString for this file:
12 // Helpful in toString methods and elsewhere.
13 #define UNISTR_FROM_STRING_EXPLICIT
14 
15 #include "charstr.h"
16 #include "cmemory.h"
17 #include "cstring.h"
18 #include "measunit_impl.h"
19 #include "resource.h"
20 #include "uarrsort.h"
21 #include "uassert.h"
22 #include "ucln_in.h"
23 #include "umutex.h"
24 #include "unicode/bytestrie.h"
25 #include "unicode/bytestriebuilder.h"
26 #include "unicode/localpointer.h"
27 #include "unicode/measunit.h"
28 #include "unicode/stringpiece.h"
29 #include "unicode/stringtriebuilder.h"
30 #include "unicode/ures.h"
31 #include "unicode/ustringtrie.h"
32 #include "uresimp.h"
33 #include "util.h"
34 #include <cstdlib>
35 
36 U_NAMESPACE_BEGIN
37 
38 
39 namespace {
40 
41 // TODO: Propose a new error code for this?
42 constexpr UErrorCode kUnitIdentifierSyntaxError = U_ILLEGAL_ARGUMENT_ERROR;
43 
44 // Trie value offset for SI or binary prefixes. This is big enough to ensure we only
45 // insert positive integers into the trie.
46 constexpr int32_t kPrefixOffset = 64;
47 static_assert(kPrefixOffset + UMEASURE_PREFIX_INTERNAL_MIN_BIN > 0,
48               "kPrefixOffset is too small for minimum UMeasurePrefix value");
49 static_assert(kPrefixOffset + UMEASURE_PREFIX_INTERNAL_MIN_SI > 0,
50               "kPrefixOffset is too small for minimum UMeasurePrefix value");
51 
52 // Trie value offset for compound parts, e.g. "-per-", "-", "-and-".
53 constexpr int32_t kCompoundPartOffset = 128;
54 static_assert(kCompoundPartOffset > kPrefixOffset + UMEASURE_PREFIX_INTERNAL_MAX_BIN,
55               "Ambiguous token values: prefix tokens are overlapping with CompoundPart tokens");
56 static_assert(kCompoundPartOffset > kPrefixOffset + UMEASURE_PREFIX_INTERNAL_MAX_SI,
57               "Ambiguous token values: prefix tokens are overlapping with CompoundPart tokens");
58 
59 enum CompoundPart {
60     // Represents "-per-"
61     COMPOUND_PART_PER = kCompoundPartOffset,
62     // Represents "-"
63     COMPOUND_PART_TIMES,
64     // Represents "-and-"
65     COMPOUND_PART_AND,
66 };
67 
68 // Trie value offset for "per-".
69 constexpr int32_t kInitialCompoundPartOffset = 192;
70 
71 enum InitialCompoundPart {
72     // Represents "per-", the only compound part that can appear at the start of
73     // an identifier.
74     INITIAL_COMPOUND_PART_PER = kInitialCompoundPartOffset,
75 };
76 
77 // Trie value offset for powers like "square-", "cubic-", "pow2-" etc.
78 constexpr int32_t kPowerPartOffset = 256;
79 
80 enum PowerPart {
81     POWER_PART_P2 = kPowerPartOffset + 2,
82     POWER_PART_P3,
83     POWER_PART_P4,
84     POWER_PART_P5,
85     POWER_PART_P6,
86     POWER_PART_P7,
87     POWER_PART_P8,
88     POWER_PART_P9,
89     POWER_PART_P10,
90     POWER_PART_P11,
91     POWER_PART_P12,
92     POWER_PART_P13,
93     POWER_PART_P14,
94     POWER_PART_P15,
95 };
96 
97 // Trie value offset for simple units, e.g. "gram", "nautical-mile",
98 // "fluid-ounce-imperial".
99 constexpr int32_t kSimpleUnitOffset = 512;
100 
101 const struct UnitPrefixStrings {
102     const char* const string;
103     UMeasurePrefix value;
104 } gUnitPrefixStrings[] = {
105     // SI prefixes
106     { "yotta", UMEASURE_PREFIX_YOTTA },
107     { "zetta", UMEASURE_PREFIX_ZETTA },
108     { "exa", UMEASURE_PREFIX_EXA },
109     { "peta", UMEASURE_PREFIX_PETA },
110     { "tera", UMEASURE_PREFIX_TERA },
111     { "giga", UMEASURE_PREFIX_GIGA },
112     { "mega", UMEASURE_PREFIX_MEGA },
113     { "kilo", UMEASURE_PREFIX_KILO },
114     { "hecto", UMEASURE_PREFIX_HECTO },
115     { "deka", UMEASURE_PREFIX_DEKA },
116     { "deci", UMEASURE_PREFIX_DECI },
117     { "centi", UMEASURE_PREFIX_CENTI },
118     { "milli", UMEASURE_PREFIX_MILLI },
119     { "micro", UMEASURE_PREFIX_MICRO },
120     { "nano", UMEASURE_PREFIX_NANO },
121     { "pico", UMEASURE_PREFIX_PICO },
122     { "femto", UMEASURE_PREFIX_FEMTO },
123     { "atto", UMEASURE_PREFIX_ATTO },
124     { "zepto", UMEASURE_PREFIX_ZEPTO },
125     { "yocto", UMEASURE_PREFIX_YOCTO },
126     // Binary prefixes
127     { "yobi", UMEASURE_PREFIX_YOBI },
128     { "zebi", UMEASURE_PREFIX_ZEBI },
129     { "exbi", UMEASURE_PREFIX_EXBI },
130     { "pebi", UMEASURE_PREFIX_PEBI },
131     { "tebi", UMEASURE_PREFIX_TEBI },
132     { "gibi", UMEASURE_PREFIX_GIBI },
133     { "mebi", UMEASURE_PREFIX_MEBI },
134     { "kibi", UMEASURE_PREFIX_KIBI },
135 };
136 
137 /**
138  * A ResourceSink that collects simple unit identifiers from the keys of the
139  * convertUnits table into an array, and adds these values to a TrieBuilder,
140  * with associated values being their index into this array plus a specified
141  * offset.
142  *
143  * Example code:
144  *
145  *     UErrorCode status = U_ZERO_ERROR;
146  *     BytesTrieBuilder b(status);
147  *     int32_t ARR_SIZE = 200;
148  *     const char *unitIdentifiers[ARR_SIZE];
149  *     int32_t *unitCategories[ARR_SIZE];
150  *     SimpleUnitIdentifiersSink identifierSink(gSerializedUnitCategoriesTrie, unitIdentifiers,
151  *                                              unitCategories, ARR_SIZE, b, kTrieValueOffset);
152  *     LocalUResourceBundlePointer unitsBundle(ures_openDirect(NULL, "units", &status));
153  *     ures_getAllItemsWithFallback(unitsBundle.getAlias(), "convertUnits", identifierSink, status);
154  */
155 class SimpleUnitIdentifiersSink : public icu::ResourceSink {
156   public:
157     /**
158      * Constructor.
159      * @param quantitiesTrieData The data for constructing a quantitiesTrie,
160      *     which maps from a simple unit identifier to an index into the
161      *     gCategories array.
162      * @param out Array of char* to which pointers to the simple unit
163      *     identifiers will be saved. (Does not take ownership.)
164      * @param outCategories Array of int32_t to which category indexes will be
165      *     saved: this corresponds to simple unit IDs saved to `out`, mapping
166      *     from the ID to the value produced by the quantitiesTrie (which is an
167      *     index into the gCategories array).
168      * @param outSize The size of `out` and `outCategories`.
169      * @param trieBuilder The trie builder to which the simple unit identifier
170      *     should be added. The trie builder must outlive this resource sink.
171      * @param trieValueOffset This is added to the index of the identifier in
172      *     the `out` array, before adding to `trieBuilder` as the value
173      *     associated with the identifier.
174      */
SimpleUnitIdentifiersSink(StringPiece quantitiesTrieData,const char ** out,int32_t * outCategories,int32_t outSize,BytesTrieBuilder & trieBuilder,int32_t trieValueOffset)175     explicit SimpleUnitIdentifiersSink(StringPiece quantitiesTrieData, const char **out,
176                                        int32_t *outCategories, int32_t outSize,
177                                        BytesTrieBuilder &trieBuilder, int32_t trieValueOffset)
178         : outArray(out), outCategories(outCategories), outSize(outSize), trieBuilder(trieBuilder),
179           trieValueOffset(trieValueOffset), quantitiesTrieData(quantitiesTrieData), outIndex(0) {}
180 
181     /**
182      * Adds the table keys found in value to the output vector.
183      * @param key The key of the resource passed to `value`: the second
184      *     parameter of the ures_getAllItemsWithFallback() call.
185      * @param value Should be a ResourceTable value, if
186      *     ures_getAllItemsWithFallback() was called correctly for this sink.
187      * @param noFallback Ignored.
188      * @param status The standard ICU error code output parameter.
189      */
put(const char *,ResourceValue & value,UBool,UErrorCode & status)190     void put(const char * /*key*/, ResourceValue &value, UBool /*noFallback*/, UErrorCode &status) {
191         ResourceTable table = value.getTable(status);
192         if (U_FAILURE(status)) return;
193 
194         if (outIndex + table.getSize() > outSize) {
195             status = U_INDEX_OUTOFBOUNDS_ERROR;
196             return;
197         }
198 
199         BytesTrie quantitiesTrie(quantitiesTrieData.data());
200 
201         // Collect keys from the table resource.
202         const char *simpleUnitID;
203         for (int32_t i = 0; table.getKeyAndValue(i, simpleUnitID, value); ++i) {
204             U_ASSERT(i < table.getSize());
205             U_ASSERT(outIndex < outSize);
206             if (uprv_strcmp(simpleUnitID, "kilogram") == 0) {
207                 // For parsing, we use "gram", the prefixless metric mass unit. We
208                 // thus ignore the SI Base Unit of Mass: it exists due to being the
209                 // mass conversion target unit, but not needed for MeasureUnit
210                 // parsing.
211                 continue;
212             }
213             outArray[outIndex] = simpleUnitID;
214             trieBuilder.add(simpleUnitID, trieValueOffset + outIndex, status);
215 
216             // Find the base target unit for this simple unit
217             ResourceTable table = value.getTable(status);
218             if (U_FAILURE(status)) { return; }
219             if (!table.findValue("target", value)) {
220                 status = U_INVALID_FORMAT_ERROR;
221                 break;
222             }
223             int32_t len;
224             const UChar* uTarget = value.getString(len, status);
225             CharString target;
226             target.appendInvariantChars(uTarget, len, status);
227             if (U_FAILURE(status)) { return; }
228             quantitiesTrie.reset();
229             UStringTrieResult result = quantitiesTrie.next(target.data(), target.length());
230             if (!USTRINGTRIE_HAS_VALUE(result)) {
231                 status = U_INVALID_FORMAT_ERROR;
232                 break;
233             }
234             outCategories[outIndex] = quantitiesTrie.getValue();
235 
236             outIndex++;
237         }
238     }
239 
240   private:
241     const char **outArray;
242     int32_t *outCategories;
243     int32_t outSize;
244     BytesTrieBuilder &trieBuilder;
245     int32_t trieValueOffset;
246 
247     StringPiece quantitiesTrieData;
248 
249     int32_t outIndex;
250 };
251 
252 /**
253  * A ResourceSink that collects information from `unitQuantities` in the `units`
254  * resource to provide key->value lookups from base unit to category, as well as
255  * preserving ordering information for these categories. See `units.txt`.
256  *
257  * For example: "kilogram" -> "mass", "meter-per-second" -> "speed".
258  *
259  * In C++ unitQuantity values are collected in order into a UChar* array, while
260  * unitQuantity keys are added added to a TrieBuilder, with associated values
261  * being the index into the aforementioned UChar* array.
262  */
263 class CategoriesSink : public icu::ResourceSink {
264   public:
265     /**
266      * Constructor.
267      * @param out Array of UChar* to which unitQuantity values will be saved.
268      *     The pointers returned  not owned: they point directly at the resource
269      *     strings in static memory.
270      * @param outSize The size of the `out` array.
271      * @param trieBuilder The trie builder to which the keys (base units) of
272      *     each unitQuantity will be added, each with value being the offset
273      *     into `out`.
274      */
CategoriesSink(const UChar ** out,int32_t & outSize,BytesTrieBuilder & trieBuilder)275     explicit CategoriesSink(const UChar **out, int32_t &outSize, BytesTrieBuilder &trieBuilder)
276         : outQuantitiesArray(out), outSize(outSize), trieBuilder(trieBuilder), outIndex(0) {}
277 
put(const char *,ResourceValue & value,UBool,UErrorCode & status)278     void put(const char * /*key*/, ResourceValue &value, UBool /*noFallback*/, UErrorCode &status) {
279         ResourceArray array = value.getArray(status);
280         if (U_FAILURE(status)) {
281             return;
282         }
283 
284         if (outIndex + array.getSize() > outSize) {
285             status = U_INDEX_OUTOFBOUNDS_ERROR;
286             return;
287         }
288 
289         for (int32_t i = 0; array.getValue(i, value); ++i) {
290             U_ASSERT(outIndex < outSize);
291             ResourceTable table = value.getTable(status);
292             if (U_FAILURE(status)) {
293                 return;
294             }
295             if (table.getSize() != 1) {
296                 status = U_INVALID_FORMAT_ERROR;
297                 return;
298             }
299             const char *key;
300             table.getKeyAndValue(0, key, value);
301             int32_t uTmpLen;
302             outQuantitiesArray[outIndex] = value.getString(uTmpLen, status);
303             trieBuilder.add(key, outIndex, status);
304             outIndex++;
305         }
306     }
307 
308   private:
309     const UChar **outQuantitiesArray;
310     int32_t &outSize;
311     BytesTrieBuilder &trieBuilder;
312 
313     int32_t outIndex;
314 };
315 
316 icu::UInitOnce gUnitExtrasInitOnce = U_INITONCE_INITIALIZER;
317 
318 // Array of simple unit IDs.
319 //
320 // The array memory itself is owned by this pointer, but the individual char* in
321 // that array point at static memory. (Note that these char* are also returned
322 // by SingleUnitImpl::getSimpleUnitID().)
323 const char **gSimpleUnits = nullptr;
324 
325 // Maps from the value associated with each simple unit ID to an index into the
326 // gCategories array.
327 int32_t *gSimpleUnitCategories = nullptr;
328 
329 char *gSerializedUnitExtrasStemTrie = nullptr;
330 
331 // Array of UChar* pointing at the unit categories (aka "quantities", aka
332 // "types"), as found in the `unitQuantities` resource. The array memory itself
333 // is owned by this pointer, but the individual UChar* in that array point at
334 // static memory.
335 const UChar **gCategories = nullptr;
336 // Number of items in `gCategories`.
337 int32_t gCategoriesCount = 0;
338 // TODO: rather save an index into gCategories?
339 const char *kConsumption = "consumption";
340 size_t kConsumptionLen = strlen("consumption");
341 // Serialized BytesTrie for mapping from base units to indices into gCategories.
342 char *gSerializedUnitCategoriesTrie = nullptr;
343 
cleanupUnitExtras()344 UBool U_CALLCONV cleanupUnitExtras() {
345     uprv_free(gSerializedUnitCategoriesTrie);
346     gSerializedUnitCategoriesTrie = nullptr;
347     uprv_free(gCategories);
348     gCategories = nullptr;
349     uprv_free(gSerializedUnitExtrasStemTrie);
350     gSerializedUnitExtrasStemTrie = nullptr;
351     uprv_free(gSimpleUnitCategories);
352     gSimpleUnitCategories = nullptr;
353     uprv_free(gSimpleUnits);
354     gSimpleUnits = nullptr;
355     gUnitExtrasInitOnce.reset();
356     return TRUE;
357 }
358 
initUnitExtras(UErrorCode & status)359 void U_CALLCONV initUnitExtras(UErrorCode& status) {
360     ucln_i18n_registerCleanup(UCLN_I18N_UNIT_EXTRAS, cleanupUnitExtras);
361     LocalUResourceBundlePointer unitsBundle(ures_openDirect(nullptr, "units", &status));
362 
363     // Collect unitQuantities information into gSerializedUnitCategoriesTrie and gCategories.
364     const char *CATEGORY_TABLE_NAME = "unitQuantities";
365     LocalUResourceBundlePointer unitQuantities(
366         ures_getByKey(unitsBundle.getAlias(), CATEGORY_TABLE_NAME, nullptr, &status));
367     if (U_FAILURE(status)) { return; }
368     gCategoriesCount = unitQuantities.getAlias()->fSize;
369     size_t quantitiesMallocSize = sizeof(UChar *) * gCategoriesCount;
370     gCategories = static_cast<const UChar **>(uprv_malloc(quantitiesMallocSize));
371     if (gCategories == nullptr) {
372         status = U_MEMORY_ALLOCATION_ERROR;
373         return;
374     }
375     uprv_memset(gCategories, 0, quantitiesMallocSize);
376     BytesTrieBuilder quantitiesBuilder(status);
377     CategoriesSink categoriesSink(gCategories, gCategoriesCount, quantitiesBuilder);
378     ures_getAllItemsWithFallback(unitsBundle.getAlias(), CATEGORY_TABLE_NAME, categoriesSink, status);
379     StringPiece resultQuantities = quantitiesBuilder.buildStringPiece(USTRINGTRIE_BUILD_FAST, status);
380     if (U_FAILURE(status)) { return; }
381     // Copy the result into the global constant pointer
382     size_t numBytesQuantities = resultQuantities.length();
383     gSerializedUnitCategoriesTrie = static_cast<char *>(uprv_malloc(numBytesQuantities));
384     if (gSerializedUnitCategoriesTrie == nullptr) {
385         status = U_MEMORY_ALLOCATION_ERROR;
386         return;
387     }
388     uprv_memcpy(gSerializedUnitCategoriesTrie, resultQuantities.data(), numBytesQuantities);
389 
390     // Build the BytesTrie that Parser needs for parsing unit identifiers.
391 
392     BytesTrieBuilder b(status);
393     if (U_FAILURE(status)) { return; }
394 
395     // Add SI and binary prefixes
396     for (const auto& unitPrefixInfo : gUnitPrefixStrings) {
397         b.add(unitPrefixInfo.string, unitPrefixInfo.value + kPrefixOffset, status);
398     }
399     if (U_FAILURE(status)) { return; }
400 
401     // Add syntax parts (compound, power prefixes)
402     b.add("-per-", COMPOUND_PART_PER, status);
403     b.add("-", COMPOUND_PART_TIMES, status);
404     b.add("-and-", COMPOUND_PART_AND, status);
405     b.add("per-", INITIAL_COMPOUND_PART_PER, status);
406     b.add("square-", POWER_PART_P2, status);
407     b.add("cubic-", POWER_PART_P3, status);
408     b.add("pow2-", POWER_PART_P2, status);
409     b.add("pow3-", POWER_PART_P3, status);
410     b.add("pow4-", POWER_PART_P4, status);
411     b.add("pow5-", POWER_PART_P5, status);
412     b.add("pow6-", POWER_PART_P6, status);
413     b.add("pow7-", POWER_PART_P7, status);
414     b.add("pow8-", POWER_PART_P8, status);
415     b.add("pow9-", POWER_PART_P9, status);
416     b.add("pow10-", POWER_PART_P10, status);
417     b.add("pow11-", POWER_PART_P11, status);
418     b.add("pow12-", POWER_PART_P12, status);
419     b.add("pow13-", POWER_PART_P13, status);
420     b.add("pow14-", POWER_PART_P14, status);
421     b.add("pow15-", POWER_PART_P15, status);
422     if (U_FAILURE(status)) { return; }
423 
424     // Add sanctioned simple units by offset: simple units all have entries in
425     // units/convertUnits resources.
426     LocalUResourceBundlePointer convertUnits(
427         ures_getByKey(unitsBundle.getAlias(), "convertUnits", nullptr, &status));
428     if (U_FAILURE(status)) { return; }
429 
430     // Allocate enough space: with identifierSink below skipping kilogram, we're
431     // probably allocating one more than needed.
432     int32_t simpleUnitsCount = convertUnits.getAlias()->fSize;
433     int32_t arrayMallocSize = sizeof(char *) * simpleUnitsCount;
434     gSimpleUnits = static_cast<const char **>(uprv_malloc(arrayMallocSize));
435     if (gSimpleUnits == nullptr) {
436         status = U_MEMORY_ALLOCATION_ERROR;
437         return;
438     }
439     uprv_memset(gSimpleUnits, 0, arrayMallocSize);
440     arrayMallocSize = sizeof(int32_t) * simpleUnitsCount;
441     gSimpleUnitCategories = static_cast<int32_t *>(uprv_malloc(arrayMallocSize));
442     if (gSimpleUnitCategories == nullptr) {
443         status = U_MEMORY_ALLOCATION_ERROR;
444         return;
445     }
446     uprv_memset(gSimpleUnitCategories, 0, arrayMallocSize);
447 
448     // Populate gSimpleUnits and build the associated trie.
449     SimpleUnitIdentifiersSink identifierSink(resultQuantities, gSimpleUnits, gSimpleUnitCategories,
450                                              simpleUnitsCount, b, kSimpleUnitOffset);
451     ures_getAllItemsWithFallback(unitsBundle.getAlias(), "convertUnits", identifierSink, status);
452 
453     // Build the CharsTrie
454     // TODO: Use SLOW or FAST here?
455     StringPiece result = b.buildStringPiece(USTRINGTRIE_BUILD_FAST, status);
456     if (U_FAILURE(status)) { return; }
457 
458     // Copy the result into the global constant pointer
459     size_t numBytes = result.length();
460     gSerializedUnitExtrasStemTrie = static_cast<char *>(uprv_malloc(numBytes));
461     if (gSerializedUnitExtrasStemTrie == nullptr) {
462         status = U_MEMORY_ALLOCATION_ERROR;
463         return;
464     }
465     uprv_memcpy(gSerializedUnitExtrasStemTrie, result.data(), numBytes);
466 }
467 
468 class Token {
469 public:
Token(int32_t match)470     Token(int32_t match) : fMatch(match) {}
471 
472     enum Type {
473         TYPE_UNDEFINED,
474         TYPE_PREFIX,
475         // Token type for "-per-", "-", and "-and-".
476         TYPE_COMPOUND_PART,
477         // Token type for "per-".
478         TYPE_INITIAL_COMPOUND_PART,
479         TYPE_POWER_PART,
480         TYPE_SIMPLE_UNIT,
481     };
482 
483     // Calling getType() is invalid, resulting in an assertion failure, if Token
484     // value isn't positive.
getType() const485     Type getType() const {
486         U_ASSERT(fMatch > 0);
487         if (fMatch < kCompoundPartOffset) {
488             return TYPE_PREFIX;
489         }
490         if (fMatch < kInitialCompoundPartOffset) {
491             return TYPE_COMPOUND_PART;
492         }
493         if (fMatch < kPowerPartOffset) {
494             return TYPE_INITIAL_COMPOUND_PART;
495         }
496         if (fMatch < kSimpleUnitOffset) {
497             return TYPE_POWER_PART;
498         }
499         return TYPE_SIMPLE_UNIT;
500     }
501 
getUnitPrefix() const502     UMeasurePrefix getUnitPrefix() const {
503         U_ASSERT(getType() == TYPE_PREFIX);
504         return static_cast<UMeasurePrefix>(fMatch - kPrefixOffset);
505     }
506 
507     // Valid only for tokens with type TYPE_COMPOUND_PART.
getMatch() const508     int32_t getMatch() const {
509         U_ASSERT(getType() == TYPE_COMPOUND_PART);
510         return fMatch;
511     }
512 
getInitialCompoundPart() const513     int32_t getInitialCompoundPart() const {
514         // Even if there is only one InitialCompoundPart value, we have this
515         // function for the simplicity of code consistency.
516         U_ASSERT(getType() == TYPE_INITIAL_COMPOUND_PART);
517         // Defensive: if this assert fails, code using this function also needs
518         // to change.
519         U_ASSERT(fMatch == INITIAL_COMPOUND_PART_PER);
520         return fMatch;
521     }
522 
getPower() const523     int8_t getPower() const {
524         U_ASSERT(getType() == TYPE_POWER_PART);
525         return static_cast<int8_t>(fMatch - kPowerPartOffset);
526     }
527 
getSimpleUnitIndex() const528     int32_t getSimpleUnitIndex() const {
529         U_ASSERT(getType() == TYPE_SIMPLE_UNIT);
530         return fMatch - kSimpleUnitOffset;
531     }
532 
533 private:
534     int32_t fMatch;
535 };
536 
537 class Parser {
538 public:
539     /**
540      * Factory function for parsing the given identifier.
541      *
542      * @param source The identifier to parse. This function does not make a copy
543      * of source: the underlying string that source points at, must outlive the
544      * parser.
545      * @param status ICU error code.
546      */
from(StringPiece source,UErrorCode & status)547     static Parser from(StringPiece source, UErrorCode& status) {
548         if (U_FAILURE(status)) {
549             return Parser();
550         }
551         umtx_initOnce(gUnitExtrasInitOnce, &initUnitExtras, status);
552         if (U_FAILURE(status)) {
553             return Parser();
554         }
555         return Parser(source);
556     }
557 
parse(UErrorCode & status)558     MeasureUnitImpl parse(UErrorCode& status) {
559         MeasureUnitImpl result;
560 
561         if (U_FAILURE(status)) {
562             return result;
563         }
564         if (fSource.empty()) {
565             // The dimenionless unit: nothing to parse. leave result as is.
566             return result;
567         }
568 
569         while (hasNext()) {
570             bool sawAnd = false;
571 
572             SingleUnitImpl singleUnit = nextSingleUnit(sawAnd, status);
573             if (U_FAILURE(status)) {
574                 return result;
575             }
576 
577             bool added = result.appendSingleUnit(singleUnit, status);
578             if (U_FAILURE(status)) {
579                 return result;
580             }
581 
582             if (sawAnd && !added) {
583                 // Two similar units are not allowed in a mixed unit.
584                 status = kUnitIdentifierSyntaxError;
585                 return result;
586             }
587 
588             if (result.singleUnits.length() >= 2) {
589                 // nextSingleUnit fails appropriately for "per" and "and" in the
590                 // same identifier. It doesn't fail for other compound units
591                 // (COMPOUND_PART_TIMES). Consequently we take care of that
592                 // here.
593                 UMeasureUnitComplexity complexity =
594                     sawAnd ? UMEASURE_UNIT_MIXED : UMEASURE_UNIT_COMPOUND;
595                 if (result.singleUnits.length() == 2) {
596                     // After appending two singleUnits, the complexity will be `UMEASURE_UNIT_COMPOUND`
597                     U_ASSERT(result.complexity == UMEASURE_UNIT_COMPOUND);
598                     result.complexity = complexity;
599                 } else if (result.complexity != complexity) {
600                     // Can't have mixed compound units
601                     status = kUnitIdentifierSyntaxError;
602                     return result;
603                 }
604             }
605         }
606 
607         return result;
608     }
609 
610 private:
611     // Tracks parser progress: the offset into fSource.
612     int32_t fIndex = 0;
613 
614     // Since we're not owning this memory, whatever is passed to the constructor
615     // should live longer than this Parser - and the parser shouldn't return any
616     // references to that string.
617     StringPiece fSource;
618     BytesTrie fTrie;
619 
620     // Set to true when we've seen a "-per-" or a "per-", after which all units
621     // are in the denominator. Until we find an "-and-", at which point the
622     // identifier is invalid pending TODO(CLDR-13700).
623     bool fAfterPer = false;
624 
Parser()625     Parser() : fSource(""), fTrie(u"") {}
626 
Parser(StringPiece source)627     Parser(StringPiece source)
628         : fSource(source), fTrie(gSerializedUnitExtrasStemTrie) {}
629 
hasNext() const630     inline bool hasNext() const {
631         return fIndex < fSource.length();
632     }
633 
634     // Returns the next Token parsed from fSource, advancing fIndex to the end
635     // of that token in fSource. In case of U_FAILURE(status), the token
636     // returned will cause an abort if getType() is called on it.
nextToken(UErrorCode & status)637     Token nextToken(UErrorCode& status) {
638         fTrie.reset();
639         int32_t match = -1;
640         // Saves the position in the fSource string for the end of the most
641         // recent matching token.
642         int32_t previ = -1;
643         // Find the longest token that matches a value in the trie:
644         while (fIndex < fSource.length()) {
645             auto result = fTrie.next(fSource.data()[fIndex++]);
646             if (result == USTRINGTRIE_NO_MATCH) {
647                 break;
648             } else if (result == USTRINGTRIE_NO_VALUE) {
649                 continue;
650             }
651             U_ASSERT(USTRINGTRIE_HAS_VALUE(result));
652             match = fTrie.getValue();
653             previ = fIndex;
654             if (result == USTRINGTRIE_FINAL_VALUE) {
655                 break;
656             }
657             U_ASSERT(result == USTRINGTRIE_INTERMEDIATE_VALUE);
658             // continue;
659         }
660 
661         if (match < 0) {
662             status = kUnitIdentifierSyntaxError;
663         } else {
664             fIndex = previ;
665         }
666         return Token(match);
667     }
668 
669     /**
670      * Returns the next "single unit" via result.
671      *
672      * If a "-per-" was parsed, the result will have appropriate negative
673      * dimensionality.
674      *
675      * Returns an error if we parse both compound units and "-and-", since mixed
676      * compound units are not yet supported - TODO(CLDR-13700).
677      *
678      * @param result Will be overwritten by the result, if status shows success.
679      * @param sawAnd If an "-and-" was parsed prior to finding the "single
680      * unit", sawAnd is set to true. If not, it is left as is.
681      * @param status ICU error code.
682      */
nextSingleUnit(bool & sawAnd,UErrorCode & status)683     SingleUnitImpl nextSingleUnit(bool &sawAnd, UErrorCode &status) {
684         SingleUnitImpl result;
685         if (U_FAILURE(status)) {
686             return result;
687         }
688 
689         // state:
690         // 0 = no tokens seen yet (will accept power, SI or binary prefix, or simple unit)
691         // 1 = power token seen (will not accept another power token)
692         // 2 = SI or binary prefix token seen (will not accept a power, or SI or binary prefix token)
693         int32_t state = 0;
694 
695         bool atStart = fIndex == 0;
696         Token token = nextToken(status);
697         if (U_FAILURE(status)) {
698             return result;
699         }
700 
701         if (atStart) {
702             // Identifiers optionally start with "per-".
703             if (token.getType() == Token::TYPE_INITIAL_COMPOUND_PART) {
704                 U_ASSERT(token.getInitialCompoundPart() == INITIAL_COMPOUND_PART_PER);
705                 fAfterPer = true;
706                 result.dimensionality = -1;
707 
708                 token = nextToken(status);
709                 if (U_FAILURE(status)) {
710                     return result;
711                 }
712             }
713         } else {
714             // All other SingleUnit's are separated from previous SingleUnit's
715             // via a compound part:
716             if (token.getType() != Token::TYPE_COMPOUND_PART) {
717                 status = kUnitIdentifierSyntaxError;
718                 return result;
719             }
720 
721             switch (token.getMatch()) {
722             case COMPOUND_PART_PER:
723                 if (sawAnd) {
724                     // Mixed compound units not yet supported,
725                     // TODO(CLDR-13700).
726                     status = kUnitIdentifierSyntaxError;
727                     return result;
728                 }
729                 fAfterPer = true;
730                 result.dimensionality = -1;
731                 break;
732 
733             case COMPOUND_PART_TIMES:
734                 if (fAfterPer) {
735                     result.dimensionality = -1;
736                 }
737                 break;
738 
739             case COMPOUND_PART_AND:
740                 if (fAfterPer) {
741                     // Can't start with "-and-", and mixed compound units
742                     // not yet supported, TODO(CLDR-13700).
743                     status = kUnitIdentifierSyntaxError;
744                     return result;
745                 }
746                 sawAnd = true;
747                 break;
748             }
749 
750             token = nextToken(status);
751             if (U_FAILURE(status)) {
752                 return result;
753             }
754         }
755 
756         // Read tokens until we have a complete SingleUnit or we reach the end.
757         while (true) {
758             switch (token.getType()) {
759                 case Token::TYPE_POWER_PART:
760                     if (state > 0) {
761                         status = kUnitIdentifierSyntaxError;
762                         return result;
763                     }
764                     result.dimensionality *= token.getPower();
765                     state = 1;
766                     break;
767 
768                 case Token::TYPE_PREFIX:
769                     if (state > 1) {
770                         status = kUnitIdentifierSyntaxError;
771                         return result;
772                     }
773                     result.unitPrefix = token.getUnitPrefix();
774                     state = 2;
775                     break;
776 
777                 case Token::TYPE_SIMPLE_UNIT:
778                     result.index = token.getSimpleUnitIndex();
779                     return result;
780 
781                 default:
782                     status = kUnitIdentifierSyntaxError;
783                     return result;
784             }
785 
786             if (!hasNext()) {
787                 // We ran out of tokens before finding a complete single unit.
788                 status = kUnitIdentifierSyntaxError;
789                 return result;
790             }
791             token = nextToken(status);
792             if (U_FAILURE(status)) {
793                 return result;
794             }
795         }
796 
797         return result;
798     }
799 };
800 
801 // Sorting function wrapping SingleUnitImpl::compareTo for use with uprv_sortArray.
802 int32_t U_CALLCONV
compareSingleUnits(const void *,const void * left,const void * right)803 compareSingleUnits(const void* /*context*/, const void* left, const void* right) {
804     auto realLeft = static_cast<const SingleUnitImpl* const*>(left);
805     auto realRight = static_cast<const SingleUnitImpl* const*>(right);
806     return (*realLeft)->compareTo(**realRight);
807 }
808 
809 // Returns an index into the gCategories array, for the "unitQuantity" (aka
810 // "type" or "category") associated with the given base unit identifier. Returns
811 // -1 on failure, together with U_UNSUPPORTED_ERROR.
getUnitCategoryIndex(StringPiece baseUnitIdentifier,UErrorCode & status)812 int32_t getUnitCategoryIndex(StringPiece baseUnitIdentifier, UErrorCode &status) {
813     umtx_initOnce(gUnitExtrasInitOnce, &initUnitExtras, status);
814     if (U_FAILURE(status)) {
815         return -1;
816     }
817     BytesTrie trie(gSerializedUnitCategoriesTrie);
818     UStringTrieResult result = trie.next(baseUnitIdentifier.data(), baseUnitIdentifier.length());
819     if (!USTRINGTRIE_HAS_VALUE(result)) {
820         status = U_UNSUPPORTED_ERROR;
821         return -1;
822     }
823     return trie.getValue();
824 }
825 
826 } // namespace
827 
828 U_CAPI int32_t U_EXPORT2
umeas_getPrefixPower(UMeasurePrefix unitPrefix)829 umeas_getPrefixPower(UMeasurePrefix unitPrefix) {
830     if (unitPrefix >= UMEASURE_PREFIX_INTERNAL_MIN_BIN &&
831         unitPrefix <= UMEASURE_PREFIX_INTERNAL_MAX_BIN) {
832         return unitPrefix - UMEASURE_PREFIX_INTERNAL_ONE_BIN;
833     }
834     U_ASSERT(unitPrefix >= UMEASURE_PREFIX_INTERNAL_MIN_SI &&
835              unitPrefix <= UMEASURE_PREFIX_INTERNAL_MAX_SI);
836     return unitPrefix - UMEASURE_PREFIX_ONE;
837 }
838 
839 U_CAPI int32_t U_EXPORT2
umeas_getPrefixBase(UMeasurePrefix unitPrefix)840 umeas_getPrefixBase(UMeasurePrefix unitPrefix) {
841     if (unitPrefix >= UMEASURE_PREFIX_INTERNAL_MIN_BIN &&
842         unitPrefix <= UMEASURE_PREFIX_INTERNAL_MAX_BIN) {
843         return 1024;
844     }
845     U_ASSERT(unitPrefix >= UMEASURE_PREFIX_INTERNAL_MIN_SI &&
846              unitPrefix <= UMEASURE_PREFIX_INTERNAL_MAX_SI);
847     return 10;
848 }
849 
getUnitQuantity(StringPiece baseUnitIdentifier,UErrorCode & status)850 CharString U_I18N_API getUnitQuantity(StringPiece baseUnitIdentifier, UErrorCode &status) {
851     CharString result;
852     U_ASSERT(result.length() == 0);
853     if (U_FAILURE(status)) {
854         return result;
855     }
856     UErrorCode localStatus = U_ZERO_ERROR;
857     int32_t idx = getUnitCategoryIndex(baseUnitIdentifier, localStatus);
858     if (U_FAILURE(localStatus)) {
859         // TODO(icu-units#130): support inverting any unit, with correct
860         // fallback logic: inversion and fallback may depend on presence or
861         // absence of a usage for that category.
862         if (uprv_strcmp(baseUnitIdentifier.data(), "meter-per-cubic-meter") == 0) {
863             result.append(kConsumption, (int32_t)kConsumptionLen, status);
864             return result;
865         }
866         status = U_INVALID_FORMAT_ERROR;
867         return result;
868     }
869     if (idx < 0 || idx >= gCategoriesCount) {
870         status = U_INVALID_FORMAT_ERROR;
871         return result;
872     }
873     result.appendInvariantChars(gCategories[idx], u_strlen(gCategories[idx]), status);
874     return result;
875 }
876 
877 // In ICU4J, this is MeasureUnit.getSingleUnitImpl().
forMeasureUnit(const MeasureUnit & measureUnit,UErrorCode & status)878 SingleUnitImpl SingleUnitImpl::forMeasureUnit(const MeasureUnit& measureUnit, UErrorCode& status) {
879     MeasureUnitImpl temp;
880     const MeasureUnitImpl& impl = MeasureUnitImpl::forMeasureUnit(measureUnit, temp, status);
881     if (U_FAILURE(status)) {
882         return {};
883     }
884     if (impl.singleUnits.length() == 0) {
885         return {};
886     }
887     if (impl.singleUnits.length() == 1) {
888         return *impl.singleUnits[0];
889     }
890     status = U_ILLEGAL_ARGUMENT_ERROR;
891     return {};
892 }
893 
build(UErrorCode & status) const894 MeasureUnit SingleUnitImpl::build(UErrorCode& status) const {
895     MeasureUnitImpl temp;
896     temp.appendSingleUnit(*this, status);
897     // TODO(icu-units#28): the MeasureUnitImpl::build() method uses
898     // findBySubtype, which is relatively slow.
899     // - At the time of loading the simple unit IDs, we could also save a
900     //   mapping to the builtin MeasureUnit type and subtype they correspond to.
901     // - This method could then check dimensionality and index, and if both are
902     //   1, directly return MeasureUnit instances very quickly.
903     return std::move(temp).build(status);
904 }
905 
getSimpleUnitID() const906 const char *SingleUnitImpl::getSimpleUnitID() const {
907     return gSimpleUnits[index];
908 }
909 
appendNeutralIdentifier(CharString & result,UErrorCode & status) const910 void SingleUnitImpl::appendNeutralIdentifier(CharString &result, UErrorCode &status) const {
911     int32_t absPower = std::abs(this->dimensionality);
912 
913     U_ASSERT(absPower > 0); // "this function does not support the dimensionless single units";
914 
915     if (absPower == 1) {
916         // no-op
917     } else if (absPower == 2) {
918         result.append(StringPiece("square-"), status);
919     } else if (absPower == 3) {
920         result.append(StringPiece("cubic-"), status);
921     } else if (absPower <= 15) {
922         result.append(StringPiece("pow"), status);
923         result.appendNumber(absPower, status);
924         result.append(StringPiece("-"), status);
925     } else {
926         status = U_ILLEGAL_ARGUMENT_ERROR; // Unit Identifier Syntax Error
927         return;
928     }
929 
930     if (U_FAILURE(status)) {
931         return;
932     }
933 
934     if (this->unitPrefix != UMEASURE_PREFIX_ONE) {
935         bool found = false;
936         for (const auto &unitPrefixInfo : gUnitPrefixStrings) {
937             // TODO: consider using binary search? If we do this, add a unit
938             // test to ensure gUnitPrefixStrings is sorted?
939             if (unitPrefixInfo.value == this->unitPrefix) {
940                 result.append(unitPrefixInfo.string, status);
941                 found = true;
942                 break;
943             }
944         }
945         if (!found) {
946             status = U_UNSUPPORTED_ERROR;
947             return;
948         }
949     }
950 
951     result.append(StringPiece(this->getSimpleUnitID()), status);
952 }
953 
getUnitCategoryIndex() const954 int32_t SingleUnitImpl::getUnitCategoryIndex() const {
955     return gSimpleUnitCategories[index];
956 }
957 
MeasureUnitImpl(const SingleUnitImpl & singleUnit,UErrorCode & status)958 MeasureUnitImpl::MeasureUnitImpl(const SingleUnitImpl &singleUnit, UErrorCode &status) {
959     this->appendSingleUnit(singleUnit, status);
960 }
961 
forIdentifier(StringPiece identifier,UErrorCode & status)962 MeasureUnitImpl MeasureUnitImpl::forIdentifier(StringPiece identifier, UErrorCode& status) {
963     return Parser::from(identifier, status).parse(status);
964 }
965 
forMeasureUnit(const MeasureUnit & measureUnit,MeasureUnitImpl & memory,UErrorCode & status)966 const MeasureUnitImpl& MeasureUnitImpl::forMeasureUnit(
967         const MeasureUnit& measureUnit, MeasureUnitImpl& memory, UErrorCode& status) {
968     if (measureUnit.fImpl) {
969         return *measureUnit.fImpl;
970     } else {
971         memory = Parser::from(measureUnit.getIdentifier(), status).parse(status);
972         return memory;
973     }
974 }
975 
forMeasureUnitMaybeCopy(const MeasureUnit & measureUnit,UErrorCode & status)976 MeasureUnitImpl MeasureUnitImpl::forMeasureUnitMaybeCopy(
977         const MeasureUnit& measureUnit, UErrorCode& status) {
978     if (measureUnit.fImpl) {
979         return measureUnit.fImpl->copy(status);
980     } else {
981         return Parser::from(measureUnit.getIdentifier(), status).parse(status);
982     }
983 }
984 
takeReciprocal(UErrorCode &)985 void MeasureUnitImpl::takeReciprocal(UErrorCode& /*status*/) {
986     identifier.clear();
987     for (int32_t i = 0; i < singleUnits.length(); i++) {
988         singleUnits[i]->dimensionality *= -1;
989     }
990 }
991 
appendSingleUnit(const SingleUnitImpl & singleUnit,UErrorCode & status)992 bool MeasureUnitImpl::appendSingleUnit(const SingleUnitImpl &singleUnit, UErrorCode &status) {
993     identifier.clear();
994 
995     if (singleUnit.isDimensionless()) {
996         // Do not append dimensionless units.
997         return false;
998     }
999 
1000     // Find a similar unit that already exists, to attempt to coalesce
1001     SingleUnitImpl *oldUnit = nullptr;
1002     for (int32_t i = 0; i < this->singleUnits.length(); i++) {
1003         auto *candidate = this->singleUnits[i];
1004         if (candidate->isCompatibleWith(singleUnit)) {
1005             oldUnit = candidate;
1006         }
1007     }
1008 
1009     if (oldUnit) {
1010         // Both dimensionalities will be positive, or both will be negative, by
1011         // virtue of isCompatibleWith().
1012         oldUnit->dimensionality += singleUnit.dimensionality;
1013 
1014         return false;
1015     }
1016 
1017     // Add a copy of singleUnit
1018     // NOTE: MaybeStackVector::emplaceBackAndCheckErrorCode creates new copy of  singleUnit.
1019     this->singleUnits.emplaceBackAndCheckErrorCode(status, singleUnit);
1020     if (U_FAILURE(status)) {
1021         return false;
1022     }
1023 
1024     // If the MeasureUnitImpl is `UMEASURE_UNIT_SINGLE` and after the appending a unit, the `singleUnits`
1025     // contains more than one. thus means the complexity should be `UMEASURE_UNIT_COMPOUND`
1026     if (this->singleUnits.length() > 1 &&
1027         this->complexity == UMeasureUnitComplexity::UMEASURE_UNIT_SINGLE) {
1028         this->complexity = UMeasureUnitComplexity::UMEASURE_UNIT_COMPOUND;
1029     }
1030 
1031     return true;
1032 }
1033 
1034 MaybeStackVector<MeasureUnitImplWithIndex>
extractIndividualUnitsWithIndices(UErrorCode & status) const1035 MeasureUnitImpl::extractIndividualUnitsWithIndices(UErrorCode &status) const {
1036     MaybeStackVector<MeasureUnitImplWithIndex> result;
1037 
1038     if (this->complexity != UMeasureUnitComplexity::UMEASURE_UNIT_MIXED) {
1039         result.emplaceBackAndCheckErrorCode(status, 0, *this, status);
1040         return result;
1041     }
1042 
1043     for (int32_t i = 0; i < singleUnits.length(); ++i) {
1044         result.emplaceBackAndCheckErrorCode(status, i, *singleUnits[i], status);
1045         if (U_FAILURE(status)) {
1046             return result;
1047         }
1048     }
1049 
1050     return result;
1051 }
1052 
1053 /**
1054  * Normalize a MeasureUnitImpl and generate the identifier string in place.
1055  */
serialize(UErrorCode & status)1056 void MeasureUnitImpl::serialize(UErrorCode &status) {
1057     if (U_FAILURE(status)) {
1058         return;
1059     }
1060 
1061     if (this->singleUnits.length() == 0) {
1062         // Dimensionless, constructed by the default constructor.
1063         return;
1064     }
1065 
1066     if (this->complexity == UMEASURE_UNIT_COMPOUND) {
1067         // Note: don't sort a MIXED unit
1068         uprv_sortArray(this->singleUnits.getAlias(), this->singleUnits.length(),
1069                        sizeof(this->singleUnits[0]), compareSingleUnits, nullptr, false, &status);
1070         if (U_FAILURE(status)) {
1071             return;
1072         }
1073     }
1074 
1075     CharString result;
1076     bool beforePer = true;
1077     bool firstTimeNegativeDimension = false;
1078     for (int32_t i = 0; i < this->singleUnits.length(); i++) {
1079         if (beforePer && (*this->singleUnits[i]).dimensionality < 0) {
1080             beforePer = false;
1081             firstTimeNegativeDimension = true;
1082         } else if ((*this->singleUnits[i]).dimensionality < 0) {
1083             firstTimeNegativeDimension = false;
1084         }
1085 
1086         if (U_FAILURE(status)) {
1087             return;
1088         }
1089 
1090         if (this->complexity == UMeasureUnitComplexity::UMEASURE_UNIT_MIXED) {
1091             if (result.length() != 0) {
1092                 result.append(StringPiece("-and-"), status);
1093             }
1094         } else {
1095             if (firstTimeNegativeDimension) {
1096                 if (result.length() == 0) {
1097                     result.append(StringPiece("per-"), status);
1098                 } else {
1099                     result.append(StringPiece("-per-"), status);
1100                 }
1101             } else {
1102                 if (result.length() != 0) {
1103                     result.append(StringPiece("-"), status);
1104                 }
1105             }
1106         }
1107 
1108         this->singleUnits[i]->appendNeutralIdentifier(result, status);
1109     }
1110 
1111     this->identifier = CharString(result, status);
1112 }
1113 
build(UErrorCode & status)1114 MeasureUnit MeasureUnitImpl::build(UErrorCode& status) && {
1115     this->serialize(status);
1116     return MeasureUnit(std::move(*this));
1117 }
1118 
forIdentifier(StringPiece identifier,UErrorCode & status)1119 MeasureUnit MeasureUnit::forIdentifier(StringPiece identifier, UErrorCode& status) {
1120     return Parser::from(identifier, status).parse(status).build(status);
1121 }
1122 
getComplexity(UErrorCode & status) const1123 UMeasureUnitComplexity MeasureUnit::getComplexity(UErrorCode& status) const {
1124     MeasureUnitImpl temp;
1125     return MeasureUnitImpl::forMeasureUnit(*this, temp, status).complexity;
1126 }
1127 
getPrefix(UErrorCode & status) const1128 UMeasurePrefix MeasureUnit::getPrefix(UErrorCode& status) const {
1129     return SingleUnitImpl::forMeasureUnit(*this, status).unitPrefix;
1130 }
1131 
withPrefix(UMeasurePrefix prefix,UErrorCode & status) const1132 MeasureUnit MeasureUnit::withPrefix(UMeasurePrefix prefix, UErrorCode& status) const {
1133     SingleUnitImpl singleUnit = SingleUnitImpl::forMeasureUnit(*this, status);
1134     singleUnit.unitPrefix = prefix;
1135     return singleUnit.build(status);
1136 }
1137 
getDimensionality(UErrorCode & status) const1138 int32_t MeasureUnit::getDimensionality(UErrorCode& status) const {
1139     SingleUnitImpl singleUnit = SingleUnitImpl::forMeasureUnit(*this, status);
1140     if (U_FAILURE(status)) { return 0; }
1141     if (singleUnit.isDimensionless()) {
1142         return 0;
1143     }
1144     return singleUnit.dimensionality;
1145 }
1146 
withDimensionality(int32_t dimensionality,UErrorCode & status) const1147 MeasureUnit MeasureUnit::withDimensionality(int32_t dimensionality, UErrorCode& status) const {
1148     SingleUnitImpl singleUnit = SingleUnitImpl::forMeasureUnit(*this, status);
1149     singleUnit.dimensionality = dimensionality;
1150     return singleUnit.build(status);
1151 }
1152 
reciprocal(UErrorCode & status) const1153 MeasureUnit MeasureUnit::reciprocal(UErrorCode& status) const {
1154     MeasureUnitImpl impl = MeasureUnitImpl::forMeasureUnitMaybeCopy(*this, status);
1155     impl.takeReciprocal(status);
1156     return std::move(impl).build(status);
1157 }
1158 
product(const MeasureUnit & other,UErrorCode & status) const1159 MeasureUnit MeasureUnit::product(const MeasureUnit& other, UErrorCode& status) const {
1160     MeasureUnitImpl impl = MeasureUnitImpl::forMeasureUnitMaybeCopy(*this, status);
1161     MeasureUnitImpl temp;
1162     const MeasureUnitImpl& otherImpl = MeasureUnitImpl::forMeasureUnit(other, temp, status);
1163     if (impl.complexity == UMEASURE_UNIT_MIXED || otherImpl.complexity == UMEASURE_UNIT_MIXED) {
1164         status = U_ILLEGAL_ARGUMENT_ERROR;
1165         return {};
1166     }
1167     for (int32_t i = 0; i < otherImpl.singleUnits.length(); i++) {
1168         impl.appendSingleUnit(*otherImpl.singleUnits[i], status);
1169     }
1170     if (impl.singleUnits.length() > 1) {
1171         impl.complexity = UMEASURE_UNIT_COMPOUND;
1172     }
1173     return std::move(impl).build(status);
1174 }
1175 
splitToSingleUnitsImpl(int32_t & outCount,UErrorCode & status) const1176 LocalArray<MeasureUnit> MeasureUnit::splitToSingleUnitsImpl(int32_t& outCount, UErrorCode& status) const {
1177     MeasureUnitImpl temp;
1178     const MeasureUnitImpl& impl = MeasureUnitImpl::forMeasureUnit(*this, temp, status);
1179     outCount = impl.singleUnits.length();
1180     MeasureUnit* arr = new MeasureUnit[outCount];
1181     if (arr == nullptr) {
1182         status = U_MEMORY_ALLOCATION_ERROR;
1183         return LocalArray<MeasureUnit>();
1184     }
1185     for (int32_t i = 0; i < outCount; i++) {
1186         arr[i] = impl.singleUnits[i]->build(status);
1187     }
1188     return LocalArray<MeasureUnit>(arr, status);
1189 }
1190 
1191 
1192 U_NAMESPACE_END
1193 
1194 #endif /* !UNCONFIG_NO_FORMATTING */
1195