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 <unicode/unistr.h>
17 #include <unicode/locid.h>
18 #include <unicode/reldatefmt.h>
19 #include <unicode/udat.h>
20 #include <unicode/localematcher.h>
21 #include <set>
22 #include <algorithm>
23 #include <array>
24 #include "libpandabase/macros.h"
25 #include "plugins/ets/stdlib/native/core/IntlRelativeTimeFormat.h"
26 #include "plugins/ets/stdlib/native/core/stdlib_ani_helpers.h"
27 #include "plugins/ets/stdlib/native/core/IntlCommon.h"
28 #include "plugins/ets/stdlib/native/core/IntlLocaleMatch.h"
29 #include "plugins/ets/runtime/ets_exceptions.h"
30 #include "ani/ani_checkers.h"
31
32 namespace ark::ets::stdlib::intl {
33
CallOptionGetter(ani_env * env,ani_object options,const char * getterName)34 static std::string CallOptionGetter(ani_env *env, ani_object options, const char *getterName)
35 {
36 ani_class optionsClass = nullptr;
37 if (env->FindClass("Lstd/core/Intl/RelativeTimeFormatOptionsImpl;", &optionsClass) != ANI_OK ||
38 optionsClass == nullptr) {
39 return "";
40 }
41
42 ani_method getter = nullptr;
43 if (env->Class_FindGetter(optionsClass, getterName, &getter) != ANI_OK) {
44 return "";
45 }
46
47 ani_ref resultRef = nullptr;
48 // NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg)
49 if (env->Object_CallMethod_Ref(options, getter, &resultRef) != ANI_OK || resultRef == nullptr) {
50 return "";
51 }
52
53 auto resultStr = static_cast<ani_string>(resultRef);
54 ani_size len = 0;
55 env->String_GetUTF8Size(resultStr, &len);
56 std::vector<char> buf(len + 1);
57 ani_size copied = 0;
58 env->String_GetUTF8(resultStr, buf.data(), buf.size(), &copied);
59 return std::string(buf.data());
60 }
61
ThrowError(ani_env * env,const char * errorClassName,const std::string & message)62 static ani_status ThrowError(ani_env *env, const char *errorClassName, const std::string &message)
63 {
64 ani_status aniStatus = ANI_OK;
65
66 ani_class errorClass = nullptr;
67 aniStatus = env->FindClass(errorClassName, &errorClass);
68 ANI_CHECK_RETURN_IF_NE(aniStatus, ANI_OK, aniStatus);
69
70 ani_method errorCtor;
71 aniStatus = env->Class_FindMethod(errorClass, "<ctor>", "Lstd/core/String;:V", &errorCtor);
72 ANI_CHECK_RETURN_IF_NE(aniStatus, ANI_OK, aniStatus);
73
74 ani_string errorMsg;
75 aniStatus = env->String_NewUTF8(message.data(), message.size(), &errorMsg);
76 ANI_CHECK_RETURN_IF_NE(aniStatus, ANI_OK, aniStatus);
77
78 ani_object errorObj;
79 // NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg)
80 aniStatus = env->Object_New(errorClass, errorCtor, &errorObj, errorMsg);
81 ANI_CHECK_RETURN_IF_NE(aniStatus, ANI_OK, aniStatus);
82
83 return env->ThrowError(static_cast<ani_error>(errorObj));
84 }
85
ThrowRangeError(ani_env * env,const std::string & message)86 static ani_status ThrowRangeError(ani_env *env, const std::string &message)
87 {
88 ani_class errorClass = nullptr;
89 if (env->FindClass("Lstd/core/RangeError;", &errorClass) != ANI_OK) {
90 return ANI_PENDING_ERROR;
91 }
92 ani_method ctor;
93 if (env->Class_FindMethod(errorClass, "<ctor>", "Lstd/core/String;:V", &ctor) != ANI_OK) {
94 return ANI_PENDING_ERROR;
95 }
96 ani_string msgStr;
97 if (env->String_NewUTF8(message.c_str(), message.length(), &msgStr) != ANI_OK) {
98 return ANI_PENDING_ERROR;
99 }
100 ani_object err;
101 // NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg)
102 if (env->Object_New(errorClass, ctor, &err, msgStr) != ANI_OK) {
103 return ANI_PENDING_ERROR;
104 }
105 return env->ThrowError(static_cast<ani_error>(err));
106 }
107
ThrowInternalError(ani_env * env,const std::string & message)108 static ani_status ThrowInternalError(ani_env *env, const std::string &message)
109 {
110 return ThrowError(env, "Lstd/core/InternalError;", message);
111 }
112
ToIcuLocale(ani_env * env,ani_object self)113 static std::unique_ptr<icu::Locale> ToIcuLocale(ani_env *env, ani_object self)
114 {
115 ani_ref localeRef = nullptr;
116 ani_status aniStatus = env->Object_GetFieldByName_Ref(self, "locale", &localeRef);
117 if (aniStatus != ANI_OK || localeRef == nullptr) {
118 return std::make_unique<icu::Locale>(icu::Locale::getDefault());
119 }
120
121 auto localeStr = static_cast<ani_string>(localeRef);
122
123 ani_boolean isUndefined = ANI_FALSE;
124 aniStatus = env->Reference_IsUndefined(localeStr, &isUndefined);
125 if (aniStatus != ANI_OK || isUndefined == ANI_TRUE) {
126 return std::make_unique<icu::Locale>(icu::Locale::getDefault());
127 }
128
129 ani_size strSize = 0;
130 env->String_GetUTF8Size(localeStr, &strSize);
131 std::vector<char> buf(strSize + 1);
132 ani_size copied = 0;
133 env->String_GetUTF8(localeStr, buf.data(), buf.size(), &copied);
134
135 icu::Locale locale(buf.data());
136 if (locale.isBogus() != 0) {
137 ThrowRangeError(env, "Invalid locale: " + std::string(buf.data()));
138 return nullptr;
139 }
140
141 return std::make_unique<icu::Locale>(locale);
142 }
143
ToICUUnitOrThrow(ani_env * env,const std::string & unitStr)144 static URelativeDateTimeUnit ToICUUnitOrThrow(ani_env *env, const std::string &unitStr)
145 {
146 std::string unit = unitStr;
147 std::transform(unit.begin(), unit.end(), unit.begin(), ::tolower);
148
149 if (!unit.empty() && unit.back() == 's') {
150 unit.pop_back();
151 }
152
153 if (unit == "second") {
154 return UDAT_REL_UNIT_SECOND;
155 }
156 if (unit == "minute") {
157 return UDAT_REL_UNIT_MINUTE;
158 }
159 if (unit == "hour") {
160 return UDAT_REL_UNIT_HOUR;
161 }
162 if (unit == "day") {
163 return UDAT_REL_UNIT_DAY;
164 }
165 if (unit == "week") {
166 return UDAT_REL_UNIT_WEEK;
167 }
168 if (unit == "month") {
169 return UDAT_REL_UNIT_MONTH;
170 }
171 if (unit == "quarter") {
172 return UDAT_REL_UNIT_QUARTER;
173 }
174 if (unit == "year") {
175 return UDAT_REL_UNIT_YEAR;
176 }
177 ThrowNewError(env, "Lstd/core/RuntimeException;", ("Invalid unit: " + unitStr).c_str(), "Lstd/core/String;:V");
178 return static_cast<URelativeDateTimeUnit>(-1);
179 }
180
StdCoreIntlRelativeTimeFormatResolvedOptionsImpl(ani_env * env,ani_object self)181 static ani_object StdCoreIntlRelativeTimeFormatResolvedOptionsImpl(ani_env *env, ani_object self)
182 {
183 std::unique_ptr<icu::Locale> icuLocale = ToIcuLocale(env, self);
184 if (!icuLocale) {
185 return nullptr;
186 }
187 UErrorCode status = U_ZERO_ERROR;
188 auto langTagStr = icuLocale->toLanguageTag<std::string>(status);
189 if (U_FAILURE(status) != 0) {
190 ThrowInternalError(env, "failed to get locale lang tag: " + std::string(u_errorName(status)));
191 return nullptr;
192 }
193
194 std::string numberingSystemStr = "latn";
195 std::string numericStr = "always";
196 std::string styleStr = "long";
197
198 ani_ref optionsRef = nullptr;
199 if (env->Object_GetFieldByName_Ref(self, "options", &optionsRef) == ANI_OK && optionsRef != nullptr) {
200 auto options = static_cast<ani_object>(optionsRef);
201
202 std::string maybeNumeric = CallOptionGetter(env, options, "numeric");
203 std::string maybeStyle = CallOptionGetter(env, options, "style");
204
205 if (!maybeNumeric.empty()) {
206 numericStr = maybeNumeric;
207 }
208 if (!maybeStyle.empty()) {
209 styleStr = maybeStyle;
210 }
211 }
212
213 ani_string locale = CreateUtf8String(env, langTagStr.c_str(), langTagStr.length());
214 ani_string numberingSystem = CreateUtf8String(env, numberingSystemStr.c_str(), numberingSystemStr.length());
215 ani_string numeric = CreateUtf8String(env, numericStr.c_str(), numericStr.length());
216 ani_string style = CreateUtf8String(env, styleStr.c_str(), styleStr.length());
217
218 ani_class optsCls = nullptr;
219 ANI_FATAL_IF_ERROR(env->FindClass("Lstd/core/Intl/ResolvedRelativeTimeFormatOptionsImpl;", &optsCls));
220 ani_method ctor = nullptr;
221 ANI_FATAL_IF_ERROR(env->Class_FindMethod(optsCls, "<ctor>", "Lstd/core/String;Lstd/core/String;:V", &ctor));
222
223 ani_object resolvedOpts = nullptr;
224 // NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg)
225 ANI_FATAL_IF_ERROR(env->Object_New(optsCls, ctor, &resolvedOpts, locale, numberingSystem));
226
227 ani_method numericSetter = nullptr;
228 ANI_FATAL_IF_ERROR(env->Class_FindSetter(optsCls, "numeric", &numericSetter));
229 // NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg)
230 ANI_FATAL_IF_ERROR(env->Object_CallMethod_Void(resolvedOpts, numericSetter, numeric));
231
232 ani_method styleSetter = nullptr;
233 ANI_FATAL_IF_ERROR(env->Class_FindSetter(optsCls, "style", &styleSetter));
234 // NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg)
235 ANI_FATAL_IF_ERROR(env->Object_CallMethod_Void(resolvedOpts, styleSetter, style));
236
237 return resolvedOpts;
238 }
239
StdCoreIntlRelativeTimeFormatFormatImpl(ani_env * env,ani_object self,ani_double value,ani_string unit)240 static ani_string StdCoreIntlRelativeTimeFormatFormatImpl(ani_env *env, ani_object self, ani_double value,
241 ani_string unit)
242 {
243 std::unique_ptr<icu::Locale> icuLocale = ToIcuLocale(env, self);
244 if (!icuLocale) {
245 return nullptr;
246 }
247 UErrorCode status = U_ZERO_ERROR;
248 icu::RelativeDateTimeFormatter formatter(*icuLocale, status);
249 if (U_FAILURE(status) != 0) {
250 return nullptr;
251 }
252 ani_size unitSize = 0;
253 env->String_GetUTF8Size(unit, &unitSize);
254 std::vector<char> unitBuf(unitSize + 1);
255 ani_size copiedChars = 0;
256 env->String_GetUTF8(unit, unitBuf.data(), unitBuf.size(), &copiedChars);
257
258 URelativeDateTimeUnit icuUnit = ToICUUnitOrThrow(env, std::string(unitBuf.data()));
259
260 icu::UnicodeString result;
261 formatter.format(value, icuUnit, result, status);
262 if (U_FAILURE(status) != 0) {
263 return nullptr;
264 }
265 ani_string output;
266 env->String_NewUTF16(reinterpret_cast<const uint16_t *>(result.getBuffer()), result.length(), &output);
267 return output;
268 }
269
RegisterIntlRelativeTimeFormatMethods(ani_env * env)270 ani_status RegisterIntlRelativeTimeFormatMethods(ani_env *env)
271 {
272 std::array methods = {
273 ani_native_function {"formatImpl", "DLstd/core/String;:Lstd/core/String;",
274 reinterpret_cast<void *>(StdCoreIntlRelativeTimeFormatFormatImpl)},
275 ani_native_function {"resolvedOptionsImpl", ":Lstd/core/Intl/ResolvedRelativeTimeFormatOptions;",
276 reinterpret_cast<void *>(StdCoreIntlRelativeTimeFormatResolvedOptionsImpl)}};
277 ani_class rtfClass;
278 ANI_FATAL_IF_ERROR(env->FindClass("Lstd/core/Intl/RelativeTimeFormat;", &rtfClass));
279
280 return env->Class_BindNativeMethods(rtfClass, methods.data(), methods.size());
281 }
282
283 } // namespace ark::ets::stdlib::intl
284