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