From 33122e33f03f8af147cdba4cab49117b2d0589c2 Mon Sep 17 00:00:00 2001 From: Yufeng Wang Date: Fri, 27 Oct 2023 12:19:31 -0700 Subject: [PATCH] Update Java8 Optional handling in JniReferences --- src/lib/support/JniReferences.cpp | 108 ++++++++++++++++++++---------- src/lib/support/JniReferences.h | 4 ++ 2 files changed, 75 insertions(+), 37 deletions(-) diff --git a/src/lib/support/JniReferences.cpp b/src/lib/support/JniReferences.cpp index f07837ade467bd..b6919f8c076b4a 100644 --- a/src/lib/support/JniReferences.cpp +++ b/src/lib/support/JniReferences.cpp @@ -21,6 +21,7 @@ #include #include #include +#include namespace chip { @@ -35,7 +36,7 @@ void JniReferences::SetJavaVm(JavaVM * jvm, const char * clsType) JNIEnv * env = GetEnvForCurrentThread(); // Any chip.devicecontroller.* class will work here - just need something to call getClassLoader() on. jclass chipClass = env->FindClass(clsType); - VerifyOrReturn(chipClass != nullptr, ChipLogError(Support, "clsType can not found")); + VerifyOrReturn(chipClass != nullptr, ChipLogError(Support, "clsType can not be found")); jclass classClass = env->FindClass("java/lang/Class"); jclass classLoaderClass = env->FindClass("java/lang/ClassLoader"); @@ -47,6 +48,36 @@ void JniReferences::SetJavaVm(JavaVM * jvm, const char * clsType) chip::JniReferences::GetInstance().GetClassRef(env, "java/util/List", mListClass); chip::JniReferences::GetInstance().GetClassRef(env, "java/util/ArrayList", mArrayListClass); chip::JniReferences::GetInstance().GetClassRef(env, "java/util/HashMap", mHashMapClass); + + // Determine if the Java code has proper Java 8 support or not. + // The class and method chosen here are arbitrary, all we care about is + // looking up any method that has an Optional parameter. + jclass controllerParamsClass = env->FindClass("chip/devicecontroller/ControllerParams"); + VerifyOrReturn(controllerParamsClass != nullptr, ChipLogError(Support, "controllerParamsClass is nullptr")); + + jmethodID getCountryCodeMethod = env->GetMethodID(controllerParamsClass, "getCountryCode", "()Ljava/util/Optional;"); + if (getCountryCodeMethod == nullptr) + { + // GetMethodID will have thrown an exception previously if it returned nullptr. + env->ExceptionClear(); + VerifyOrReturn(env->GetMethodID(controllerParamsClass, "getCountryCode", "()Lj$/util/Optional;") != nullptr, + ChipLogError(Support, "Method getCountryCode can not be found")); + use_java8_optional = false; + } + else + { + use_java8_optional = true; + } + + if (use_java8_optional) + { + chip::JniReferences::GetInstance().GetClassRef(env, "java/util/Optional", mOptionalClass); + } + else + { + chip::JniReferences::GetInstance().GetClassRef(env, "j$/util/Optional", mOptionalClass); + } + VerifyOrReturn(mOptionalClass != nullptr, ChipLogError(Support, "mOptionalClass is nullptr")); } JNIEnv * JniReferences::GetEnvForCurrentThread() @@ -90,11 +121,11 @@ CHIP_ERROR JniReferences::GetLocalClassRef(JNIEnv * env, const char * clsType, j { jclass cls = nullptr; - // Try `j$/util/Optional` when enabling Java8. - if (strcmp(clsType, "java/util/Optional") == 0) + // Try `j$/util/Optional` when enabling Java8. Check whether mOptionalClass + // is null because this method is used to originally set mOptionalClass. + if (mOptionalClass != nullptr && (strcmp(clsType, "java/util/Optional") == 0 || strcmp(clsType, "j$/util/Optional") == 0)) { - cls = env->FindClass("j$/util/Optional"); - env->ExceptionClear(); + cls = mOptionalClass; } if (cls == nullptr) @@ -129,6 +160,18 @@ CHIP_ERROR JniReferences::N2J_ByteArray(JNIEnv * env, const uint8_t * inArray, j return err; } +static std::string StrReplaceAll(const std::string & source, const std::string & from, const std::string & to) +{ + std::string newString = source; + size_t pos = 0; + while ((pos = newString.find(from, pos)) != std::string::npos) + { + newString.replace(pos, from.length(), to); + pos += to.length(); + } + return newString; +} + CHIP_ERROR JniReferences::FindMethod(JNIEnv * env, jobject object, const char * methodName, const char * methodSignature, jmethodID * methodId) { @@ -146,21 +189,14 @@ CHIP_ERROR JniReferences::FindMethod(JNIEnv * env, jobject object, const char * return CHIP_NO_ERROR; } - // Try `j$` when enabling Java8. - std::string methodSignature_java8_str(methodSignature); - size_t pos = methodSignature_java8_str.find("java/util/Optional"); - if (pos != std::string::npos) + std::string method_signature = methodSignature; + if (!use_java8_optional) { - // Replace all "java/util/Optional" with "j$/util/Optional". - while (pos != std::string::npos) - { - methodSignature_java8_str.replace(pos, strlen("java/util/Optional"), "j$/util/Optional"); - pos = methodSignature_java8_str.find("java/util/Optional"); - } - *methodId = env->GetMethodID(javaClass, methodName, methodSignature_java8_str.c_str()); - env->ExceptionClear(); + method_signature = StrReplaceAll(method_signature, "java/util/Optional", "j$/util/Optional"); } + *methodId = env->GetMethodID(javaClass, methodName, method_signature.data()); + VerifyOrReturnError(*methodId != nullptr, CHIP_JNI_ERROR_METHOD_NOT_FOUND); return CHIP_NO_ERROR; @@ -226,24 +262,23 @@ void JniReferences::ThrowError(JNIEnv * env, jclass exceptionCls, CHIP_ERROR err CHIP_ERROR JniReferences::CreateOptional(jobject objectToWrap, jobject & outOptional) { - JNIEnv * env = GetEnvForCurrentThread(); - jclass optionalCls; - chip::JniReferences::GetInstance().GetClassRef(env, "java/util/Optional", optionalCls); - VerifyOrReturnError(optionalCls != nullptr, CHIP_JNI_ERROR_TYPE_NOT_FOUND); - chip::JniClass jniClass(optionalCls); + VerifyOrReturnError(mOptionalClass != nullptr, CHIP_JNI_ERROR_TYPE_NOT_FOUND); - jmethodID ofMethod = env->GetStaticMethodID(optionalCls, "ofNullable", "(Ljava/lang/Object;)Ljava/util/Optional;"); - env->ExceptionClear(); + JNIEnv * const env = GetEnvForCurrentThread(); + VerifyOrReturnError(env != nullptr, CHIP_JNI_ERROR_NO_ENV); - // Try `Lj$/util/Optional;` when enabling Java8. - if (ofMethod == nullptr) + jmethodID ofMethod = nullptr; + if (use_java8_optional) { - ofMethod = env->GetStaticMethodID(optionalCls, "ofNullable", "(Ljava/lang/Object;)Lj$/util/Optional;"); - env->ExceptionClear(); + ofMethod = env->GetStaticMethodID(mOptionalClass, "ofNullable", "(Ljava/lang/Object;)Ljava/util/Optional;"); + } + else + { + ofMethod = env->GetStaticMethodID(mOptionalClass, "ofNullable", "(Ljava/lang/Object;)Lj$/util/Optional;"); } - VerifyOrReturnError(ofMethod != nullptr, CHIP_JNI_ERROR_METHOD_NOT_FOUND); - outOptional = env->CallStaticObjectMethod(optionalCls, ofMethod, objectToWrap); + + outOptional = env->CallStaticObjectMethod(mOptionalClass, ofMethod, objectToWrap); VerifyOrReturnError(!env->ExceptionCheck(), CHIP_JNI_ERROR_EXCEPTION_THROWN); @@ -252,13 +287,12 @@ CHIP_ERROR JniReferences::CreateOptional(jobject objectToWrap, jobject & outOpti CHIP_ERROR JniReferences::GetOptionalValue(jobject optionalObj, jobject & optionalValue) { - JNIEnv * env = GetEnvForCurrentThread(); - jclass optionalCls; - chip::JniReferences::GetInstance().GetClassRef(env, "java/util/Optional", optionalCls); - VerifyOrReturnError(optionalCls != nullptr, CHIP_JNI_ERROR_TYPE_NOT_FOUND); - chip::JniClass jniClass(optionalCls); + VerifyOrReturnError(mOptionalClass != nullptr, CHIP_JNI_ERROR_TYPE_NOT_FOUND); + + JNIEnv * const env = GetEnvForCurrentThread(); + VerifyOrReturnError(env != nullptr, CHIP_JNI_ERROR_NO_ENV); - jmethodID isPresentMethod = env->GetMethodID(optionalCls, "isPresent", "()Z"); + jmethodID isPresentMethod = env->GetMethodID(mOptionalClass, "isPresent", "()Z"); VerifyOrReturnError(isPresentMethod != nullptr, CHIP_JNI_ERROR_METHOD_NOT_FOUND); jboolean isPresent = optionalObj && env->CallBooleanMethod(optionalObj, isPresentMethod); @@ -268,7 +302,7 @@ CHIP_ERROR JniReferences::GetOptionalValue(jobject optionalObj, jobject & option return CHIP_NO_ERROR; } - jmethodID getMethod = env->GetMethodID(optionalCls, "get", "()Ljava/lang/Object;"); + jmethodID getMethod = env->GetMethodID(mOptionalClass, "get", "()Ljava/lang/Object;"); VerifyOrReturnError(getMethod != nullptr, CHIP_JNI_ERROR_METHOD_NOT_FOUND); optionalValue = env->CallObjectMethod(optionalObj, getMethod); return CHIP_NO_ERROR; diff --git a/src/lib/support/JniReferences.h b/src/lib/support/JniReferences.h index 9a0fea52019040..4abef990822b0c 100644 --- a/src/lib/support/JniReferences.h +++ b/src/lib/support/JniReferences.h @@ -190,8 +190,12 @@ class JniReferences jobject mClassLoader = nullptr; jmethodID mFindClassMethod = nullptr; + // These are global refs and therefore safe to persist. jclass mHashMapClass = nullptr; jclass mListClass = nullptr; jclass mArrayListClass = nullptr; + jclass mOptionalClass = nullptr; + + bool use_java8_optional = false; }; } // namespace chip