Skip to content

Commit

Permalink
Update Java8 Optional handling in JniReferences (#30071)
Browse files Browse the repository at this point in the history
  • Loading branch information
yufengwangca authored and pull[bot] committed Oct 31, 2023
1 parent 50467d8 commit 1576030
Show file tree
Hide file tree
Showing 2 changed files with 75 additions and 37 deletions.
108 changes: 71 additions & 37 deletions src/lib/support/JniReferences.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
#include <lib/support/CodeUtils.h>
#include <lib/support/JniReferences.h>
#include <lib/support/JniTypeWrappers.h>
#include <string>

namespace chip {

Expand All @@ -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");
Expand All @@ -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()
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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)
{
Expand All @@ -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;
Expand Down Expand Up @@ -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);

Expand All @@ -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);

Expand All @@ -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;
Expand Down
4 changes: 4 additions & 0 deletions src/lib/support/JniReferences.h
Original file line number Diff line number Diff line change
Expand Up @@ -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

0 comments on commit 1576030

Please sign in to comment.