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