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