// Copyright (C) 2019 Google LLC // // 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 "icing/jni/jni-cache.h" #ifdef ICING_REVERSE_JNI_SEGMENTATION #include "icing/text_classifier/lib3/utils/java/jni-base.h" #include "icing/text_classifier/lib3/utils/java/jni-helper.h" #include "icing/absl_ports/canonical_errors.h" #include "icing/util/logging.h" #include "icing/util/status-macros.h" namespace icing { namespace lib { JniCache::JniCache(JavaVM* jvm) : jvm(jvm), string_class(nullptr, jvm), string_utf8(nullptr, jvm), locale_class(nullptr, jvm), locale_us(nullptr, jvm), breakiterator_class(nullptr, jvm) {} // The macros below are intended to reduce the boilerplate in Create and avoid // easily introduced copy/paste errors. #define ICING_GET_CLASS_OR_RETURN_NULL(FIELD, NAME) \ { \ ICING_ASSIGN_OR_RETURN( \ libtextclassifier3::ScopedLocalRef clazz, \ libtextclassifier3::JniHelper::FindClass(env, NAME), nullptr); \ result->FIELD##_class = \ libtextclassifier3::MakeGlobalRef(clazz.get(), env, jvm); \ if (result->FIELD##_class == nullptr) { \ ICING_LOG(ERROR) << "Error finding class: " << NAME; \ return nullptr; \ } \ } #define ICING_GET_OPTIONAL_CLASS(FIELD, NAME) \ { \ libtextclassifier3::StatusOr> \ status_or_class = libtextclassifier3::JniHelper::FindClass(env, NAME); \ if (status_or_class.ok()) { \ result->FIELD##_class = libtextclassifier3::MakeGlobalRef( \ std::move(status_or_class).ValueOrDie().get(), env, jvm); \ } \ } #define ICING_GET_METHOD(CLASS, FIELD, NAME, SIGNATURE) \ result->CLASS##_##FIELD = \ env->GetMethodID(result->CLASS##_class.get(), NAME, SIGNATURE); \ if (!result->CLASS##_##FIELD) { \ ICING_LOG(WARNING) << __FILE__ << ":" << __LINE__ \ << "Error finding method: " << NAME; \ return absl_ports::AbortedError("Unable to get Java method."); \ } #define ICING_GET_OPTIONAL_STATIC_METHOD(CLASS, FIELD, NAME, SIGNATURE) \ if (result->CLASS##_class != nullptr) { \ result->CLASS##_##FIELD = \ env->GetStaticMethodID(result->CLASS##_class.get(), NAME, SIGNATURE); \ env->ExceptionClear(); \ } #define ICING_GET_STATIC_METHOD(CLASS, FIELD, NAME, SIGNATURE) \ result->CLASS##_##FIELD = \ env->GetStaticMethodID(result->CLASS##_class.get(), NAME, SIGNATURE); \ if (!result->CLASS##_##FIELD) { \ ICING_LOG(WARNING) << __FILE__ << ":" << __LINE__ \ << "Error finding method: " << NAME; \ return absl_ports::AbortedError("Unable to get Java static method."); \ } #define ICING_GET_STATIC_OBJECT_FIELD_OR_RETURN_NULL(CLASS, FIELD, NAME, \ SIGNATURE) \ { \ const jfieldID CLASS##_##FIELD##_field = \ env->GetStaticFieldID(result->CLASS##_class.get(), NAME, SIGNATURE); \ if (!CLASS##_##FIELD##_field) { \ ICING_LOG(WARNING) << __FILE__ << ":" << __LINE__ \ << "Error finding field id: " << NAME; \ return absl_ports::AbortedError("Unable to get Java field id."); \ } \ ICING_ASSIGN_OR_RETURN( \ libtextclassifier3::ScopedLocalRef static_object, \ libtextclassifier3::JniHelper::GetStaticObjectField( \ env, result->CLASS##_class.get(), CLASS##_##FIELD##_field), \ nullptr); \ result->CLASS##_##FIELD = \ libtextclassifier3::MakeGlobalRef(static_object.get(), env, jvm); \ if (result->CLASS##_##FIELD == nullptr) { \ ICING_LOG(ERROR) << "Error finding field: " << NAME; \ return nullptr; \ } \ } #define ICING_GET_STATIC_INT_FIELD(CLASS, FIELD, NAME) \ const jfieldID CLASS##_##FIELD##_field = \ env->GetStaticFieldID(result->CLASS##_class.get(), NAME, "I"); \ << "Error finding field id: " << NAME; \ if (!CLASS##_##FIELD##_field) { \ ICING_LOG(WARNING) << __FILE__ << ":" << __LINE__ \ << "Error finding field id: " << NAME; \ return absl_ports::AbortedError( \ "Unable to get Java static int field id."); \ } \ result->CLASS##_##FIELD = env->GetStaticIntField( \ result->CLASS##_class.get(), CLASS##_##FIELD##_field); \ if (!result->CLASS##_##FIELD) { \ ICING_LOG(WARNING) << __FILE__ << ":" << __LINE__ \ << "Error finding field: " << NAME; \ return absl_ports::AbortedError("Unable to get Java static int field."); \ } libtextclassifier3::StatusOr> 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 ICING_GET_CLASS_OR_RETURN_NULL(string, "java/lang/String"); ICING_GET_METHOD(string, constructor, "", "([BLjava/lang/String;)V"); ICING_GET_METHOD(string, code_point_count, "codePointCount", "(II)I"); ICING_GET_METHOD(string, length, "length", "()I"); ICING_ASSIGN_OR_RETURN( libtextclassifier3::ScopedLocalRef result_string, libtextclassifier3::JniHelper::NewStringUTF(env, "UTF-8"), nullptr); result->string_utf8 = libtextclassifier3::MakeGlobalRef(result_string.get(), env, jvm); if (result->string_utf8 == nullptr) { return nullptr; } // Locale ICING_GET_CLASS_OR_RETURN_NULL(locale, "java/util/Locale"); ICING_GET_STATIC_OBJECT_FIELD_OR_RETURN_NULL(locale, us, "US", "Ljava/util/Locale;"); ICING_GET_METHOD(locale, constructor, "", "(Ljava/lang/String;)V"); ICING_GET_OPTIONAL_STATIC_METHOD(locale, for_language_tag, "forLanguageTag", "(Ljava/lang/String;)Ljava/util/Locale;"); // BreakIteratorBatcher ICING_GET_CLASS_OR_RETURN_NULL( breakiterator, "com/google/android/icing/BreakIteratorBatcher"); ICING_GET_METHOD(breakiterator, constructor, "", "(Ljava/util/Locale;)V"); ICING_GET_METHOD(breakiterator, settext, "setText", "(Ljava/lang/String;)V"); ICING_GET_METHOD(breakiterator, next, "next", "(I)[I"); ICING_GET_METHOD(breakiterator, first, "first", "()I"); ICING_GET_METHOD(breakiterator, following, "following", "(I)I"); ICING_GET_METHOD(breakiterator, preceding, "preceding", "(I)I"); return result; } #undef ICING_GET_STATIC_INT_FIELD #undef ICING_GET_STATIC_OBJECT_FIELD_OR_RETURN_NULL #undef ICING_GET_STATIC_METHOD #undef ICING_GET_METHOD #undef ICING_GET_CLASS_OR_RETURN_NULL #undef ICING_GET_OPTIONAL_CLASS JNIEnv* JniCache::GetEnv() const { void* env; if (JNI_OK == jvm->GetEnv(&env, JNI_VERSION_1_4)) { return reinterpret_cast(env); } else { ICING_LOG(ERROR) << "Icing JniCache used on unattached thread"; return nullptr; } } bool JniCache::ExceptionCheckAndClear() const { return libtextclassifier3::JniExceptionCheckAndClear(GetEnv()); } libtextclassifier3::StatusOr> JniCache::ConvertToJavaString(const char* utf8_text, const int utf8_text_size_bytes) const { // Create java byte array. JNIEnv* jenv = GetEnv(); ICING_ASSIGN_OR_RETURN( libtextclassifier3::ScopedLocalRef text_java_utf8, libtextclassifier3::JniHelper::NewByteArray(jenv, utf8_text_size_bytes)); jenv->SetByteArrayRegion(text_java_utf8.get(), 0, utf8_text_size_bytes, reinterpret_cast(utf8_text)); // Create the string with a UTF-8 charset. ICING_ASSIGN_OR_RETURN(libtextclassifier3::ScopedLocalRef result, libtextclassifier3::JniHelper::NewObject( jenv, string_class.get(), string_constructor, text_java_utf8.get(), string_utf8.get())); return result; } } // namespace lib } // namespace icing #endif // ICING_REVERSE_JNI_SEGMENTATION