/* * Copyright (C) 2018 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include "utils/java/jni-cache.h" #include "utils/base/logging.h" #include "utils/base/status_macros.h" #include "utils/java/jni-base.h" #include "utils/java/jni-helper.h" namespace libtextclassifier3 { JniCache::JniCache(JavaVM* jvm) : jvm(jvm), string_class(nullptr, jvm), string_utf8(nullptr, jvm), pattern_class(nullptr, jvm), matcher_class(nullptr, jvm), locale_class(nullptr, jvm), locale_us(nullptr, jvm), breakiterator_class(nullptr, jvm), integer_class(nullptr, jvm), calendar_class(nullptr, jvm), timezone_class(nullptr, jvm) #ifdef __ANDROID__ , context_class(nullptr, jvm), uri_class(nullptr, jvm), usermanager_class(nullptr, jvm), bundle_class(nullptr, jvm), resources_class(nullptr, jvm) #endif { } // The macros below are intended to reduce the boilerplate in Create and avoid // easily introduced copy/paste errors. #define TC3_CHECK_JNI_PTR(PTR) TC3_CHECK((PTR) != nullptr) #define TC3_CHECK_JNI_RESULT(RESULT) TC3_CHECK(RESULT) #define TC3_GET_CLASS_OR_RETURN_NULL(FIELD, NAME) \ { \ TC3_ASSIGN_OR_RETURN_NULL(ScopedLocalRef clazz, \ JniHelper::FindClass(env, NAME)); \ result->FIELD##_class = MakeGlobalRef(clazz.get(), env, jvm); \ if (result->FIELD##_class == nullptr) { \ TC3_LOG(ERROR) << "Error finding class: " << NAME; \ return nullptr; \ } \ } #define TC3_GET_OPTIONAL_CLASS(FIELD, NAME) \ { \ StatusOr> status_or_class = \ JniHelper::FindClass(env, NAME); \ if (status_or_class.ok()) { \ result->FIELD##_class = MakeGlobalRef( \ std::move(status_or_class).ValueOrDie().get(), env, jvm); \ } \ } #define TC3_GET_METHOD(CLASS, FIELD, NAME, SIGNATURE) \ TC3_ASSIGN_OR_RETURN_NULL( \ result->CLASS##_##FIELD, \ JniHelper::GetMethodID(env, result->CLASS##_class.get(), NAME, \ SIGNATURE)); #define TC3_GET_OPTIONAL_METHOD(CLASS, FIELD, NAME, SIGNATURE) \ TC3_GET_OPTIONAL_METHOD_INTERNAL(CLASS, FIELD, NAME, SIGNATURE, GetMethodID) #define TC3_GET_OPTIONAL_STATIC_METHOD(CLASS, FIELD, NAME, SIGNATURE) \ TC3_GET_OPTIONAL_METHOD_INTERNAL(CLASS, FIELD, NAME, SIGNATURE, \ GetStaticMethodID) #define TC3_GET_OPTIONAL_METHOD_INTERNAL(CLASS, FIELD, NAME, SIGNATURE, \ METHOD_NAME) \ if (result->CLASS##_class != nullptr) { \ if (StatusOr status_or_method_id = JniHelper::METHOD_NAME( \ env, result->CLASS##_class.get(), NAME, SIGNATURE); \ status_or_method_id.ok()) { \ result->CLASS##_##FIELD = status_or_method_id.ValueOrDie(); \ } \ } #define TC3_GET_STATIC_METHOD(CLASS, FIELD, NAME, SIGNATURE) \ TC3_ASSIGN_OR_RETURN_NULL( \ result->CLASS##_##FIELD, \ JniHelper::GetStaticMethodID(env, result->CLASS##_class.get(), NAME, \ SIGNATURE)); #define TC3_GET_STATIC_OBJECT_FIELD_OR_RETURN_NULL(CLASS, FIELD, NAME, \ SIGNATURE) \ { \ TC3_ASSIGN_OR_RETURN_NULL( \ const jfieldID CLASS##_##FIELD##_field, \ JniHelper::GetStaticFieldID(env, result->CLASS##_class.get(), NAME, \ SIGNATURE)); \ TC3_ASSIGN_OR_RETURN_NULL( \ ScopedLocalRef static_object, \ JniHelper::GetStaticObjectField(env, result->CLASS##_class.get(), \ CLASS##_##FIELD##_field)); \ result->CLASS##_##FIELD = MakeGlobalRef(static_object.get(), env, jvm); \ if (result->CLASS##_##FIELD == nullptr) { \ TC3_LOG(ERROR) << "Error finding field: " << NAME; \ return nullptr; \ } \ } #define TC3_GET_STATIC_INT_FIELD(CLASS, FIELD, NAME) \ TC3_ASSIGN_OR_RETURN_NULL(const jfieldID CLASS##_##FIELD##_field, \ JniHelper::GetStaticFieldID( \ env, result->CLASS##_class.get(), NAME, "I")); \ TC3_ASSIGN_OR_RETURN_NULL( \ result->CLASS##_##FIELD, \ JniHelper::GetStaticIntField(env, result->CLASS##_class.get(), \ CLASS##_##FIELD##_field)); std::unique_ptr JniCache::Create(JNIEnv* env) { if (env == nullptr) { return nullptr; } JavaVM* jvm = nullptr; if (JNI_OK != env->GetJavaVM(&jvm) || jvm == nullptr) { return nullptr; } std::unique_ptr result(new JniCache(jvm)); // String TC3_GET_CLASS_OR_RETURN_NULL(string, "java/lang/String"); TC3_GET_METHOD(string, init_bytes_charset, "", "([BLjava/lang/String;)V"); TC3_GET_METHOD(string, code_point_count, "codePointCount", "(II)I"); TC3_GET_METHOD(string, length, "length", "()I"); TC3_ASSIGN_OR_RETURN_NULL(ScopedLocalRef result_string, JniHelper::NewStringUTF(env, "UTF-8")); result->string_utf8 = MakeGlobalRef(result_string.get(), env, jvm); TC3_CHECK_JNI_PTR(result->string_utf8); // Pattern TC3_GET_CLASS_OR_RETURN_NULL(pattern, "java/util/regex/Pattern"); TC3_GET_STATIC_METHOD(pattern, compile, "compile", "(Ljava/lang/String;)Ljava/util/regex/Pattern;"); TC3_GET_METHOD(pattern, matcher, "matcher", "(Ljava/lang/CharSequence;)Ljava/util/regex/Matcher;"); // Matcher TC3_GET_CLASS_OR_RETURN_NULL(matcher, "java/util/regex/Matcher"); TC3_GET_METHOD(matcher, matches, "matches", "()Z"); TC3_GET_METHOD(matcher, find, "find", "()Z"); TC3_GET_METHOD(matcher, reset, "reset", "()Ljava/util/regex/Matcher;"); TC3_GET_METHOD(matcher, start_idx, "start", "(I)I"); TC3_GET_METHOD(matcher, end_idx, "end", "(I)I"); TC3_GET_METHOD(matcher, group, "group", "()Ljava/lang/String;"); TC3_GET_METHOD(matcher, group_idx, "group", "(I)Ljava/lang/String;"); // Locale TC3_GET_CLASS_OR_RETURN_NULL(locale, "java/util/Locale"); TC3_GET_STATIC_OBJECT_FIELD_OR_RETURN_NULL(locale, us, "US", "Ljava/util/Locale;"); TC3_GET_METHOD(locale, init_string, "", "(Ljava/lang/String;)V"); TC3_GET_OPTIONAL_STATIC_METHOD(locale, for_language_tag, "forLanguageTag", "(Ljava/lang/String;)Ljava/util/Locale;"); // BreakIterator TC3_GET_CLASS_OR_RETURN_NULL(breakiterator, "java/text/BreakIterator"); TC3_GET_STATIC_METHOD(breakiterator, getwordinstance, "getWordInstance", "(Ljava/util/Locale;)Ljava/text/BreakIterator;"); TC3_GET_METHOD(breakiterator, settext, "setText", "(Ljava/lang/String;)V"); TC3_GET_METHOD(breakiterator, next, "next", "()I"); // Integer TC3_GET_CLASS_OR_RETURN_NULL(integer, "java/lang/Integer"); TC3_GET_STATIC_METHOD(integer, parse_int, "parseInt", "(Ljava/lang/String;)I"); // Calendar. TC3_GET_CLASS_OR_RETURN_NULL(calendar, "java/util/Calendar"); TC3_GET_STATIC_METHOD( calendar, get_instance, "getInstance", "(Ljava/util/TimeZone;Ljava/util/Locale;)Ljava/util/Calendar;"); TC3_GET_METHOD(calendar, get_first_day_of_week, "getFirstDayOfWeek", "()I"); TC3_GET_METHOD(calendar, get_time_in_millis, "getTimeInMillis", "()J"); TC3_GET_METHOD(calendar, set_time_in_millis, "setTimeInMillis", "(J)V"); TC3_GET_METHOD(calendar, add, "add", "(II)V"); TC3_GET_METHOD(calendar, get, "get", "(I)I"); TC3_GET_METHOD(calendar, set, "set", "(II)V"); TC3_GET_STATIC_INT_FIELD(calendar, zone_offset, "ZONE_OFFSET"); TC3_GET_STATIC_INT_FIELD(calendar, dst_offset, "DST_OFFSET"); TC3_GET_STATIC_INT_FIELD(calendar, year, "YEAR"); TC3_GET_STATIC_INT_FIELD(calendar, month, "MONTH"); TC3_GET_STATIC_INT_FIELD(calendar, day_of_year, "DAY_OF_YEAR"); TC3_GET_STATIC_INT_FIELD(calendar, day_of_month, "DAY_OF_MONTH"); TC3_GET_STATIC_INT_FIELD(calendar, day_of_week, "DAY_OF_WEEK"); TC3_GET_STATIC_INT_FIELD(calendar, hour_of_day, "HOUR_OF_DAY"); TC3_GET_STATIC_INT_FIELD(calendar, minute, "MINUTE"); TC3_GET_STATIC_INT_FIELD(calendar, second, "SECOND"); TC3_GET_STATIC_INT_FIELD(calendar, millisecond, "MILLISECOND"); TC3_GET_STATIC_INT_FIELD(calendar, sunday, "SUNDAY"); TC3_GET_STATIC_INT_FIELD(calendar, monday, "MONDAY"); TC3_GET_STATIC_INT_FIELD(calendar, tuesday, "TUESDAY"); TC3_GET_STATIC_INT_FIELD(calendar, wednesday, "WEDNESDAY"); TC3_GET_STATIC_INT_FIELD(calendar, thursday, "THURSDAY"); TC3_GET_STATIC_INT_FIELD(calendar, friday, "FRIDAY"); TC3_GET_STATIC_INT_FIELD(calendar, saturday, "SATURDAY"); // TimeZone. TC3_GET_CLASS_OR_RETURN_NULL(timezone, "java/util/TimeZone"); TC3_GET_STATIC_METHOD(timezone, get_timezone, "getTimeZone", "(Ljava/lang/String;)Ljava/util/TimeZone;"); #ifdef __ANDROID__ // Context. TC3_GET_CLASS_OR_RETURN_NULL(context, "android/content/Context"); TC3_GET_METHOD(context, get_package_name, "getPackageName", "()Ljava/lang/String;"); TC3_GET_METHOD(context, get_system_service, "getSystemService", "(Ljava/lang/String;)Ljava/lang/Object;"); // Uri. TC3_GET_CLASS_OR_RETURN_NULL(uri, "android/net/Uri"); TC3_GET_STATIC_METHOD(uri, parse, "parse", "(Ljava/lang/String;)Landroid/net/Uri;"); TC3_GET_METHOD(uri, get_scheme, "getScheme", "()Ljava/lang/String;"); TC3_GET_METHOD(uri, get_host, "getHost", "()Ljava/lang/String;"); TC3_GET_STATIC_METHOD(uri, encode, "encode", "(Ljava/lang/String;)Ljava/lang/String;"); // UserManager. TC3_GET_OPTIONAL_CLASS(usermanager, "android/os/UserManager"); TC3_GET_OPTIONAL_METHOD(usermanager, get_user_restrictions, "getUserRestrictions", "()Landroid/os/Bundle;"); // Bundle. TC3_GET_CLASS_OR_RETURN_NULL(bundle, "android/os/Bundle"); TC3_GET_METHOD(bundle, get_boolean, "getBoolean", "(Ljava/lang/String;)Z"); // String resources. TC3_GET_CLASS_OR_RETURN_NULL(resources, "android/content/res/Resources"); TC3_GET_STATIC_METHOD(resources, get_system, "getSystem", "()Landroid/content/res/Resources;"); TC3_GET_METHOD(resources, get_identifier, "getIdentifier", "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)I"); TC3_GET_METHOD(resources, get_string, "getString", "(I)Ljava/lang/String;"); #endif return result; } #undef TC3_GET_STATIC_INT_FIELD #undef TC3_GET_STATIC_OBJECT_FIELD_OR_RETURN_NULL #undef TC3_GET_STATIC_METHOD #undef TC3_GET_METHOD #undef TC3_GET_CLASS_OR_RETURN_NULL #undef TC3_GET_OPTIONAL_CLASS #undef TC3_CHECK_JNI_PTR JNIEnv* JniCache::GetEnv() const { void* env; if (JNI_OK == jvm->GetEnv(&env, JNI_VERSION_1_4)) { return reinterpret_cast(env); } else { TC3_LOG(ERROR) << "JavaICU UniLib used on unattached thread"; return nullptr; } } bool JniCache::ExceptionCheckAndClear() const { return JniExceptionCheckAndClear(GetEnv()); } StatusOr> JniCache::ConvertToJavaString( const char* utf8_text, const int utf8_text_size_bytes) const { // Create java byte array. JNIEnv* jenv = GetEnv(); TC3_ASSIGN_OR_RETURN(ScopedLocalRef text_java_utf8, JniHelper::NewByteArray(jenv, utf8_text_size_bytes)); TC3_RETURN_IF_ERROR(JniHelper::SetByteArrayRegion( jenv, text_java_utf8.get(), 0, utf8_text_size_bytes, reinterpret_cast(utf8_text))); // Create the string with a UTF-8 charset. TC3_ASSIGN_OR_RETURN(ScopedLocalRef result, JniHelper::NewObject( jenv, string_class.get(), string_init_bytes_charset, text_java_utf8.get(), string_utf8.get())); return result; } StatusOr> JniCache::ConvertToJavaString( StringPiece utf8_text) const { return ConvertToJavaString(utf8_text.data(), utf8_text.size()); } StatusOr> JniCache::ConvertToJavaString( const UnicodeText& text) const { return ConvertToJavaString(text.data(), text.size_bytes()); } } // namespace libtextclassifier3