Browse Source

Add JNI method loading functionality to JniClass

Patrick-Christopher Mattulat 1 year ago
parent
commit
bb25478797

+ 1 - 0
include/ls-std/core/interface/IJniApi.hpp

@@ -24,6 +24,7 @@ namespace ls::std::core::interface_type
       virtual ~IJniApi();
 
       virtual jclass findClass(const ::std::string &_classPath) = 0;
+      virtual jmethodID getMethodId(jclass _javaClass, const char *_methodIdentifier, const char *_methodSignature) = 0;
   };
 }
 

+ 1 - 0
include/ls-std/core/jni/JniApi.hpp

@@ -23,6 +23,7 @@ namespace ls::std::core
       ~JniApi() noexcept override;
 
       jclass findClass(const ::std::string &_classPath) override;
+      jmethodID getMethodId(jclass _javaClass, const char *_methodIdentifier, const char *_methodSignature) override;
 
     private:
 

+ 6 - 1
include/ls-std/core/jni/JniClass.hpp

@@ -16,6 +16,7 @@
 #include <ls-std/os/dynamic-goal.hpp>
 #include <memory>
 #include <string>
+#include <unordered_map>
 
 namespace ls::std::core
 {
@@ -26,15 +27,19 @@ namespace ls::std::core
       explicit JniClass(const ::std::shared_ptr<ls::std::core::JniClassParameter> &_parameter, const ::std::string &_path);
       virtual ~JniClass();
 
-      bool load(); // nodiscard is optional here
+      [[nodiscard]] bool hasMethod(const ::std::string &_methodIdentifier);
+      bool load();                                                                                    // nodiscard is optional here
+      bool loadMethod(const ::std::string &_methodIdentifier, const ::std::string &_methodSignature); // nodiscard is optional here
 
     private:
 
       jclass javaClass{};
+      ::std::unordered_map<::std::string, jmethodID> methods{};
       ::std::shared_ptr<ls::std::core::JniClassParameter> parameter{};
       ::std::string path{};
 
       void _createJniApi();
+      [[nodiscard]] bool _hasMethod(const ::std::string &_methodIdentifier);
   };
 }
 

+ 5 - 0
source/ls-std/core/jni/JniApi.cpp

@@ -26,3 +26,8 @@ jclass JniApi::findClass(const string &_classPath)
 {
   return this->environment->FindClass(_classPath.c_str());
 }
+
+jmethodID JniApi::getMethodId(jclass _javaClass, const char *_methodIdentifier, const char *_methodSignature)
+{
+  return this->environment->GetMethodID(_javaClass, _methodIdentifier, _methodSignature);
+}

+ 28 - 0
source/ls-std/core/jni/JniClass.cpp

@@ -10,6 +10,7 @@
 #include <ls-std/core/ConditionalFunctionExecutor.hpp>
 #include <ls-std/core/evaluator/EmptyStringArgumentEvaluator.hpp>
 #include <ls-std/core/evaluator/NullPointerArgumentEvaluator.hpp>
+#include <ls-std/core/evaluator/NullPointerEvaluator.hpp>
 #include <ls-std/core/jni/JniApi.hpp>
 #include <ls-std/core/jni/JniClass.hpp>
 #include <memory>
@@ -20,7 +21,10 @@ using ls::std::core::JniApi;
 using ls::std::core::JniClass;
 using ls::std::core::JniClassParameter;
 using ls::std::core::NullPointerArgumentEvaluator;
+using ls::std::core::NullPointerEvaluator;
+using std::make_pair;
 using std::make_shared;
+using std::pair;
 using std::shared_ptr;
 using std::string;
 
@@ -36,13 +40,37 @@ JniClass::JniClass(const shared_ptr<JniClassParameter> &_parameter, const string
 
 JniClass::~JniClass() = default;
 
+bool JniClass::hasMethod(const string &_methodIdentifier)
+{
+  return this->_hasMethod(_methodIdentifier);
+}
+
 bool JniClass::load()
 {
   this->javaClass = this->parameter->getJniApi()->findClass(this->path);
   return this->javaClass != nullptr;
 }
 
+bool JniClass::loadMethod(const string &_methodIdentifier, const string &_methodSignature)
+{
+  NullPointerEvaluator{this->javaClass}.evaluate();
+  jmethodID methodId = this->parameter->getJniApi()->getMethodId(this->javaClass, _methodIdentifier.c_str(), _methodSignature.c_str());
+  bool succeeded = methodId != nullptr && !this->_hasMethod(_methodIdentifier);
+
+  if (succeeded)
+  {
+    succeeded = this->methods.insert(make_pair<string, jmethodID>(string{_methodIdentifier}, jmethodID{methodId})).second;
+  }
+
+  return succeeded;
+}
+
 void JniClass::_createJniApi()
 {
   this->parameter->setJniApi(make_shared<JniApi>(this->parameter->getJavaEnvironment()));
 }
+
+bool JniClass::_hasMethod(const string &_methodIdentifier)
+{
+  return this->methods.find(_methodIdentifier) != this->methods.end();
+}

+ 82 - 8
test/cases/core/jni/JniClassTest.cpp

@@ -17,6 +17,7 @@
 using ls::std::core::IllegalArgumentException;
 using ls::std::core::JniClass;
 using ls::std::core::JniClassParameter;
+using ls::std::core::NullPointerException;
 using std::make_shared;
 using std::shared_ptr;
 using std::string;
@@ -33,6 +34,19 @@ namespace
 
       JniClassTest() = default;
       ~JniClassTest() override = default;
+
+      JniClass createJniClass(const string &_classPath)
+      {
+        shared_ptr<JniClassParameter> parameter = make_shared<JniClassParameter>();
+        this->jniApi = make_shared<MockJniApi>();
+        parameter->setJniApi(this->jniApi);
+        shared_ptr<JNIEnv> environment = make_shared<JNIEnv>();
+        parameter->setJavaEnvironment(environment.get());
+
+        return JniClass{parameter, _classPath};
+      }
+
+      shared_ptr<MockJniApi> jniApi{};
   };
 
   TEST_F(JniClassTest, constructor_no_parameter_reference)
