1 /*
2 * Copyright (C) 2018 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 #include "utils/calendar/calendar-javaicu.h"
18
19 #include "annotator/types.h"
20 #include "utils/base/statusor.h"
21 #include "utils/java/jni-base.h"
22 #include "utils/java/jni-helper.h"
23
24 namespace libtextclassifier3 {
25 namespace {
26
27 // Generic version of icu::Calendar::add with error checking.
CalendarAdd(JniCache * jni_cache,JNIEnv * jenv,jobject calendar,jint field,jint value)28 bool CalendarAdd(JniCache* jni_cache, JNIEnv* jenv, jobject calendar,
29 jint field, jint value) {
30 return JniHelper::CallVoidMethod(jenv, calendar, jni_cache->calendar_add,
31 field, value)
32 .ok();
33 }
34
35 // Generic version of icu::Calendar::get with error checking.
CalendarGet(JniCache * jni_cache,JNIEnv * jenv,jobject calendar,jint field,jint * value)36 bool CalendarGet(JniCache* jni_cache, JNIEnv* jenv, jobject calendar,
37 jint field, jint* value) {
38 TC3_ASSIGN_OR_RETURN_FALSE(
39 *value,
40 JniHelper::CallIntMethod(jenv, calendar, jni_cache->calendar_get, field));
41 return true;
42 }
43
44 // Generic version of icu::Calendar::set with error checking.
CalendarSet(JniCache * jni_cache,JNIEnv * jenv,jobject calendar,jint field,jint value)45 bool CalendarSet(JniCache* jni_cache, JNIEnv* jenv, jobject calendar,
46 jint field, jint value) {
47 return JniHelper::CallVoidMethod(jenv, calendar, jni_cache->calendar_set,
48 field, value)
49 .ok();
50 }
51
52 // Extracts the first tag from a BCP47 tag (e.g. "en" for "en-US").
GetFirstBcp47Tag(const std::string & tag)53 std::string GetFirstBcp47Tag(const std::string& tag) {
54 for (size_t i = 0; i < tag.size(); ++i) {
55 if (tag[i] == '_' || tag[i] == '-') {
56 return std::string(tag, 0, i);
57 }
58 }
59 return tag;
60 }
61
62 } // anonymous namespace
63
Calendar(JniCache * jni_cache)64 Calendar::Calendar(JniCache* jni_cache)
65 : jni_cache_(jni_cache),
66 jenv_(jni_cache_ ? jni_cache->GetEnv() : nullptr),
67 calendar_(nullptr, jenv_) {}
68
Initialize(const std::string & time_zone,const std::string & locale,int64 time_ms_utc)69 bool Calendar::Initialize(const std::string& time_zone,
70 const std::string& locale, int64 time_ms_utc) {
71 if (!jni_cache_ || !jenv_) {
72 TC3_LOG(ERROR) << "Initialize without env";
73 return false;
74 }
75
76 // We'll assume the day indices match later on, so verify it here.
77 if (jni_cache_->calendar_sunday != kSunday ||
78 jni_cache_->calendar_monday != kMonday ||
79 jni_cache_->calendar_tuesday != kTuesday ||
80 jni_cache_->calendar_wednesday != kWednesday ||
81 jni_cache_->calendar_thursday != kThursday ||
82 jni_cache_->calendar_friday != kFriday ||
83 jni_cache_->calendar_saturday != kSaturday) {
84 TC3_LOG(ERROR) << "day of the week indices mismatch";
85 return false;
86 }
87
88 // Get the time zone.
89 TC3_ASSIGN_OR_RETURN_FALSE(ScopedLocalRef<jstring> java_time_zone_str,
90 JniHelper::NewStringUTF(jenv_, time_zone.c_str()));
91 TC3_ASSIGN_OR_RETURN_FALSE(
92 ScopedLocalRef<jobject> java_time_zone,
93 JniHelper::CallStaticObjectMethod(jenv_, jni_cache_->timezone_class.get(),
94 jni_cache_->timezone_get_timezone,
95 java_time_zone_str.get()));
96 if (java_time_zone == nullptr) {
97 TC3_LOG(ERROR) << "failed to get timezone";
98 return false;
99 }
100
101 // Get the locale.
102 ScopedLocalRef<jobject> java_locale(nullptr, jenv_);
103 if (jni_cache_->locale_for_language_tag) {
104 // API level 21+, we can actually parse language tags.
105 TC3_ASSIGN_OR_RETURN_FALSE(ScopedLocalRef<jstring> java_locale_str,
106 JniHelper::NewStringUTF(jenv_, locale.c_str()));
107
108 TC3_ASSIGN_OR_RETURN_FALSE(
109 java_locale,
110 JniHelper::CallStaticObjectMethod(jenv_, jni_cache_->locale_class.get(),
111 jni_cache_->locale_for_language_tag,
112 java_locale_str.get()));
113 } else {
114 // API level <21. We can't parse tags, so we just use the language.
115 TC3_ASSIGN_OR_RETURN_FALSE(
116 ScopedLocalRef<jstring> java_language_str,
117 JniHelper::NewStringUTF(jenv_, GetFirstBcp47Tag(locale).c_str()));
118
119 TC3_ASSIGN_OR_RETURN_FALSE(
120 java_locale, JniHelper::NewObject(jenv_, jni_cache_->locale_class.get(),
121 jni_cache_->locale_init_string,
122 java_language_str.get()));
123 }
124 if (java_locale == nullptr) {
125 TC3_LOG(ERROR) << "failed to get locale";
126 return false;
127 }
128
129 // Get the calendar.
130 TC3_ASSIGN_OR_RETURN_FALSE(
131 calendar_, JniHelper::CallStaticObjectMethod(
132 jenv_, jni_cache_->calendar_class.get(),
133 jni_cache_->calendar_get_instance, java_time_zone.get(),
134 java_locale.get()));
135 if (calendar_ == nullptr) {
136 TC3_LOG(ERROR) << "failed to get calendar";
137 return false;
138 }
139
140 // Set the time.
141 if (!JniHelper::CallVoidMethod(jenv_, calendar_.get(),
142 jni_cache_->calendar_set_time_in_millis,
143 time_ms_utc)
144 .ok()) {
145 TC3_LOG(ERROR) << "failed to set time";
146 return false;
147 }
148 return true;
149 }
150
GetFirstDayOfWeek(int * value) const151 bool Calendar::GetFirstDayOfWeek(int* value) const {
152 if (!jni_cache_ || !jenv_ || !calendar_) return false;
153
154 TC3_ASSIGN_OR_RETURN_FALSE(
155 *value,
156 JniHelper::CallIntMethod(jenv_, calendar_.get(),
157 jni_cache_->calendar_get_first_day_of_week));
158 return true;
159 }
160
GetTimeInMillis(int64 * value) const161 bool Calendar::GetTimeInMillis(int64* value) const {
162 if (!jni_cache_ || !jenv_ || !calendar_) return false;
163
164 TC3_ASSIGN_OR_RETURN_FALSE(
165 *value,
166 JniHelper::CallLongMethod(jenv_, calendar_.get(),
167 jni_cache_->calendar_get_time_in_millis));
168
169 return true;
170 }
171
CalendarLib()172 CalendarLib::CalendarLib() {
173 TC3_LOG(FATAL) << "Java ICU CalendarLib must be initialized with a JniCache.";
174 }
175
CalendarLib(const std::shared_ptr<JniCache> & jni_cache)176 CalendarLib::CalendarLib(const std::shared_ptr<JniCache>& jni_cache)
177 : jni_cache_(jni_cache) {}
178
179 // Below is the boilerplate code for implementing the specialisations of
180 // get/set/add for the various field types.
181 #define TC3_DEFINE_FIELD_ACCESSOR(NAME, FIELD, KIND, TYPE) \
182 bool Calendar::KIND##NAME(TYPE value) const { \
183 if (!jni_cache_ || !jenv_ || !calendar_) return false; \
184 return Calendar##KIND(jni_cache_, jenv_, calendar_.get(), \
185 jni_cache_->calendar_##FIELD, value); \
186 }
187 #define TC3_DEFINE_ADD(NAME, CONST) \
188 TC3_DEFINE_FIELD_ACCESSOR(NAME, CONST, Add, int)
189 #define TC3_DEFINE_SET(NAME, CONST) \
190 TC3_DEFINE_FIELD_ACCESSOR(NAME, CONST, Set, int)
191 #define TC3_DEFINE_GET(NAME, CONST) \
192 TC3_DEFINE_FIELD_ACCESSOR(NAME, CONST, Get, int*)
193
194 TC3_DEFINE_ADD(Second, second)
195 TC3_DEFINE_ADD(Minute, minute)
196 TC3_DEFINE_ADD(HourOfDay, hour_of_day)
197 TC3_DEFINE_ADD(DayOfMonth, day_of_month)
198 TC3_DEFINE_ADD(Year, year)
199 TC3_DEFINE_ADD(Month, month)
200 TC3_DEFINE_GET(DayOfWeek, day_of_week)
201 TC3_DEFINE_SET(ZoneOffset, zone_offset)
202 TC3_DEFINE_SET(DstOffset, dst_offset)
203 TC3_DEFINE_SET(Year, year)
204 TC3_DEFINE_SET(Month, month)
205 TC3_DEFINE_SET(DayOfYear, day_of_year)
206 TC3_DEFINE_SET(DayOfMonth, day_of_month)
207 TC3_DEFINE_SET(DayOfWeek, day_of_week)
208 TC3_DEFINE_SET(HourOfDay, hour_of_day)
209 TC3_DEFINE_SET(Minute, minute)
210 TC3_DEFINE_SET(Second, second)
211 TC3_DEFINE_SET(Millisecond, millisecond)
212
213 #undef TC3_DEFINE_FIELD_ACCESSOR
214 #undef TC3_DEFINE_ADD
215 #undef TC3_DEFINE_SET
216 #undef TC3_DEFINE_GET
217
218 } // namespace libtextclassifier3
219