Skip to content
Permalink
master
Switch branches/tags
Go to file
 
 
Cannot retrieve contributors at this time
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#include "MethodInvoker.h"
#ifdef WITH_FBSYSTRACE
#include <fbsystrace.h>
#endif
#include <glog/logging.h>
#include <cxxreact/CxxNativeModule.h>
#include <fbjni/fbjni.h>
#include "JCallback.h"
#include "ReadableNativeArray.h"
#include "ReadableNativeMap.h"
#include "WritableNativeArray.h"
#include "WritableNativeMap.h"
using namespace facebook::jni;
namespace facebook {
namespace react {
namespace {
using dynamic_iterator = folly::dynamic::const_iterator;
struct JPromiseImpl : public JavaClass<JPromiseImpl> {
constexpr static auto kJavaDescriptor =
"Lcom/facebook/react/bridge/PromiseImpl;";
static local_ref<javaobject> create(
local_ref<JCallback::javaobject> resolve,
local_ref<JCallback::javaobject> reject) {
return newInstance(resolve, reject);
}
};
// HACK: Exposes constructor
struct ExposedReadableNativeArray : public ReadableNativeArray {
explicit ExposedReadableNativeArray(folly::dynamic array)
: ReadableNativeArray(std::move(array)) {}
};
jdouble extractDouble(const folly::dynamic &value) {
if (value.isInt()) {
return static_cast<jdouble>(value.getInt());
} else {
return static_cast<jdouble>(value.getDouble());
}
}
jint extractInteger(const folly::dynamic &value) {
// The logic here is taken from convertDynamicIfIntegral, but the
// return type and exception are different.
if (value.isInt()) {
return value.getInt();
}
double dbl = value.getDouble();
jint result = static_cast<jint>(dbl);
if (dbl != result) {
throw std::invalid_argument(folly::to<std::string>(
"Tried to convert jint argument, but got a non-integral double: ",
dbl));
}
return result;
}
local_ref<JCxxCallbackImpl::jhybridobject> extractCallback(
std::weak_ptr<Instance> &instance,
const folly::dynamic &value) {
if (value.isNull()) {
return local_ref<JCxxCallbackImpl::jhybridobject>(nullptr);
} else {
return JCxxCallbackImpl::newObjectCxxArgs(makeCallback(instance, value));
}
}
local_ref<JPromiseImpl::javaobject> extractPromise(
std::weak_ptr<Instance> &instance,
dynamic_iterator &it,
dynamic_iterator &end) {
auto resolve = extractCallback(instance, *it++);
CHECK(it != end);
auto reject = extractCallback(instance, *it++);
return JPromiseImpl::create(resolve, reject);
}
bool isNullable(char type) {
switch (type) {
case 'Z':
case 'I':
case 'F':
case 'S':
case 'A':
case 'M':
case 'X':
return true;
default:
return false;
;
}
}
jvalue extract(
std::weak_ptr<Instance> &instance,
char type,
dynamic_iterator &it,
dynamic_iterator &end) {
CHECK(it != end);
jvalue value;
if (type == 'P') {
value.l = extractPromise(instance, it, end).release();
return value;
}
const auto &arg = *it++;
if (isNullable(type) && arg.isNull()) {
value.l = nullptr;
return value;
}
switch (type) {
case 'z':
value.z = static_cast<jboolean>(arg.getBool());
break;
case 'Z':
value.l =
JBoolean::valueOf(static_cast<jboolean>(arg.getBool())).release();
break;
case 'i':
value.i = extractInteger(arg);
break;
case 'I':
value.l = JInteger::valueOf(extractInteger(arg)).release();
break;
case 'f':
value.f = static_cast<jfloat>(extractDouble(arg));
break;
case 'F':
value.l =
JFloat::valueOf(static_cast<jfloat>(extractDouble(arg))).release();
break;
case 'd':
value.d = extractDouble(arg);
break;
case 'D':
value.l = JDouble::valueOf(extractDouble(arg)).release();
break;
case 'S':
value.l = make_jstring(arg.getString().c_str()).release();
break;
case 'A':
value.l = ReadableNativeArray::newObjectCxxArgs(arg).release();
break;
case 'M':
value.l = ReadableNativeMap::newObjectCxxArgs(arg).release();
break;
case 'X':
value.l = extractCallback(instance, arg).release();
break;
default:
LOG(FATAL) << "Unknown param type: " << type;
}
return value;
}
std::size_t countJsArgs(const std::string &signature) {
std::size_t count = 0;
for (char c : signature) {
switch (c) {
case 'P':
count += 2;
break;
default:
count += 1;
break;
}
}
return count;
}
} // namespace
MethodInvoker::MethodInvoker(
alias_ref<JReflectMethod::javaobject> method,
std::string methodName,
std::string signature,
std::string traceName,
bool isSync)
: method_(method->getMethodID()),
methodName_(methodName),
signature_(signature),
jsArgCount_(countJsArgs(signature) - 2),
traceName_(std::move(traceName)),
isSync_(isSync) {
CHECK(signature_.at(1) == '.') << "Improper module method signature";
CHECK(isSync_ || signature_.at(0) == 'v')
<< "Non-sync hooks cannot have a non-void return type";
}
std::string MethodInvoker::getMethodName() const {
return methodName_;
}
MethodCallResult MethodInvoker::invoke(
std::weak_ptr<Instance> &instance,
alias_ref<JBaseJavaModule::javaobject> module,
const folly::dynamic &params) {
#ifdef WITH_FBSYSTRACE
fbsystrace::FbSystraceSection s(
TRACE_TAG_REACT_CXX_BRIDGE,
isSync_ ? "callJavaSyncHook" : "callJavaModuleMethod",
"method",
traceName_);
#endif
if (params.size() != jsArgCount_) {
throw std::invalid_argument(folly::to<std::string>(
"expected ", jsArgCount_, " arguments, got ", params.size()));
}
auto env = Environment::current();
auto argCount = signature_.size() - 2;
JniLocalScope scope(env, argCount);
jvalue args[argCount];
std::transform(
signature_.begin() + 2,
signature_.end(),
args,
[&instance, it = params.begin(), end = params.end()](char type) mutable {
return extract(instance, type, it, end);
});
#define PRIMITIVE_CASE(METHOD) \
{ \
auto result = env->Call##METHOD##MethodA(module.get(), method_, args); \
throwPendingJniExceptionAsCppException(); \
return folly::dynamic(result); \
}
#define PRIMITIVE_CASE_CASTING(METHOD, RESULT_TYPE) \
{ \
auto result = env->Call##METHOD##MethodA(module.get(), method_, args); \
throwPendingJniExceptionAsCppException(); \
return folly::dynamic(static_cast<RESULT_TYPE>(result)); \
}
#define OBJECT_CASE(JNI_CLASS, ACTIONS) \
{ \
auto jobject = env->CallObjectMethodA(module.get(), method_, args); \
throwPendingJniExceptionAsCppException(); \
if (!jobject) { \
return folly::dynamic(nullptr); \
} \
auto result = adopt_local(static_cast<JNI_CLASS::javaobject>(jobject)); \
return folly::dynamic(result->ACTIONS()); \
}
#define OBJECT_CASE_CASTING(JNI_CLASS, ACTIONS, RESULT_TYPE) \
{ \
auto jobject = env->CallObjectMethodA(module.get(), method_, args); \
throwPendingJniExceptionAsCppException(); \
if (!jobject) { \
return folly::dynamic(nullptr); \
} \
auto result = adopt_local(static_cast<JNI_CLASS::javaobject>(jobject)); \
return folly::dynamic(static_cast<RESULT_TYPE>(result->ACTIONS())); \
}
char returnType = signature_.at(0);
switch (returnType) {
case 'v':
env->CallVoidMethodA(module.get(), method_, args);
throwPendingJniExceptionAsCppException();
return folly::none;
case 'z':
PRIMITIVE_CASE_CASTING(Boolean, bool)
case 'Z':
OBJECT_CASE_CASTING(JBoolean, value, bool)
case 'i':
PRIMITIVE_CASE(Int)
case 'I':
OBJECT_CASE(JInteger, value)
case 'd':
PRIMITIVE_CASE(Double)
case 'D':
OBJECT_CASE(JDouble, value)
case 'f':
PRIMITIVE_CASE(Float)
case 'F':
OBJECT_CASE(JFloat, value)
case 'S':
OBJECT_CASE(JString, toStdString)
case 'M':
OBJECT_CASE(WritableNativeMap, cthis()->consume)
case 'A':
OBJECT_CASE(WritableNativeArray, cthis()->consume)
default:
LOG(FATAL) << "Unknown return type: " << returnType;
return folly::none;
}
}
} // namespace react
} // namespace facebook