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 #include "plugins/ets/stdlib/native/core/IntlLocale.h"
16 #include "plugins/ets/stdlib/native/core/IntlLanguageTag.h"
17 #include "plugins/ets/stdlib/native/core/IntlCommon.h"
18 #include "plugins/ets/stdlib/native/core/stdlib_ani_helpers.h"
19 #include <unicode/locid.h>
20 #include <unicode/numsys.h>
21 #include <unicode/localpointer.h>
22 #include "libpandabase/macros.h"
23
24 #include <cassert>
25 #include <cstring>
26 #include <set>
27 #include <array>
28 #include <sstream>
29 #include <vector>
30 #include <algorithm>
31
32 namespace ark::ets::stdlib::intl {
33
34 enum LocaleInfo { LANG, SCRIPT, COUNTRY, U_CA, U_KF, U_CO, U_HC, U_NU, U_KN, COUNT };
35
36 struct LocaleCheckInfo {
37 std::set<std::string> langs;
38 std::set<std::string> regions;
39 std::set<std::string> scripts;
40 };
41
GetLocaleCheckInfo()42 static LocaleCheckInfo &GetLocaleCheckInfo()
43 {
44 static LocaleCheckInfo gCheckInfo;
45 return gCheckInfo;
46 }
47
GetLocaleInfo()48 static std::vector<std::string> &GetLocaleInfo()
49 {
50 static std::vector<std::string> gLocaleInfo(LocaleInfo::COUNT, "");
51 std::fill(gLocaleInfo.begin(), gLocaleInfo.end(), "");
52 return gLocaleInfo;
53 }
54
HandleLocale(const std::string & localeString)55 ParsedLocale HandleLocale(const std::string &localeString)
56 {
57 size_t len = localeString.size();
58 ParsedLocale parsedResult;
59
60 // a. The single-character subtag ’x’ as the primary subtag indicates
61 // that the language tag consists solely of subtags whose meaning is
62 // defined by private agreement.
63 // b. Extensions cannot be used in tags that are entirely private use.
64 if (intl::IsPrivateSubTag(localeString, len)) {
65 parsedResult.base = localeString;
66 return parsedResult;
67 }
68 // If cannot find "-u-", return the whole string as base.
69 size_t foundExtension = localeString.find("-u-");
70 if (foundExtension == std::string::npos) {
71 parsedResult.base = localeString;
72 return parsedResult;
73 }
74 // Let privateIndex be Call(%StringProto_indexOf%, foundLocale, « "-x-" »).
75 size_t privateIndex = localeString.find("-x-");
76 if (privateIndex != std::string::npos && privateIndex < foundExtension) {
77 parsedResult.base = localeString;
78 return parsedResult;
79 }
80 const std::string basis = localeString.substr(0, foundExtension);
81 size_t extensionEnd = len;
82 ASSERT(len > INTL_INDEX_TWO);
83 size_t start = foundExtension + 1;
84 HandleLocaleExtension(start, extensionEnd, localeString, len);
85 const std::string end = localeString.substr(extensionEnd);
86 parsedResult.base = basis + end;
87 parsedResult.extension = localeString.substr(foundExtension, extensionEnd - foundExtension);
88 return parsedResult;
89 }
90
HandleLocaleExtension(size_t & start,size_t & extensionEnd,const std::string & result,size_t len)91 void HandleLocaleExtension(size_t &start, size_t &extensionEnd, const std::string &result, size_t len)
92 {
93 while (start < len - INTL_INDEX_TWO) {
94 if (result[start] != '-') {
95 start++;
96 continue;
97 }
98 if (result[start + INTL_INDEX_TWO] == '-') {
99 extensionEnd = start;
100 break;
101 }
102 start += INTL_INDEX_THREE;
103 }
104 }
105
StdCoreIntlLocaleFillCheckInfo()106 void StdCoreIntlLocaleFillCheckInfo()
107 {
108 int32_t availableCount;
109 const icu::Locale *availableLocales = icu::Locale::getAvailableLocales(availableCount);
110
111 for (int i = 0; i < availableCount; ++i) {
112 // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic)
113 const auto &locale = availableLocales[i];
114 const std::string lang = locale.getLanguage();
115 const std::string region = locale.getCountry();
116 const std::string script = locale.getScript();
117 if (!lang.empty()) {
118 GetLocaleCheckInfo().langs.insert(lang);
119 }
120 if (!region.empty()) {
121 GetLocaleCheckInfo().regions.insert(region);
122 }
123 if (!script.empty()) {
124 GetLocaleCheckInfo().scripts.insert(script);
125 }
126 }
127 }
128
Set2String(std::set<std::string> & inSet)129 std::string Set2String(std::set<std::string> &inSet)
130 {
131 const int lastCommaLength = 1;
132 std::string outList;
133 for (const auto &item : inSet) {
134 outList += item;
135 outList += ",";
136 }
137 if (outList.length() > lastCommaLength) {
138 outList.erase(outList.length() - lastCommaLength);
139 }
140 return outList;
141 }
142
StdCoreIntlLocaleDefaultLang(ani_env * env,ani_class klass)143 ani_string StdCoreIntlLocaleDefaultLang(ani_env *env, [[maybe_unused]] ani_class klass)
144 {
145 const std::string lang = icu::Locale::getDefault().getLanguage();
146 return StdStrToAni(env, lang);
147 }
148
StdCoreIntlLocaleRegionList(ani_env * env,ani_class klass)149 ani_string StdCoreIntlLocaleRegionList(ani_env *env, [[maybe_unused]] ani_class klass)
150 {
151 std::string regionsList = Set2String(GetLocaleCheckInfo().regions);
152 return StdStrToAni(env, regionsList);
153 }
154
StdCoreIntlLocaleLangList(ani_env * env,ani_class klass)155 ani_string StdCoreIntlLocaleLangList(ani_env *env, [[maybe_unused]] ani_class klass)
156 {
157 std::string langsList = Set2String(GetLocaleCheckInfo().langs);
158 return StdStrToAni(env, langsList);
159 }
160
StdCoreIntlLocaleScriptList(ani_env * env,ani_class klass)161 ani_string StdCoreIntlLocaleScriptList(ani_env *env, [[maybe_unused]] ani_class klass)
162 {
163 std::string scriptList = Set2String(GetLocaleCheckInfo().scripts);
164 return StdStrToAni(env, scriptList);
165 }
166
StdCoreIntlLocaleNumberingSystemList(ani_env * env,ani_class klass)167 ani_string StdCoreIntlLocaleNumberingSystemList(ani_env *env, [[maybe_unused]] ani_class klass)
168 {
169 UErrorCode success = U_ZERO_ERROR;
170 std::unique_ptr<icu::StringEnumeration> numberingSystemList(icu::NumberingSystem::getAvailableNames(success));
171 if (UNLIKELY(U_FAILURE(success))) {
172 ASSERT(true);
173 }
174 std::string nsList;
175 while (const icu::UnicodeString *ns = numberingSystemList->snext(success)) {
176 if (UNLIKELY(U_FAILURE(success))) {
177 ASSERT(true);
178 }
179 std::string str;
180 ns->toUTF8String(str);
181 nsList += str;
182 }
183
184 return StdStrToAni(env, nsList);
185 }
186
StdCoreIntlLocaleInfo(ani_env * env,ani_class klass,ani_string lang)187 ani_string StdCoreIntlLocaleInfo(ani_env *env, [[maybe_unused]] ani_class klass, ani_string lang)
188 {
189 std::string langKey = ConvertFromAniString(env, lang);
190
191 UErrorCode success = U_ZERO_ERROR;
192 auto l = icu::Locale::forLanguageTag(langKey, success);
193 if (UNLIKELY(U_FAILURE(success))) {
194 ASSERT(true);
195 }
196 l.addLikelySubtags(success);
197 if (UNLIKELY(U_FAILURE(success))) {
198 ASSERT(true);
199 }
200
201 std::string result;
202 result += l.getCountry();
203 result += ",";
204 result += l.getScript();
205
206 return StdStrToAni(env, result);
207 }
208
StdCoreIntlLocaleIsTagValid(ani_env * env,ani_class klass,ani_string tag)209 ani_int StdCoreIntlLocaleIsTagValid(ani_env *env, [[maybe_unused]] ani_class klass, ani_string tag)
210 {
211 std::string tagKey = ConvertFromAniString(env, tag);
212
213 UErrorCode success = U_ZERO_ERROR;
214 icu::Locale::forLanguageTag(tagKey, success);
215 if (UNLIKELY(U_FAILURE(success))) {
216 return 1;
217 }
218 return 0;
219 }
220
UnicodeKeyToLocaleInfo(const std::string & unicodeKey)221 static int UnicodeKeyToLocaleInfo(const std::string &unicodeKey)
222 {
223 int keyIndex = -1;
224 if (unicodeKey == "ca") {
225 keyIndex = LocaleInfo::U_CA;
226 } else if (unicodeKey == "co") {
227 keyIndex = LocaleInfo::U_CO;
228 } else if (unicodeKey == "kf") {
229 keyIndex = LocaleInfo::U_KF;
230 } else if (unicodeKey == "hc") {
231 keyIndex = LocaleInfo::U_HC;
232 } else if (unicodeKey == "nu") {
233 keyIndex = LocaleInfo::U_NU;
234 } else if (unicodeKey == "kn") {
235 keyIndex = LocaleInfo::U_KN;
236 }
237 return keyIndex;
238 }
239
StdCoreIntlLocaleParseTag(ani_env * env,ani_class klass,ani_string tag)240 ani_ref StdCoreIntlLocaleParseTag(ani_env *env, [[maybe_unused]] ani_class klass, ani_string tag)
241 {
242 std::vector<std::string> data = GetLocaleInfo();
243 std::string tagKey = ConvertFromAniString(env, tag);
244 UErrorCode success = U_ZERO_ERROR;
245
246 auto l = icu::Locale::forLanguageTag(tagKey, success);
247 if (UNLIKELY(U_FAILURE(success))) {
248 std::string message = "Failed to find locale from tag";
249 ThrowNewError(env, "Lstd/core/RuntimeException;", message.c_str(), "Lstd/core/String;:V");
250 return nullptr;
251 }
252
253 data[LocaleInfo::LANG] = l.getLanguage();
254 data[LocaleInfo::SCRIPT] = l.getScript();
255 data[LocaleInfo::COUNTRY] = l.getCountry();
256
257 std::set<std::string> keyWordsSet;
258 l.getUnicodeKeywords<std::string>(std::insert_iterator<decltype(keyWordsSet)>(keyWordsSet, keyWordsSet.begin()),
259 success);
260
261 for (auto &unicodeKey : keyWordsSet) {
262 auto unicodeKeywordValue = l.getUnicodeKeywordValue<std::string>(unicodeKey, success);
263 if (UNLIKELY(U_FAILURE(success))) {
264 std::string message = "Failed to getUnicodeKeywordValue";
265 ThrowNewError(env, "Lstd/core/RuntimeException;", message.c_str(), "Lstd/core/String;:V");
266 return nullptr;
267 }
268 int keyIndex = UnicodeKeyToLocaleInfo(unicodeKey);
269 if (keyIndex >= LocaleInfo::U_CA && keyIndex < LocaleInfo::COUNT) {
270 data[keyIndex] = unicodeKeywordValue;
271 }
272 }
273
274 std::stringstream s;
275 for (int i = 0; i < LocaleInfo::COUNT; ++i) {
276 s << (data[i]) << " ";
277 }
278 std::string res = s.str();
279 if (res.length() > 1) {
280 res.resize(res.length() - 1);
281 }
282
283 return StdStrToAni(env, res);
284 }
285
StdCoreIntlLocaleDefaultTag(ani_env * env)286 ani_string StdCoreIntlLocaleDefaultTag(ani_env *env)
287 {
288 auto defaultLocale = icu::Locale::getDefault();
289 auto defaultLocaleName = defaultLocale.getName();
290 if (strcmp(defaultLocaleName, "en_US_POSIX") == 0 || strcmp(defaultLocaleName, "c") == 0) {
291 return StdStrToAni(env, "en-US");
292 }
293 auto status = UErrorCode::U_ZERO_ERROR;
294 auto tag = defaultLocale.toLanguageTag<std::string>(status);
295 if (UNLIKELY(U_FAILURE(status))) {
296 std::string message = "Error receiving default locale language tag: ";
297 message += u_errorName(status);
298 ThrowNewError(env, "Lstd/core/RuntimeException;", message.c_str(), "Lstd/core/String;:V");
299 return nullptr;
300 }
301 return StdStrToAni(env, tag);
302 }
303
RegisterIntlLocaleNativeMethods(ani_env * env)304 ani_status RegisterIntlLocaleNativeMethods(ani_env *env)
305 {
306 const auto methods = std::array {
307 ani_native_function {"initLocale", ":V", reinterpret_cast<void *>(StdCoreIntlLocaleFillCheckInfo)},
308 ani_native_function {"maximizeInfo", "Lstd/core/String;:Lstd/core/String;",
309 reinterpret_cast<void *>(StdCoreIntlLocaleInfo)},
310 ani_native_function {"regionList", ":Lstd/core/String;", reinterpret_cast<void *>(StdCoreIntlLocaleRegionList)},
311 ani_native_function {"langList", ":Lstd/core/String;", reinterpret_cast<void *>(StdCoreIntlLocaleLangList)},
312 ani_native_function {"scriptList", ":Lstd/core/String;", reinterpret_cast<void *>(StdCoreIntlLocaleScriptList)},
313 ani_native_function {"numberingSystemList", ":Lstd/core/String;",
314 reinterpret_cast<void *>(StdCoreIntlLocaleNumberingSystemList)},
315 ani_native_function {"defaultLang", ":Lstd/core/String;",
316 reinterpret_cast<void *>(StdCoreIntlLocaleDefaultLang)},
317 ani_native_function {"isTagValid", "Lstd/core/String;:I",
318 reinterpret_cast<void *>(StdCoreIntlLocaleIsTagValid)},
319 ani_native_function {"parseTagImpl", "Lstd/core/String;:Lstd/core/String;",
320 reinterpret_cast<void *>(StdCoreIntlLocaleParseTag)},
321 ani_native_function {"defaultTag", ":Lstd/core/String;",
322 reinterpret_cast<void *>(StdCoreIntlLocaleDefaultTag)}};
323
324 ani_class localeClass;
325 ANI_FATAL_IF_ERROR(env->FindClass("Lstd/core/Intl/Locale;", &localeClass));
326 return env->Class_BindNativeMethods(localeClass, methods.data(), methods.size());
327 }
328
StdCoreIntlLocaleDefaultBaseName(ani_env * env,ani_class klass)329 ani_string StdCoreIntlLocaleDefaultBaseName(ani_env *env, [[maybe_unused]] ani_class klass)
330 {
331 const std::string name = icu::Locale::getDefault().getBaseName();
332 return StdStrToAni(env, name);
333 }
334
335 } // namespace ark::ets::stdlib::intl
336