Skip to content

Commit

Permalink
feat: Explicitly catch ClassNotFound and NoSuchMethod errors (#318)
Browse files Browse the repository at this point in the history
* feat: Explicitly catch `ClassNotFound` errors

* feat: Also check for NoSuchMethodError

* chore: Use tick `

* Adjust message

* feat: Move JNI lookup code to `AutolinkedHybridObject` to avoid spaghetti (#319)

* feat: Move JNI lookup code to `AutolinkedHybridObject`

* fix: Extract name & format

* fix: Remove null check from generated code

* Fix typename

* whoops

* Update AutolinkedHybridObject.hpp

* Update AutolinkedHybridObject.hpp

* hä geht eh so oida

* `AutolinkedHybridObject` -> `ConstructableHybridObject`
  • Loading branch information
mrousavy authored Nov 11, 2024
1 parent 6682b2d commit e37d261
Show file tree
Hide file tree
Showing 3 changed files with 95 additions and 45 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -33,20 +33,18 @@ export function createJNIHybridObjectRegistration({
language: 'c++',
space: 'system',
},
{
name: 'NitroModules/DefaultConstructableObject.hpp',
language: 'c++',
space: 'system',
},
],
cppCode: `
HybridObjectRegistry::registerHybridObjectConstructor(
"${hybridObjectName}",
[]() -> std::shared_ptr<HybridObject> {
static auto javaClass = jni::findClassStatic("${jniNamespace}");
static auto defaultConstructor = javaClass->getConstructor<${JHybridTSpec}::javaobject()>();
auto instance = javaClass->newObject(defaultConstructor);
#ifdef NITRO_DEBUG
if (instance == nullptr) [[unlikely]] {
throw std::runtime_error("Failed to create an instance of \\"${JHybridTSpec}\\" - the constructor returned null!");
}
#endif
static DefaultConstructableObject<${JHybridTSpec}::javaobject> object("${jniNamespace}");
auto instance = object.create();
auto globalRef = jni::make_global(instance);
return JNISharedPtr::make_shared_from_jni<${JHybridTSpec}>(globalRef);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
#include "JHybridBaseSpec.hpp"
#include "JHybridChildSpec.hpp"
#include <NitroModules/JNISharedPtr.hpp>
#include <NitroModules/DefaultConstructableObject.hpp>
#include "HybridTestObjectCpp.hpp"

namespace margelo::nitro::image {
Expand Down Expand Up @@ -49,15 +50,8 @@ int initialize(JavaVM* vm) {
HybridObjectRegistry::registerHybridObjectConstructor(
"ImageFactory",
[]() -> std::shared_ptr<HybridObject> {
static auto javaClass = jni::findClassStatic("com/margelo/nitro/image/ImageFactory");
static auto defaultConstructor = javaClass->getConstructor<JHybridImageFactorySpec::javaobject()>();

auto instance = javaClass->newObject(defaultConstructor);
#ifdef NITRO_DEBUG
if (instance == nullptr) [[unlikely]] {
throw std::runtime_error("Failed to create an instance of \"JHybridImageFactorySpec\" - the constructor returned null!");
}
#endif
static DefaultConstructableObject<JHybridImageFactorySpec::javaobject> object("com/margelo/nitro/image/ImageFactory");
auto instance = object.create();
auto globalRef = jni::make_global(instance);
return JNISharedPtr::make_shared_from_jni<JHybridImageFactorySpec>(globalRef);
}
Expand All @@ -74,47 +68,26 @@ int initialize(JavaVM* vm) {
HybridObjectRegistry::registerHybridObjectConstructor(
"TestObjectSwiftKotlin",
[]() -> std::shared_ptr<HybridObject> {
static auto javaClass = jni::findClassStatic("com/margelo/nitro/image/HybridTestObjectKotlin");
static auto defaultConstructor = javaClass->getConstructor<JHybridTestObjectSwiftKotlinSpec::javaobject()>();

auto instance = javaClass->newObject(defaultConstructor);
#ifdef NITRO_DEBUG
if (instance == nullptr) [[unlikely]] {
throw std::runtime_error("Failed to create an instance of \"JHybridTestObjectSwiftKotlinSpec\" - the constructor returned null!");
}
#endif
static DefaultConstructableObject<JHybridTestObjectSwiftKotlinSpec::javaobject> object("com/margelo/nitro/image/HybridTestObjectKotlin");
auto instance = object.create();
auto globalRef = jni::make_global(instance);
return JNISharedPtr::make_shared_from_jni<JHybridTestObjectSwiftKotlinSpec>(globalRef);
}
);
HybridObjectRegistry::registerHybridObjectConstructor(
"Base",
[]() -> std::shared_ptr<HybridObject> {
static auto javaClass = jni::findClassStatic("com/margelo/nitro/image/HybridBase");
static auto defaultConstructor = javaClass->getConstructor<JHybridBaseSpec::javaobject()>();

auto instance = javaClass->newObject(defaultConstructor);
#ifdef NITRO_DEBUG
if (instance == nullptr) [[unlikely]] {
throw std::runtime_error("Failed to create an instance of \"JHybridBaseSpec\" - the constructor returned null!");
}
#endif
static DefaultConstructableObject<JHybridBaseSpec::javaobject> object("com/margelo/nitro/image/HybridBase");
auto instance = object.create();
auto globalRef = jni::make_global(instance);
return JNISharedPtr::make_shared_from_jni<JHybridBaseSpec>(globalRef);
}
);
HybridObjectRegistry::registerHybridObjectConstructor(
"Child",
[]() -> std::shared_ptr<HybridObject> {
static auto javaClass = jni::findClassStatic("com/margelo/nitro/image/HybridChild");
static auto defaultConstructor = javaClass->getConstructor<JHybridChildSpec::javaobject()>();

auto instance = javaClass->newObject(defaultConstructor);
#ifdef NITRO_DEBUG
if (instance == nullptr) [[unlikely]] {
throw std::runtime_error("Failed to create an instance of \"JHybridChildSpec\" - the constructor returned null!");
}
#endif
static DefaultConstructableObject<JHybridChildSpec::javaobject> object("com/margelo/nitro/image/HybridChild");
auto instance = object.create();
auto globalRef = jni::make_global(instance);
return JNISharedPtr::make_shared_from_jni<JHybridChildSpec>(globalRef);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
//
// DefaultConstructableObject.hpp
// react-native-nitro
//
// Created by Marc Rousavy on 11.11.24.
//

#pragma once

#include "NitroDefines.hpp"
#include <fbjni/fbjni.h>

namespace margelo::nitro {

using namespace facebook;

template <typename T>
class DefaultConstructableObject {
public:
explicit DefaultConstructableObject(const char* javaClassDescriptor) {
try {
// Find JNI class and default constructor
_javaClass = jni::findClassStatic(javaClassDescriptor);
_defaultConstructor = _javaClass->getConstructor<T()>();
} catch (const jni::JniException& exc) {
std::string message = exc.what();
std::string descriptor = javaClassDescriptor;
std::string className = findClassName(descriptor);
if (message.find("ClassNotFoundException")) {
// Java class cannot be found
throw std::runtime_error(
"Couldn't find class `" + descriptor +
"`!\n"
"- Make sure the class exists in the specified namespace.\n"
"- Make sure the class is not stripped. If you are using ProGuard, add `@Keep` and `@DoNotStrip` annotations to `" +
className + "`.");
} else if (message.find("NoSuchMethodError")) {
// Default Constructor cannot be found
throw std::runtime_error(
"Couldn't find " + className +
"'s default constructor!\n"
"- If you want to autolink " +
className +
", add a default constructor that takes zero arguments.\n"
"- If you need arguments to create instances of " +
className +
", create a separate HybridObject that acts as a factory for this HybridObject to create instances of it with parameters.\n"
"- If you already have a default constructor, make sure it is not being stripped. If you are using ProGuard, add `@Keep` and "
"`@DoNotStrip` annotations to the default constructor.");
} else {
throw;
}
}
}

public:
jni::local_ref<T> create() const {
// Calls the class's default constructor
auto instance = _javaClass->newObject(_defaultConstructor);
#ifdef NITRO_DEBUG
if (instance == nullptr) [[unlikely]] {
throw std::runtime_error("Failed to create an instance of \"JHybridTestObjectSwiftKotlinSpec\" - the constructor returned null!");
}
#endif
return instance;
}

private:
static std::string findClassName(const std::string& jniDescriptor) {
size_t lastSlash = jniDescriptor.rfind('/');
return jniDescriptor.substr(lastSlash + 1);
}

private:
jni::alias_ref<jni::JClass> _javaClass;
jni::JConstructor<T()> _defaultConstructor;
};

} // namespace margelo::nitro

0 comments on commit e37d261

Please sign in to comment.