1 /*
2 * Copyright (C) 2008 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 #define LOG_NDEBUG 1
18 #define LOG_TAG "LocaleDataTest"
19
20 #include <string.h>
21 #include <memory>
22
23 #include <log/log.h>
24 #include <nativehelper/JNIHelp.h>
25 #include <nativehelper/ScopedLocalRef.h>
26 #include <nativehelper/ScopedStringChars.h>
27 #include <nativehelper/ScopedUtfChars.h>
28
29 #include "ScopedIcuLocale.h"
30 #include "unicode/brkiter.h"
31 #include "unicode/calendar.h"
32 #include "unicode/dcfmtsym.h"
33 #include "unicode/decimfmt.h"
34 #include "unicode/dtfmtsym.h"
35 #include "unicode/locid.h"
36 #include "unicode/numfmt.h"
37 #include "unicode/strenum.h"
38 #include "unicode/ucasemap.h"
39 #include "unicode/udat.h"
40 #include "unicode/uloc.h"
41 #include "unicode/ures.h"
42 #include "unicode/ustring.h"
43
44
45 static jclass icu4cLocaleDataClass = nullptr;
46
47 class ScopedResourceBundle {
48 public:
ScopedResourceBundle(UResourceBundle * bundle)49 explicit ScopedResourceBundle(UResourceBundle* bundle) : bundle_(bundle) {
50 }
51
~ScopedResourceBundle()52 ~ScopedResourceBundle() {
53 if (bundle_ != NULL) {
54 ures_close(bundle_);
55 }
56 }
57
get()58 UResourceBundle* get() {
59 return bundle_;
60 }
61
hasKey(const char * key)62 bool hasKey(const char* key) {
63 UErrorCode status = U_ZERO_ERROR;
64 ures_getStringByKey(bundle_, key, NULL, &status);
65 return U_SUCCESS(status);
66 }
67
68 private:
69 UResourceBundle* bundle_;
70 DISALLOW_COPY_AND_ASSIGN(ScopedResourceBundle);
71 };
72
73 template <typename T>
valueOf(JNIEnv * env,jclass c,const char * signature,const T & value)74 static jobject valueOf(JNIEnv* env, jclass c, const char* signature, const T& value) {
75 static jmethodID valueOfMethod = env->GetStaticMethodID(c, "valueOf", signature);
76 if (env->ExceptionCheck()) {
77 return NULL;
78 }
79 jobject result = env->CallStaticObjectMethod(c, valueOfMethod, value);
80 if (env->ExceptionCheck()) {
81 return NULL;
82 }
83 return result;
84 }
85
integerValueOf(JNIEnv * env,jint value)86 static jobject integerValueOf(JNIEnv* env, jint value) {
87 return valueOf(env, env->FindClass("java/lang/Integer"), "(I)Ljava/lang/Integer;", value);
88 }
89
90
setIntegerField(JNIEnv * env,jobject obj,const char * fieldName,int value)91 static bool setIntegerField(JNIEnv* env, jobject obj, const char* fieldName, int value) {
92 ScopedLocalRef<jobject> integerValue(env, integerValueOf(env, value));
93 if (integerValue.get() == NULL) return false;
94 jfieldID fid = env->GetFieldID(icu4cLocaleDataClass, fieldName, "Ljava/lang/Integer;");
95 env->SetObjectField(obj, fid, integerValue.get());
96 return true;
97 }
98
setStringField(JNIEnv * env,jobject obj,const char * fieldName,jstring value)99 static void setStringField(JNIEnv* env, jobject obj, const char* fieldName, jstring value) {
100 jfieldID fid = env->GetFieldID(icu4cLocaleDataClass, fieldName, "Ljava/lang/String;");
101 env->SetObjectField(obj, fid, value);
102 env->DeleteLocalRef(value);
103 }
104
setStringArrayField(JNIEnv * env,jobject obj,const char * fieldName,jobjectArray value)105 static void setStringArrayField(JNIEnv* env, jobject obj, const char* fieldName, jobjectArray value) {
106 jfieldID fid = env->GetFieldID(icu4cLocaleDataClass, fieldName, "[Ljava/lang/String;");
107 env->SetObjectField(obj, fid, value);
108 }
109
setStringArrayField(JNIEnv * env,jobject obj,const char * fieldName,const icu::UnicodeString * valueArray,int32_t size)110 static void setStringArrayField(JNIEnv* env, jobject obj, const char* fieldName, const icu::UnicodeString* valueArray, int32_t size) {
111 ScopedLocalRef<jobjectArray> result(env, env->NewObjectArray(size, env->FindClass("java/lang/String"), NULL));
112 for (int32_t i = 0; i < size ; i++) {
113 ScopedLocalRef<jstring> s(env, jniCreateString(env, valueArray[i].getBuffer(),valueArray[i].length()));
114 if (env->ExceptionCheck()) {
115 return;
116 }
117 env->SetObjectArrayElement(result.get(), i, s.get());
118 if (env->ExceptionCheck()) {
119 return;
120 }
121 }
122 setStringArrayField(env, obj, fieldName, result.get());
123 }
124
setStringField(JNIEnv * env,jobject obj,const char * fieldName,UResourceBundle * bundle,int index)125 static void setStringField(JNIEnv* env, jobject obj, const char* fieldName, UResourceBundle* bundle, int index) {
126 UErrorCode status = U_ZERO_ERROR;
127 int charCount;
128 const UChar* chars;
129 UResourceBundle* currentBundle = ures_getByIndex(bundle, index, NULL, &status);
130 switch (ures_getType(currentBundle)) {
131 case URES_STRING:
132 chars = ures_getString(currentBundle, &charCount, &status);
133 break;
134 case URES_ARRAY:
135 // In case there is an array, Android currently only cares about the
136 // first string of that array, the rest of the array is used by ICU
137 // for additional data ignored by Android.
138 chars = ures_getStringByIndex(currentBundle, 0, &charCount, &status);
139 break;
140 default:
141 status = U_INVALID_FORMAT_ERROR;
142 }
143 ures_close(currentBundle);
144
145 if (U_SUCCESS(status)) {
146 setStringField(env, obj, fieldName, jniCreateString(env, chars, charCount));
147 } else {
148 ALOGE("Error setting String field %s from ICU resource (index %d): %s", fieldName, index, u_errorName(status));
149 }
150 }
151
setCharField(JNIEnv * env,jobject obj,const char * fieldName,const icu::UnicodeString & value)152 static void setCharField(JNIEnv* env, jobject obj, const char* fieldName, const icu::UnicodeString& value) {
153 if (value.length() == 0) {
154 return;
155 }
156 jfieldID fid = env->GetFieldID(icu4cLocaleDataClass, fieldName, "C");
157 env->SetCharField(obj, fid, value.charAt(0));
158 }
159
setStringField(JNIEnv * env,jobject obj,const char * fieldName,const icu::UnicodeString & value)160 static void setStringField(JNIEnv* env, jobject obj, const char* fieldName, const icu::UnicodeString& value) {
161 const UChar* chars = value.getBuffer();
162 setStringField(env, obj, fieldName, jniCreateString(env, chars, value.length()));
163 }
164
setNumberPatterns(JNIEnv * env,jobject obj,icu::Locale & locale)165 static void setNumberPatterns(JNIEnv* env, jobject obj, icu::Locale& locale) {
166 UErrorCode status = U_ZERO_ERROR;
167
168 icu::UnicodeString pattern;
169 std::unique_ptr<icu::DecimalFormat> fmt(static_cast<icu::DecimalFormat*>(icu::NumberFormat::createInstance(locale, UNUM_CURRENCY, status)));
170 pattern = fmt->toPattern(pattern.remove());
171 setStringField(env, obj, "currencyPattern", pattern);
172
173 fmt.reset(static_cast<icu::DecimalFormat*>(icu::NumberFormat::createInstance(locale, UNUM_DECIMAL, status)));
174 pattern = fmt->toPattern(pattern.remove());
175 setStringField(env, obj, "numberPattern", pattern);
176
177 fmt.reset(static_cast<icu::DecimalFormat*>(icu::NumberFormat::createInstance(locale, UNUM_PERCENT, status)));
178 pattern = fmt->toPattern(pattern.remove());
179 setStringField(env, obj, "percentPattern", pattern);
180 }
181
setDecimalFormatSymbolsData(JNIEnv * env,jobject obj,icu::Locale & locale)182 static void setDecimalFormatSymbolsData(JNIEnv* env, jobject obj, icu::Locale& locale) {
183 UErrorCode status = U_ZERO_ERROR;
184 icu::DecimalFormatSymbols dfs(locale, status);
185
186 setCharField(env, obj, "decimalSeparator", dfs.getSymbol(icu::DecimalFormatSymbols::kDecimalSeparatorSymbol));
187 setCharField(env, obj, "groupingSeparator", dfs.getSymbol(icu::DecimalFormatSymbols::kGroupingSeparatorSymbol));
188 setCharField(env, obj, "patternSeparator", dfs.getSymbol(icu::DecimalFormatSymbols::kPatternSeparatorSymbol));
189 setStringField(env, obj, "percent", dfs.getSymbol(icu::DecimalFormatSymbols::kPercentSymbol));
190 setStringField(env, obj, "perMill", dfs.getSymbol(icu::DecimalFormatSymbols::kPerMillSymbol));
191 setCharField(env, obj, "monetarySeparator", dfs.getSymbol(icu::DecimalFormatSymbols::kMonetarySeparatorSymbol));
192 setStringField(env, obj, "minusSign", dfs.getSymbol(icu::DecimalFormatSymbols:: kMinusSignSymbol));
193 setStringField(env, obj, "exponentSeparator", dfs.getSymbol(icu::DecimalFormatSymbols::kExponentialSymbol));
194 setStringField(env, obj, "infinity", dfs.getSymbol(icu::DecimalFormatSymbols::kInfinitySymbol));
195 setStringField(env, obj, "NaN", dfs.getSymbol(icu::DecimalFormatSymbols::kNaNSymbol));
196 setCharField(env, obj, "zeroDigit", dfs.getSymbol(icu::DecimalFormatSymbols::kZeroDigitSymbol));
197 }
198
199
200 // Iterates up through the locale hierarchy. So "en_US" would return "en_US", "en", "".
201 class LocaleNameIterator {
202 public:
LocaleNameIterator(const char * locale_name,UErrorCode & status)203 LocaleNameIterator(const char* locale_name, UErrorCode& status) : status_(status), has_next_(true) {
204 strcpy(locale_name_, locale_name);
205 locale_name_length_ = strlen(locale_name_);
206 }
207
Get()208 const char* Get() {
209 return locale_name_;
210 }
211
HasNext()212 bool HasNext() {
213 return has_next_;
214 }
215
Up()216 void Up() {
217 if (locale_name_length_ == 0) {
218 has_next_ = false;
219 } else {
220 locale_name_length_ = uloc_getParent(locale_name_, locale_name_, sizeof(locale_name_), &status_);
221 }
222 }
223
224 private:
225 UErrorCode& status_;
226 bool has_next_;
227 char locale_name_[ULOC_FULLNAME_CAPACITY];
228 int32_t locale_name_length_;
229
230 DISALLOW_COPY_AND_ASSIGN(LocaleNameIterator);
231 };
232
getAmPmMarkersNarrow(JNIEnv * env,jobject localeData,const char * locale_name)233 static bool getAmPmMarkersNarrow(JNIEnv* env, jobject localeData, const char* locale_name) {
234 UErrorCode status = U_ZERO_ERROR;
235 ScopedResourceBundle root(ures_open(NULL, locale_name, &status));
236 if (U_FAILURE(status)) {
237 return false;
238 }
239 ScopedResourceBundle calendar(ures_getByKey(root.get(), "calendar", NULL, &status));
240 if (U_FAILURE(status)) {
241 return false;
242 }
243 ScopedResourceBundle gregorian(ures_getByKey(calendar.get(), "gregorian", NULL, &status));
244 if (U_FAILURE(status)) {
245 return false;
246 }
247 ScopedResourceBundle amPmMarkersNarrow(ures_getByKey(gregorian.get(), "AmPmMarkersNarrow", NULL, &status));
248 if (U_FAILURE(status)) {
249 return false;
250 }
251 setStringField(env, localeData, "narrowAm", amPmMarkersNarrow.get(), 0);
252 setStringField(env, localeData, "narrowPm", amPmMarkersNarrow.get(), 1);
253 return true;
254 }
255
getDateTimePatterns(JNIEnv * env,jobject localeData,const char * locale_name)256 static bool getDateTimePatterns(JNIEnv* env, jobject localeData, const char* locale_name) {
257 UErrorCode status = U_ZERO_ERROR;
258 ScopedResourceBundle root(ures_open(NULL, locale_name, &status));
259 if (U_FAILURE(status)) {
260 return false;
261 }
262 ScopedResourceBundle calendar(ures_getByKey(root.get(), "calendar", NULL, &status));
263 if (U_FAILURE(status)) {
264 return false;
265 }
266 ScopedResourceBundle gregorian(ures_getByKey(calendar.get(), "gregorian", NULL, &status));
267 if (U_FAILURE(status)) {
268 return false;
269 }
270 ScopedResourceBundle dateTimePatterns(ures_getByKey(gregorian.get(), "DateTimePatterns", NULL, &status));
271 if (U_FAILURE(status)) {
272 return false;
273 }
274 setStringField(env, localeData, "fullTimeFormat", dateTimePatterns.get(), 0);
275 setStringField(env, localeData, "longTimeFormat", dateTimePatterns.get(), 1);
276 setStringField(env, localeData, "mediumTimeFormat", dateTimePatterns.get(), 2);
277 setStringField(env, localeData, "shortTimeFormat", dateTimePatterns.get(), 3);
278 setStringField(env, localeData, "fullDateFormat", dateTimePatterns.get(), 4);
279 setStringField(env, localeData, "longDateFormat", dateTimePatterns.get(), 5);
280 setStringField(env, localeData, "mediumDateFormat", dateTimePatterns.get(), 6);
281 setStringField(env, localeData, "shortDateFormat", dateTimePatterns.get(), 7);
282 return true;
283 }
284
Java_libcore_libcore_icu_LocaleDataTest_initIcu4cLocaleData(JNIEnv * env,jclass,jstring javaLanguageTag,jobject icu4cLocaleData,jobject dataClass)285 extern "C" jboolean Java_libcore_libcore_icu_LocaleDataTest_initIcu4cLocaleData(JNIEnv* env,
286 jclass, jstring javaLanguageTag, jobject icu4cLocaleData, jobject dataClass) {
287 if (icu4cLocaleDataClass == nullptr) {
288 // It's okay to hold the class in the test.
289 icu4cLocaleDataClass = reinterpret_cast<jclass>(env->NewGlobalRef(dataClass));
290 }
291
292 // alias pointer
293 jobject localeData = icu4cLocaleData;
294
295 ScopedUtfChars languageTag(env, javaLanguageTag);
296 if (languageTag.c_str() == NULL) {
297 return JNI_FALSE;
298 }
299 if (languageTag.size() >= ULOC_FULLNAME_CAPACITY) {
300 return JNI_FALSE; // ICU has a fixed-length limit.
301 }
302
303 ScopedIcuLocale icuLocale(env, javaLanguageTag);
304 if (!icuLocale.valid()) {
305 return JNI_FALSE;
306 }
307
308 // Get the DateTimePatterns.
309 UErrorCode status = U_ZERO_ERROR;
310 bool foundDateTimePatterns = false;
311 for (LocaleNameIterator it(icuLocale.locale().getBaseName(), status); it.HasNext(); it.Up()) {
312 if (getDateTimePatterns(env, localeData, it.Get())) {
313 foundDateTimePatterns = true;
314 break;
315 }
316 }
317 if (!foundDateTimePatterns) {
318 ALOGE("Couldn't find ICU DateTimePatterns for %s", languageTag.c_str());
319 return JNI_FALSE;
320 }
321
322 // Get the narrow "AM" and "PM" strings.
323 bool foundAmPmMarkersNarrow = false;
324 for (LocaleNameIterator it(icuLocale.locale().getBaseName(), status); it.HasNext(); it.Up()) {
325 if (getAmPmMarkersNarrow(env, localeData, it.Get())) {
326 foundAmPmMarkersNarrow = true;
327 break;
328 }
329 }
330 if (!foundAmPmMarkersNarrow) {
331 ALOGE("Couldn't find ICU AmPmMarkersNarrow for %s", languageTag.c_str());
332 return JNI_FALSE;
333 }
334
335 status = U_ZERO_ERROR;
336 std::unique_ptr<icu::Calendar> cal(icu::Calendar::createInstance(icuLocale.locale(), status));
337 if (U_FAILURE(status)) {
338 return JNI_FALSE;
339 }
340 if (!setIntegerField(env, localeData, "firstDayOfWeek", cal->getFirstDayOfWeek())) {
341 return JNI_FALSE;
342 }
343 if (!setIntegerField(env, localeData, "minimalDaysInFirstWeek", cal->getMinimalDaysInFirstWeek())) {
344 return JNI_FALSE;
345 }
346
347 // Get DateFormatSymbols.
348 status = U_ZERO_ERROR;
349 icu::DateFormatSymbols dateFormatSym(icuLocale.locale(), status);
350 if (U_FAILURE(status)) {
351 return JNI_FALSE;
352 }
353
354 // Get AM/PM and BC/AD.
355 int32_t count = 0;
356 const icu::UnicodeString* amPmStrs = dateFormatSym.getAmPmStrings(count);
357 setStringArrayField(env, localeData, "amPm", amPmStrs, count);
358 const icu::UnicodeString* erasStrs = dateFormatSym.getEras(count);
359 setStringArrayField(env, localeData, "eras", erasStrs, count);
360
361 const icu::UnicodeString* longMonthNames =
362 dateFormatSym.getMonths(count, icu::DateFormatSymbols::FORMAT, icu::DateFormatSymbols::WIDE);
363 setStringArrayField(env, localeData, "longMonthNames", longMonthNames, count);
364 const icu::UnicodeString* shortMonthNames =
365 dateFormatSym.getMonths(count, icu::DateFormatSymbols::FORMAT, icu::DateFormatSymbols::ABBREVIATED);
366 setStringArrayField(env, localeData, "shortMonthNames", shortMonthNames, count);
367 const icu::UnicodeString* tinyMonthNames =
368 dateFormatSym.getMonths(count, icu::DateFormatSymbols::FORMAT, icu::DateFormatSymbols::NARROW);
369 setStringArrayField(env, localeData, "tinyMonthNames", tinyMonthNames, count);
370 const icu::UnicodeString* longWeekdayNames =
371 dateFormatSym.getWeekdays(count, icu::DateFormatSymbols::FORMAT, icu::DateFormatSymbols::WIDE);
372 setStringArrayField(env, localeData, "longWeekdayNames", longWeekdayNames, count);
373 const icu::UnicodeString* shortWeekdayNames =
374 dateFormatSym.getWeekdays(count, icu::DateFormatSymbols::FORMAT, icu::DateFormatSymbols::ABBREVIATED);
375 setStringArrayField(env, localeData, "shortWeekdayNames", shortWeekdayNames, count);
376 const icu::UnicodeString* tinyWeekdayNames =
377 dateFormatSym.getWeekdays(count, icu::DateFormatSymbols::FORMAT, icu::DateFormatSymbols::NARROW);
378 setStringArrayField(env, localeData, "tinyWeekdayNames", tinyWeekdayNames, count);
379
380 const icu::UnicodeString* longStandAloneMonthNames =
381 dateFormatSym.getMonths(count, icu::DateFormatSymbols::STANDALONE, icu::DateFormatSymbols::WIDE);
382 setStringArrayField(env, localeData, "longStandAloneMonthNames", longStandAloneMonthNames, count);
383 const icu::UnicodeString* shortStandAloneMonthNames =
384 dateFormatSym.getMonths(count, icu::DateFormatSymbols::STANDALONE, icu::DateFormatSymbols::ABBREVIATED);
385 setStringArrayField(env, localeData, "shortStandAloneMonthNames", shortStandAloneMonthNames, count);
386 const icu::UnicodeString* tinyStandAloneMonthNames =
387 dateFormatSym.getMonths(count, icu::DateFormatSymbols::STANDALONE, icu::DateFormatSymbols::NARROW);
388 setStringArrayField(env, localeData, "tinyStandAloneMonthNames", tinyStandAloneMonthNames, count);
389 const icu::UnicodeString* longStandAloneWeekdayNames =
390 dateFormatSym.getWeekdays(count, icu::DateFormatSymbols::STANDALONE, icu::DateFormatSymbols::WIDE);
391 setStringArrayField(env, localeData, "longStandAloneWeekdayNames", longStandAloneWeekdayNames, count);
392 const icu::UnicodeString* shortStandAloneWeekdayNames =
393 dateFormatSym.getWeekdays(count, icu::DateFormatSymbols::STANDALONE, icu::DateFormatSymbols::ABBREVIATED);
394 setStringArrayField(env, localeData, "shortStandAloneWeekdayNames", shortStandAloneWeekdayNames, count);
395 const icu::UnicodeString* tinyStandAloneWeekdayNames =
396 dateFormatSym.getWeekdays(count, icu::DateFormatSymbols::STANDALONE, icu::DateFormatSymbols::NARROW);
397 setStringArrayField(env, localeData, "tinyStandAloneWeekdayNames", tinyStandAloneWeekdayNames, count);
398
399 status = U_ZERO_ERROR;
400
401 // For numberPatterns and symbols.
402 setNumberPatterns(env, localeData, icuLocale.locale());
403 setDecimalFormatSymbolsData(env, localeData, icuLocale.locale());
404
405 return JNI_TRUE;
406 }
407