• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /**
2  * Copyright (c) 2025 Huawei Device Co., Ltd.
3  * Licensed under the Apache License, Version 2.0 (the "License");
4  * you may not use this file except in compliance with the License.
5  * You may obtain a copy of the License at
6  *
7  * http://www.apache.org/licenses/LICENSE-2.0
8  *
9  * Unless required by applicable law or agreed to in writing, software
10  * distributed under the License is distributed on an "AS IS" BASIS,
11  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12  * See the License for the specific language governing permissions and
13  * limitations under the License.
14  */
15 
16 #include "plugins/ets/stdlib/native/core/stdlib_ani_helpers.h"
17 #include "IntlCommon.h"
18 #include "IntlNumberFormatters.h"
19 #include <cstring>
20 
21 namespace ark::ets::stdlib::intl {
22 
LocTagToIcuLocale(ani_env * env,const std::string & localeTag,icu::Locale & locale)23 ANI_EXPORT ani_status LocTagToIcuLocale(ani_env *env, const std::string &localeTag, icu::Locale &locale)
24 {
25     icu::StringPiece sp {localeTag.data(), static_cast<int32_t>(localeTag.size())};
26     UErrorCode status = U_ZERO_ERROR;
27     locale = icu::Locale::forLanguageTag(sp, status);
28     if (UNLIKELY(U_FAILURE(status))) {
29         std::string message = "Language tag '" + localeTag + std::string("' is invalid or not supported");
30         ThrowNewError(env, ERR_CLS_RUNTIME_EXCEPTION, message.c_str(), CTOR_SIGNATURE_STR);
31         return ANI_PENDING_ERROR;
32     }
33     return ANI_OK;
34 }
35 
SetNumberingSystemIntoLocale(ani_env * env,const ParsedOptions & options,icu::Locale & locale)36 ani_status SetNumberingSystemIntoLocale(ani_env *env, const ParsedOptions &options, icu::Locale &locale)
37 {
38     if (!options.numberingSystem.empty()) {
39         UErrorCode status = U_ZERO_ERROR;
40         locale.setKeywordValue("nu", options.numberingSystem.c_str(), status);
41         if (UNLIKELY(U_FAILURE(status))) {
42             std::string message = "Invalid numbering system " + options.numberingSystem;
43             ThrowNewError(env, ERR_CLS_RUNTIME_EXCEPTION, message.c_str(), CTOR_SIGNATURE_STR);
44             return ANI_PENDING_ERROR;
45         }
46     }
47     return ANI_OK;
48 }
49 
50 // Configure minFractionDigits, maxFractionDigits, minSignificantDigits, maxSignificantDigits, minIntegerDigits
51 template <typename FormatterType>
ConfigurePrecision(ani_env * env,const ParsedOptions & options,FormatterType & fmt)52 ani_status ConfigurePrecision([[maybe_unused]] ani_env *env, const ParsedOptions &options, FormatterType &fmt)
53 {
54     fmt = fmt.precision(
55         icu::number::Precision::minMaxFraction(stoi(options.minFractionDigits), stoi(options.maxFractionDigits)));
56     if (!options.minSignificantDigits.empty()) {
57         if (!options.maxSignificantDigits.empty()) {
58             if (options.notation == NOTATION_COMPACT) {
59                 fmt = fmt.precision(icu::number::Precision::minMaxFraction(0, 0).withSignificantDigits(
60                     stoi(options.minSignificantDigits), stoi(options.maxSignificantDigits),
61                     UNUM_ROUNDING_PRIORITY_RELAXED));
62             } else {
63                 fmt = fmt.precision(icu::number::Precision::minMaxSignificantDigits(
64                     stoi(options.minSignificantDigits), stoi(options.maxSignificantDigits)));
65             }
66         } else {
67             fmt = fmt.precision(icu::number::Precision::minSignificantDigits(stoi(options.minSignificantDigits)));
68         }
69     } else {
70         if (!options.maxSignificantDigits.empty()) {
71             fmt = fmt.precision(icu::number::Precision::maxSignificantDigits(stoi(options.maxSignificantDigits)));
72         }
73     }
74     fmt = fmt.integerWidth(icu::number::IntegerWidth::zeroFillTo(stoi(options.minIntegerDigits)));
75     fmt = fmt.roundingMode(UNUM_ROUND_HALFUP);
76     return ANI_OK;
77 }
78 
79 template <typename FormatterType>
ConfigureNotation(ani_env * env,const ParsedOptions & options,FormatterType & fmt)80 ani_status ConfigureNotation(ani_env *env, const ParsedOptions &options, FormatterType &fmt)
81 {
82     if (options.notation == NOTATION_STANDARD) {
83         fmt = fmt.notation(icu::number::Notation::simple());
84     } else if (options.notation == NOTATION_SCIENTIFIC) {
85         fmt = fmt.notation(icu::number::Notation::scientific());
86     } else if (options.notation == NOTATION_ENGINEERING) {
87         fmt = fmt.notation(icu::number::Notation::engineering());
88     } else if (options.notation == NOTATION_COMPACT) {
89         if (options.compactDisplay == COMPACT_DISPLAY_SHORT) {
90             fmt = fmt.notation(icu::number::Notation::compactShort());
91         } else if (options.compactDisplay == COMPACT_DISPLAY_LONG) {
92             fmt = fmt.notation(icu::number::Notation::compactLong());
93         } else {
94             if (!options.compactDisplay.empty()) {
95                 std::string message = "Invalid compactDisplay " + options.compactDisplay;
96                 ThrowNewError(env, ERR_CLS_RUNTIME_EXCEPTION, message.c_str(), CTOR_SIGNATURE_STR);
97                 return ANI_PENDING_ERROR;
98             }
99         }
100     } else {
101         if (!options.notation.empty()) {
102             std::string message = "Invalid compactDisplay " + options.notation;
103             ThrowNewError(env, ERR_CLS_RUNTIME_EXCEPTION, message.c_str(), CTOR_SIGNATURE_STR);
104             return ANI_PENDING_ERROR;
105         }
106     }
107     return ANI_OK;
108 }
109 
IsCorrectUnitIdentifier(const std::string & unit)110 bool IsCorrectUnitIdentifier(const std::string &unit)
111 {
112     // Quick check
113     return REFERENCE_UNITS.find(unit) != REFERENCE_UNITS.end();
114 }
115 
StdStrToIcuUnit(ani_env * env,const std::string & str,icu::MeasureUnit & icuUnit)116 ani_status StdStrToIcuUnit(ani_env *env, const std::string &str, icu::MeasureUnit &icuUnit)
117 {
118     // Handle MeasureUnit
119     UErrorCode status = U_ZERO_ERROR;
120     icuUnit = icu::MeasureUnit::forIdentifier(icu::StringPiece(str.c_str()), status);
121     if (UNLIKELY(U_FAILURE(status))) {
122         std::string message = "Unit input is illegal " + str;
123         ThrowNewError(env, ERR_CLS_RUNTIME_EXCEPTION, message, CTOR_SIGNATURE_STR);
124         return ANI_PENDING_ERROR;
125     }
126     return ANI_OK;
127 }
128 
129 template <typename FormatterType>
ConfigureUnit(ani_env * env,const ParsedOptions & options,FormatterType & fmt)130 ani_status ConfigureUnit(ani_env *env, const ParsedOptions &options, FormatterType &fmt)
131 {
132     ani_status err;
133     icu::MeasureUnit icuUnit;  // Default is empty
134 
135     // If no "-per-" inside string unit
136     auto pos = options.unit.find("-per-");
137     if (IsCorrectUnitIdentifier(options.unit) && pos == std::string::npos) {
138         err = StdStrToIcuUnit(env, options.unit, icuUnit);
139         if (err != ANI_OK) {
140             return err;
141         }
142         fmt = fmt.unit(icuUnit);
143         return ANI_OK;
144     }
145 
146     // If the substring "-per-" invalid
147     size_t afterPos = pos + PER_UNIT_STR_SIZE;
148     if (pos == std::string::npos || options.unit.find("-per-", afterPos) != std::string::npos) {
149         std::string message = "Unit input is illegal " + options.unit;
150         ThrowNewError(env, ERR_CLS_RUNTIME_EXCEPTION, message, CTOR_SIGNATURE_STR);
151         return ANI_PENDING_ERROR;
152     }
153 
154     // First part of unit
155     std::string numerator = options.unit.substr(0, pos);
156     std::string errMessage = "Not a wellformed " + numerator;
157     if (!IsCorrectUnitIdentifier(numerator)) {
158         ThrowNewError(env, ERR_CLS_RUNTIME_EXCEPTION, errMessage, CTOR_SIGNATURE_STR);
159         return ANI_PENDING_ERROR;
160     }
161     err = StdStrToIcuUnit(env, numerator, icuUnit);
162     if (err != ANI_OK) {
163         std::string message = "Unit input is illegal " + options.unit;
164         ThrowNewError(env, ERR_CLS_RUNTIME_EXCEPTION, message, CTOR_SIGNATURE_STR);
165         return err;
166     }
167 
168     // Second part of unit is perUnit
169     std::string denominator = options.unit.substr(pos + PER_UNIT_STR_SIZE);
170     icu::MeasureUnit icuPerUnit = icu::MeasureUnit();
171     errMessage = "Not a wellformed  " + denominator;
172     if (!IsCorrectUnitIdentifier(denominator)) {
173         ThrowNewError(env, ERR_CLS_RUNTIME_EXCEPTION, errMessage, CTOR_SIGNATURE_STR);
174         return ANI_PENDING_ERROR;
175     }
176     err = StdStrToIcuUnit(env, denominator, icuPerUnit);
177     if (err != ANI_OK) {
178         std::string message = "Unit input is illegal " + options.unit;
179         ThrowNewError(env, ERR_CLS_RUNTIME_EXCEPTION, message, CTOR_SIGNATURE_STR);
180         return err;
181     }
182 
183     fmt = fmt.unit(icuUnit);
184     fmt = fmt.perUnit(icuPerUnit);
185     return ANI_OK;
186 }
187 
188 template <typename FormatterType>
ConfigureStyleUnit(ani_env * env,const ParsedOptions & options,FormatterType & fmt)189 ani_status ConfigureStyleUnit(ani_env *env, const ParsedOptions &options, FormatterType &fmt)
190 {
191     ani_status err;
192 
193     // Set unit and perUnit
194     if (!options.unit.empty()) {
195         err = ConfigureUnit<FormatterType>(env, options, fmt);
196         if (err != ANI_OK) {
197             return err;
198         }
199     }
200 
201     // Set unitDisplay
202     if (options.unitDisplay == UNIT_DISPLAY_SHORT) {
203         fmt = fmt.unitWidth(UNUM_UNIT_WIDTH_SHORT);
204     } else if (options.unitDisplay == UNIT_DISPLAY_LONG) {
205         fmt = fmt.unitWidth(UNUM_UNIT_WIDTH_FULL_NAME);
206     } else if (options.unitDisplay == UNIT_DISPLAY_NARROW) {
207         fmt = fmt.unitWidth(UNUM_UNIT_WIDTH_NARROW);
208     } else {
209         if (!options.unitDisplay.empty()) {
210             std::string message = "Invalid unitDisplay " + options.unitDisplay;
211             ThrowNewError(env, ERR_CLS_RUNTIME_EXCEPTION, message.c_str(), CTOR_SIGNATURE_STR);
212             return ANI_PENDING_ERROR;
213         }
214     }
215     return ANI_OK;
216 }
217 
218 template <typename FormatterType>
ConfigureStyleCurrency(ani_env * env,const ParsedOptions & options,FormatterType & fmt)219 ani_status ConfigureStyleCurrency(ani_env *env, const ParsedOptions &options, FormatterType &fmt)
220 {
221     UErrorCode status = U_ZERO_ERROR;
222     icu::CurrencyUnit currency(icu::StringPiece(options.currency.c_str()), status);
223     if (UNLIKELY(U_FAILURE(status))) {
224         std::string message = "Invalid currency " + options.currency;
225         ThrowNewError(env, ERR_CLS_RUNTIME_EXCEPTION, message.c_str(), CTOR_SIGNATURE_STR);
226         return ANI_PENDING_ERROR;
227     }
228     fmt = fmt.unit(currency);
229     // Set currency display
230     if (options.currencyDisplay == CURRENCY_DISPLAY_CODE) {
231         fmt = fmt.unitWidth(UNUM_UNIT_WIDTH_ISO_CODE);
232     } else if (options.currencyDisplay == CURRENCY_DISPLAY_SYMBOL) {
233         fmt = fmt.unitWidth(UNUM_UNIT_WIDTH_SHORT);
234     } else if (options.currencyDisplay == CURRENCY_DISPLAY_NARROWSYMBOL) {
235         fmt = fmt.unitWidth(UNUM_UNIT_WIDTH_NARROW);
236     } else if (options.currencyDisplay == CURRENCY_DISPLAY_NAME) {
237         fmt = fmt.unitWidth(UNUM_UNIT_WIDTH_FULL_NAME);
238     } else {
239         // Default style, no need to set anything
240         if (!options.currencyDisplay.empty()) {
241             std::string message = "Invalid currencyDisplay " + options.currencyDisplay;
242             ThrowNewError(env, ERR_CLS_RUNTIME_EXCEPTION, message.c_str(), CTOR_SIGNATURE_STR);
243             return ANI_PENDING_ERROR;
244         }
245     }
246     return ANI_OK;
247 }
248 
249 template <typename FormatterType>
ConfigureStyle(ani_env * env,const ParsedOptions & options,FormatterType & fmt)250 ani_status ConfigureStyle(ani_env *env, const ParsedOptions &options, FormatterType &fmt)
251 {
252     if (options.style == STYLE_PERCENT) {
253         // Use percent unit
254         fmt = fmt.unit(icu::MeasureUnit::getPercent());
255         fmt = fmt.scale(icu::number::Scale::powerOfTen(STYLE_PERCENT_SCALE_FACTOR));
256     } else if (options.style == STYLE_CURRENCY && !options.currency.empty()) {
257         return ConfigureStyleCurrency(env, options, fmt);
258     } else if (options.style == STYLE_UNIT) {
259         return ConfigureStyleUnit(env, options, fmt);
260     } else {
261         // Default style, no need to set anything
262         if (!options.style.empty() && options.style != STYLE_DECIMAL) {
263             std::string message = "Invalid style " + options.style;
264             ThrowNewError(env, ERR_CLS_RUNTIME_EXCEPTION, message.c_str(), CTOR_SIGNATURE_STR);
265             return ANI_PENDING_ERROR;
266         }
267     }
268     return ANI_OK;
269 }
270 
271 template <typename FormatterType>
ConfigureUseGrouping(ani_env * env,const ParsedOptions & options,FormatterType & fmt)272 ani_status ConfigureUseGrouping(ani_env *env, const ParsedOptions &options, FormatterType &fmt)
273 {
274     if (options.useGrouping == USE_GROUPING_TRUE) {
275         fmt = fmt.grouping(UNumberGroupingStrategy::UNUM_GROUPING_ON_ALIGNED);
276     } else if (options.useGrouping == USE_GROUPING_FALSE) {
277         fmt = fmt.grouping(UNumberGroupingStrategy::UNUM_GROUPING_OFF);
278     } else if (options.useGrouping == USE_GROUPING_MIN2) {
279         fmt = fmt.grouping(UNumberGroupingStrategy::UNUM_GROUPING_MIN2);
280     } else {
281         // Default is UNumberGroupingStrategy::UNUM_GROUPING_AUTO
282         if (!options.useGrouping.empty()) {
283             std::string message = "Invalid useGrouping " + options.useGrouping;
284             ThrowNewError(env, ERR_CLS_RUNTIME_EXCEPTION, message.c_str(), CTOR_SIGNATURE_STR);
285             return ANI_PENDING_ERROR;
286         }
287     }
288     return ANI_OK;
289 }
290 
291 template <typename FormatterType>
ConfigureSignDisplay(ani_env * env,const ParsedOptions & options,FormatterType & fmt)292 ani_status ConfigureSignDisplay(ani_env *env, const ParsedOptions &options, FormatterType &fmt)
293 {
294     // Configure signDisplay
295     if (options.signDisplay == SIGN_DISPLAY_AUTO) {
296         // Handle currency sign
297         if (options.currencySign == CURRENCY_SIGN_ACCOUNTING) {
298             fmt = fmt.sign(UNumberSignDisplay::UNUM_SIGN_ACCOUNTING);
299         } else {
300             // CURRENCY_SIGN_STANDARD
301             fmt = fmt.sign(UNumberSignDisplay::UNUM_SIGN_AUTO);
302         }
303     } else if (options.signDisplay == SIGN_DISPLAY_NEVER) {
304         fmt = fmt.sign(UNumberSignDisplay::UNUM_SIGN_NEVER);
305     } else if (options.signDisplay == SIGN_DISPLAY_ALWAYS) {
306         // Handle currency sign
307         if (options.currencySign == CURRENCY_SIGN_ACCOUNTING) {
308             fmt = fmt.sign(UNumberSignDisplay::UNUM_SIGN_ACCOUNTING_ALWAYS);
309         } else {
310             // CURRENCY_SIGN_STANDARD AND SIGN_DISPLAY_ALWAYS
311             fmt = fmt.sign(UNumberSignDisplay::UNUM_SIGN_ALWAYS);
312         }
313     } else if (options.signDisplay == SIGN_DISPLAY_EXCEPTZERO) {
314         // Handle currency sign
315         if (options.currencySign == CURRENCY_SIGN_ACCOUNTING) {
316             fmt = fmt.sign(UNumberSignDisplay::UNUM_SIGN_ACCOUNTING_EXCEPT_ZERO);
317         } else {
318             // CURRENCY_SIGN_STANDARD AND SIGN_DISPLAY_EXCEPTZERO
319             fmt = fmt.sign(UNumberSignDisplay::UNUM_SIGN_EXCEPT_ZERO);
320         }
321     } else {
322         if (!options.signDisplay.empty()) {
323             ThrowNewError(env, ERR_CLS_RUNTIME_EXCEPTION, "Invalid signDisplay", CTOR_SIGNATURE_STR);
324             return ANI_PENDING_ERROR;
325         }
326     }
327     return ANI_OK;
328 }
329 
InitUnlocNumFormatter(ani_env * env,const ParsedOptions & options,UnlocNumFmt & fmt)330 ani_status InitUnlocNumFormatter(ani_env *env, const ParsedOptions &options, UnlocNumFmt &fmt)
331 {
332     ani_status err = ConfigureNotation<UnlocNumFmt>(env, options, fmt);
333     err = err == ANI_OK ? ConfigurePrecision<UnlocNumFmt>(env, options, fmt) : err;
334     err = err == ANI_OK ? ConfigureStyle<UnlocNumFmt>(env, options, fmt) : err;
335     err = err == ANI_OK ? ConfigureUseGrouping<UnlocNumFmt>(env, options, fmt) : err;
336     err = err == ANI_OK ? ConfigureSignDisplay<UnlocNumFmt>(env, options, fmt) : err;
337     return err;
338 }
339 
InitNumFormatter(ani_env * env,const ParsedOptions & options,LocNumFmt & fmt)340 ani_status InitNumFormatter(ani_env *env, const ParsedOptions &options, LocNumFmt &fmt)
341 {
342     icu::Locale localeWithNumSystem(icu::Locale::getDefault());
343     ani_status err = LocTagToIcuLocale(env, options.locale, localeWithNumSystem);
344     if (err != ANI_OK) {
345         return err;
346     }
347     err = SetNumberingSystemIntoLocale(env, options, localeWithNumSystem);
348     if (err == ANI_OK) {
349         auto unlocFmt = icu::number::NumberFormatter::with();
350         err = InitUnlocNumFormatter(env, options, unlocFmt);
351         if (err == ANI_OK) {
352             fmt = unlocFmt.locale(localeWithNumSystem);
353         }
354     }
355     return err;
356 }
357 
InitNumRangeFormatter(ani_env * env,const ParsedOptions & options,LocNumRangeFmt & fmt)358 ani_status InitNumRangeFormatter(ani_env *env, const ParsedOptions &options, LocNumRangeFmt &fmt)
359 {
360     icu::Locale localeWithNumSystem(icu::Locale::getDefault());
361     ani_status err = LocTagToIcuLocale(env, options.locale, localeWithNumSystem);
362     if (err != ANI_OK) {
363         return err;
364     }
365     err = SetNumberingSystemIntoLocale(env, options, localeWithNumSystem);
366     if (err == ANI_OK) {
367         auto unlocFmt = icu::number::NumberFormatter::with();
368         err = InitUnlocNumFormatter(env, options, unlocFmt);
369         if (err == ANI_OK) {
370             fmt = icu::number::NumberRangeFormatter::withLocale(localeWithNumSystem);
371             fmt = fmt.numberFormatterBoth(unlocFmt);
372         }
373     }
374     return err;
375 }
376 
ParseOptions(ani_env * env,ani_object self,ParsedOptions & options)377 void ParseOptions(ani_env *env, ani_object self, ParsedOptions &options)
378 {
379     ani_ref optionsRef = nullptr;
380     ANI_FATAL_IF_ERROR(env->Object_GetFieldByName_Ref(self, "options", &optionsRef));
381     auto optionsObj = static_cast<ani_object>(optionsRef);
382     ASSERT(optionsObj != nullptr);
383 
384     options.locale = GetFieldStr(env, optionsObj, "_locale");
385     options.compactDisplay = GetFieldStrUndefined(env, optionsObj, "_compactDisplay");
386     options.currencySign = GetFieldStrUndefined(env, optionsObj, "_currencySign");
387     options.currency = GetFieldStrUndefined(env, optionsObj, "_currency");
388     options.currencyDisplay = GetFieldStrUndefined(env, optionsObj, "_currencyDisplay");
389     options.minFractionDigits = GetFieldStr(env, optionsObj, "minFracStr");
390     options.maxFractionDigits = GetFieldStr(env, optionsObj, "maxFracStr");
391     options.minSignificantDigits = GetFieldStrUndefined(env, optionsObj, "minSignStr");
392     options.maxSignificantDigits = GetFieldStrUndefined(env, optionsObj, "maxSignStr");
393     options.minIntegerDigits = GetFieldStr(env, optionsObj, "minIntStr");
394     options.notation = GetFieldStrUndefined(env, optionsObj, "_notation");
395     options.numberingSystem = GetFieldStr(env, optionsObj, "_numberingSystem");
396     options.signDisplay = GetFieldStrUndefined(env, optionsObj, "_signDisplay");
397     options.style = GetFieldStr(env, optionsObj, "_style");
398     options.unit = GetFieldStrUndefined(env, optionsObj, "_unit");
399     options.unitDisplay = GetFieldStrUndefined(env, optionsObj, "_unitDisplay");
400     options.useGrouping = GetFieldStrUndefined(env, optionsObj, "useGroupingStr");
401 }
402 
403 }  // namespace ark::ets::stdlib::intl
404