• 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 <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