@@ -83,19 +97,79 @@ namespace
         IllegalArgumentException);
   }
 
+  TEST_F(JniClassTest, hasMethod)
+  {
+    string classPath = "java.utils.String";
+    JniClass javaClass = this->createJniClass(classPath);
+
+    ASSERT_FALSE(javaClass.hasMethod("getDay"));
+  }
+
   TEST_F(JniClassTest, load)
   {
-    shared_ptr<JniClassParameter> parameter = make_shared<JniClassParameter>();
-    shared_ptr<MockJniApi> mockJniApi = make_shared<MockJniApi>();
-    parameter->setJniApi(mockJniApi);
-    shared_ptr<JNIEnv> environment = make_shared<JNIEnv>();
-    parameter->setJavaEnvironment(environment.get());
     string classPath = "java.utils.String";
-    JniClass javaClass{parameter, classPath};
+    JniClass javaClass = this->createJniClass(classPath);
 
-    EXPECT_CALL(*mockJniApi, findClass(classPath)).Times(AtLeast(1));
-    ON_CALL(*mockJniApi, findClass(classPath)).WillByDefault(Return(make_shared<_jclass>().get()));
+    EXPECT_CALL(*this->jniApi, findClass(classPath)).Times(AtLeast(1));
+    ON_CALL(*this->jniApi, findClass(classPath)).WillByDefault(Return(make_shared<_jclass>().get()));
 
     ASSERT_TRUE(javaClass.load());
   }
+
+  TEST_F(JniClassTest, loadMethod)
+  {
+    string classPath = "java.utils.String";
+    JniClass javaClass = this->createJniClass(classPath);
+
+    EXPECT_CALL(*this->jniApi, findClass(classPath)).Times(AtLeast(1));
+    ON_CALL(*this->jniApi, findClass(classPath)).WillByDefault(Return(make_shared<_jclass>().get()));
+    string methodIdentifier = "getDay";
+    string methodSignature = "()B";
+    EXPECT_CALL(*this->jniApi, getMethodId(testing::_, methodIdentifier.c_str(), methodSignature.c_str())).Times(AtLeast(1));
+    jmethodID methodId = (jmethodID) make_shared<int>().get();
+    ON_CALL(*this->jniApi, getMethodId(testing::_, methodIdentifier.c_str(), methodSignature.c_str())).WillByDefault(Return(methodId));
+
+    ASSERT_TRUE(javaClass.load());
+    ASSERT_TRUE(javaClass.loadMethod(methodIdentifier, methodSignature));
+  }
+
+  TEST_F(JniClassTest, loadMethod_repeat)
+  {
+    string classPath = "java.utils.String";
+    JniClass javaClass = this->createJniClass(classPath);
+
+    EXPECT_CALL(*this->jniApi, findClass(classPath)).Times(AtLeast(1));
+    ON_CALL(*this->jniApi, findClass(classPath)).WillByDefault(Return(make_shared<_jclass>().get()));
+    string methodIdentifier = "getDay";
+    string methodSignature = "()B";
+    EXPECT_CALL(*this->jniApi, getMethodId(testing::_, methodIdentifier.c_str(), methodSignature.c_str())).Times(AtLeast(1));
+    jmethodID methodId = (jmethodID) make_shared<int>().get();
+    ON_CALL(*this->jniApi, getMethodId(testing::_, methodIdentifier.c_str(), methodSignature.c_str())).WillByDefault(Return(methodId));
+
+    ASSERT_TRUE(javaClass.load());
+    ASSERT_TRUE(javaClass.loadMethod(methodIdentifier, methodSignature));
+    ASSERT_FALSE(javaClass.loadMethod(methodIdentifier, methodSignature));
+  }
+
+  TEST_F(JniClassTest, loadMethod_without_previous_class_load)
+  {
+    string classPath = "java.utils.String";
+    JniClass javaClass = this->createJniClass(classPath);
+
+    string methodIdentifier = "getDay";
+    string methodSignature = "()B";
+
+    EXPECT_THROW(
+        {
+          try
+          {
+            javaClass.loadMethod(methodIdentifier, methodSignature);
+          }
+          catch (const NullPointerException &_exception)
+          {
+            throw;
+          }
+        },
+        NullPointerException);
+  }
 }

+ 1 - 0
test/classes/core/jni/MockJniApi.hpp

@@ -23,6 +23,7 @@ namespace test::core::jni
       ~MockJniApi() noexcept override;
 
       MOCK_METHOD(jclass, findClass, (const ::std::string &_classPath), (override));
+      MOCK_METHOD(jmethodID, getMethodId, (jclass _javaClass, const char *_methodIdentifier, const char *_methodSignature), (override));
   };
 }