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