1 // Copyright © 2021-present 650 Industries, Inc. (aka Expo)
2
3 #include "FrontendConverter.h"
4 #include "ExpectedType.h"
5 #include "FrontendConverterProvider.h"
6 #include "../JavaReferencesCache.h"
7 #include "../Exceptions.h"
8 #include "../JavaScriptTypedArray.h"
9 #include "../JSIInteropModuleRegistry.h"
10 #include "../JavaScriptObject.h"
11 #include "../JavaScriptValue.h"
12 #include "../JavaScriptFunction.h"
13 #include "../javaclasses/Collections.h"
14
15 #include "react/jni/ReadableNativeMap.h"
16 #include "react/jni/ReadableNativeArray.h"
17 #include <jsi/JSIDynamic.h>
18
19 #include <utility>
20 #include <algorithm>
21
22 namespace jni = facebook::jni;
23 namespace jsi = facebook::jsi;
24 namespace react = facebook::react;
25
26 namespace expo {
convert(jsi::Runtime & rt,JNIEnv * env,JSIInteropModuleRegistry * moduleRegistry,const jsi::Value & value) const27 jobject IntegerFrontendConverter::convert(
28 jsi::Runtime &rt,
29 JNIEnv *env,
30 JSIInteropModuleRegistry *moduleRegistry,
31 const jsi::Value &value
32 ) const {
33 auto &integerClass = JavaReferencesCache::instance()
34 ->getJClass("java/lang/Integer");
35 jmethodID integerConstructor = integerClass.getMethod("<init>", "(I)V");
36 return env->NewObject(integerClass.clazz, integerConstructor,
37 static_cast<int>(value.getNumber()));
38 }
39
canConvert(jsi::Runtime & rt,const jsi::Value & value) const40 bool IntegerFrontendConverter::canConvert(jsi::Runtime &rt, const jsi::Value &value) const {
41 return value.isNumber();
42 }
43
convert(jsi::Runtime & rt,JNIEnv * env,JSIInteropModuleRegistry * moduleRegistry,const jsi::Value & value) const44 jobject LongFrontendConverter::convert(
45 jsi::Runtime &rt,
46 JNIEnv *env,
47 JSIInteropModuleRegistry *moduleRegistry,
48 const jsi::Value &value
49 ) const {
50 auto &longClass = JavaReferencesCache::instance()
51 ->getJClass("java/lang/Long");
52 jmethodID longConstructor = longClass.getMethod("<init>", "(J)V");
53 return env->NewObject(longClass.clazz, longConstructor,
54 static_cast<jlong>(value.getNumber()));
55 }
56
canConvert(jsi::Runtime & rt,const jsi::Value & value) const57 bool LongFrontendConverter::canConvert(jsi::Runtime &rt, const jsi::Value &value) const {
58 return value.isNumber();
59 }
60
convert(jsi::Runtime & rt,JNIEnv * env,JSIInteropModuleRegistry * moduleRegistry,const jsi::Value & value) const61 jobject FloatFrontendConverter::convert(
62 jsi::Runtime &rt,
63 JNIEnv *env,
64 JSIInteropModuleRegistry *moduleRegistry,
65 const jsi::Value &value
66 ) const {
67 auto &floatClass = JavaReferencesCache::instance()
68 ->getJClass("java/lang/Float");
69 jmethodID floatConstructor = floatClass.getMethod("<init>", "(F)V");
70 return env->NewObject(floatClass.clazz, floatConstructor,
71 static_cast<float>(value.getNumber()));
72 }
73
canConvert(jsi::Runtime & rt,const jsi::Value & value) const74 bool FloatFrontendConverter::canConvert(jsi::Runtime &rt, const jsi::Value &value) const {
75 return value.isNumber();
76 }
77
convert(jsi::Runtime & rt,JNIEnv * env,JSIInteropModuleRegistry * moduleRegistry,const jsi::Value & value) const78 jobject BooleanFrontendConverter::convert(
79 jsi::Runtime &rt,
80 JNIEnv *env,
81 JSIInteropModuleRegistry *moduleRegistry,
82 const jsi::Value &value
83 ) const {
84 auto &booleanClass = JavaReferencesCache::instance()
85 ->getJClass("java/lang/Boolean");
86 jmethodID booleanConstructor = booleanClass.getMethod("<init>", "(Z)V");
87 return env->NewObject(booleanClass.clazz, booleanConstructor, value.getBool());
88 }
89
canConvert(jsi::Runtime & rt,const jsi::Value & value) const90 bool BooleanFrontendConverter::canConvert(jsi::Runtime &rt, const jsi::Value &value) const {
91 return value.isBool();
92 }
93
convert(jsi::Runtime & rt,JNIEnv * env,JSIInteropModuleRegistry * moduleRegistry,const jsi::Value & value) const94 jobject DoubleFrontendConverter::convert(
95 jsi::Runtime &rt,
96 JNIEnv *env,
97 JSIInteropModuleRegistry *moduleRegistry,
98 const jsi::Value &value
99 ) const {
100 auto &doubleClass = JavaReferencesCache::instance()
101 ->getJClass("java/lang/Double");
102 jmethodID doubleConstructor = doubleClass.getMethod("<init>", "(D)V");
103 return env->NewObject(doubleClass.clazz, doubleConstructor, value.getNumber());
104 }
105
canConvert(jsi::Runtime & rt,const jsi::Value & value) const106 bool DoubleFrontendConverter::canConvert(jsi::Runtime &rt, const jsi::Value &value) const {
107 return value.isNumber();
108 }
109
convert(jsi::Runtime & rt,JNIEnv * env,JSIInteropModuleRegistry * moduleRegistry,const jsi::Value & value) const110 jobject StringFrontendConverter::convert(
111 jsi::Runtime &rt,
112 JNIEnv *env,
113 JSIInteropModuleRegistry *moduleRegistry,
114 const jsi::Value &value
115 ) const {
116 return env->NewStringUTF(value.getString(rt).utf8(rt).c_str());
117 }
118
canConvert(jsi::Runtime & rt,const jsi::Value & value) const119 bool StringFrontendConverter::canConvert(jsi::Runtime &rt, const jsi::Value &value) const {
120 return value.isString();
121 }
122
convert(jsi::Runtime & rt,JNIEnv * env,JSIInteropModuleRegistry * moduleRegistry,const jsi::Value & value) const123 jobject ReadableNativeArrayFrontendConverter::convert(
124 jsi::Runtime &rt,
125 JNIEnv *env,
126 JSIInteropModuleRegistry *moduleRegistry,
127 const jsi::Value &value
128 ) const {
129 auto dynamic = jsi::dynamicFromValue(rt, value);
130 return react::ReadableNativeArray::newObjectCxxArgs(std::move(dynamic)).release();
131 }
132
canConvert(jsi::Runtime & rt,const jsi::Value & value) const133 bool ReadableNativeArrayFrontendConverter::canConvert(
134 jsi::Runtime &rt,
135 const jsi::Value &value
136 ) const {
137 return value.isObject() && value.getObject(rt).isArray(rt);
138 }
139
convert(jsi::Runtime & rt,JNIEnv * env,JSIInteropModuleRegistry * moduleRegistry,const jsi::Value & value) const140 jobject ReadableNativeMapArrayFrontendConverter::convert(
141 jsi::Runtime &rt,
142 JNIEnv *env,
143 JSIInteropModuleRegistry *moduleRegistry,
144 const jsi::Value &value
145 ) const {
146 auto dynamic = jsi::dynamicFromValue(rt, value);
147 return react::ReadableNativeMap::createWithContents(std::move(dynamic)).release();
148 }
149
canConvert(jsi::Runtime & rt,const jsi::Value & value) const150 bool ReadableNativeMapArrayFrontendConverter::canConvert(
151 jsi::Runtime &rt,
152 const jsi::Value &value) const {
153 return value.isObject();
154 }
155
convert(jsi::Runtime & rt,JNIEnv * env,JSIInteropModuleRegistry * moduleRegistry,const jsi::Value & value) const156 jobject TypedArrayFrontendConverter::convert(
157 jsi::Runtime &rt,
158 JNIEnv *env,
159 JSIInteropModuleRegistry *moduleRegistry,
160 const jsi::Value &value
161 ) const {
162 return JavaScriptTypedArray::newInstance(
163 moduleRegistry,
164 moduleRegistry->runtimeHolder->weak_from_this(),
165 std::make_shared<jsi::Object>(value.getObject(rt))
166 ).release();
167 }
168
canConvert(jsi::Runtime & rt,const jsi::Value & value) const169 bool TypedArrayFrontendConverter::canConvert(
170 jsi::Runtime &rt,
171 const jsi::Value &value
172 ) const {
173 return value.isObject();
174 }
175
convert(jsi::Runtime & rt,JNIEnv * env,JSIInteropModuleRegistry * moduleRegistry,const jsi::Value & value) const176 jobject JavaScriptValueFrontendConverter::convert(
177 jsi::Runtime &rt,
178 JNIEnv *env,
179 JSIInteropModuleRegistry *moduleRegistry,
180 const jsi::Value &value
181 ) const {
182 return JavaScriptValue::newInstance(
183 moduleRegistry,
184 moduleRegistry->runtimeHolder->weak_from_this(),
185 // TODO(@lukmccall): make sure that copy here is necessary
186 std::make_shared<jsi::Value>(jsi::Value(rt, value))
187 ).release();
188 }
189
canConvert(jsi::Runtime & rt,const jsi::Value & value) const190 bool JavaScriptValueFrontendConverter::canConvert(jsi::Runtime &rt, const jsi::Value &value) const {
191 return true;
192 }
193
convert(jsi::Runtime & rt,JNIEnv * env,JSIInteropModuleRegistry * moduleRegistry,const jsi::Value & value) const194 jobject JavaScriptObjectFrontendConverter::convert(
195 jsi::Runtime &rt,
196 JNIEnv *env,
197 JSIInteropModuleRegistry *moduleRegistry,
198 const jsi::Value &value
199 ) const {
200 return JavaScriptObject::newInstance(
201 moduleRegistry,
202 moduleRegistry->runtimeHolder->weak_from_this(),
203 std::make_shared<jsi::Object>(value.getObject(rt))
204 ).release();
205 }
206
canConvert(jsi::Runtime & rt,const jsi::Value & value) const207 bool JavaScriptObjectFrontendConverter::canConvert(
208 jsi::Runtime &rt,
209 const jsi::Value &value
210 ) const {
211 return value.isObject();
212 }
213
convert(jsi::Runtime & rt,JNIEnv * env,JSIInteropModuleRegistry * moduleRegistry,const jsi::Value & value) const214 jobject JavaScriptFunctionFrontendConverter::convert(
215 jsi::Runtime &rt,
216 JNIEnv *env,
217 JSIInteropModuleRegistry *moduleRegistry,
218 const jsi::Value &value
219 ) const {
220 return JavaScriptFunction::newInstance(
221 moduleRegistry,
222 moduleRegistry->runtimeHolder->weak_from_this(),
223 std::make_shared<jsi::Function>(value.getObject(rt).asFunction(rt))
224 ).release();
225 }
226
canConvert(jsi::Runtime & rt,const jsi::Value & value) const227 bool JavaScriptFunctionFrontendConverter::canConvert(
228 jsi::Runtime &rt,
229 const jsi::Value &value
230 ) const {
231 return value.isObject() && value.asObject(rt).isFunction(rt);
232 }
233
convert(jsi::Runtime & rt,JNIEnv * env,JSIInteropModuleRegistry * moduleRegistry,const jsi::Value & value) const234 jobject UnknownFrontendConverter::convert(
235 jsi::Runtime &rt,
236 JNIEnv *env,
237 JSIInteropModuleRegistry *moduleRegistry,
238 const jsi::Value &value
239 ) const {
240 auto stringRepresentation = value.toString(rt).utf8(rt);
241 throwNewJavaException(
242 UnexpectedException::create(
243 "Cannot convert '" + stringRepresentation + "' to a Kotlin type.").get()
244 );
245 }
246
canConvert(jsi::Runtime & rt,const jsi::Value & value) const247 bool UnknownFrontendConverter::canConvert(jsi::Runtime &rt, const jsi::Value &value) const {
248 return true;
249 }
250
PolyFrontendConverter(std::vector<std::shared_ptr<FrontendConverter>> converters)251 PolyFrontendConverter::PolyFrontendConverter(
252 std::vector<std::shared_ptr<FrontendConverter>> converters
253 ) : converters(std::move(converters)) {
254 }
255
canConvert(jsi::Runtime & rt,const jsi::Value & value) const256 bool PolyFrontendConverter::canConvert(
257 jsi::Runtime &rt,
258 const jsi::Value &value
259 ) const {
260 // Checks whether any of inner converters can handle the conversion.
261 return std::any_of(
262 converters.begin(),
263 converters.end(),
264 [&rt = rt, &value = value](const std::shared_ptr<FrontendConverter> &converter) {
265 return converter->canConvert(rt, value);
266 }
267 );
268 }
269
convert(jsi::Runtime & rt,JNIEnv * env,JSIInteropModuleRegistry * moduleRegistry,const jsi::Value & value) const270 jobject PolyFrontendConverter::convert(
271 jsi::Runtime &rt,
272 JNIEnv *env,
273 JSIInteropModuleRegistry *moduleRegistry,
274 const jsi::Value &value
275 ) const {
276 for (auto &converter: converters) {
277 if (converter->canConvert(rt, value)) {
278 return converter->convert(rt, env, moduleRegistry, value);
279 }
280 }
281 // That shouldn't happen.
282 auto stringRepresentation = value.toString(rt).utf8(rt);
283 throwNewJavaException(
284 UnexpectedException::create(
285 "Cannot convert '" + stringRepresentation + "' to a Kotlin type.").get()
286 );
287 }
288
PrimitiveArrayFrontendConverter(jni::local_ref<SingleType::javaobject> expectedType)289 PrimitiveArrayFrontendConverter::PrimitiveArrayFrontendConverter(
290 jni::local_ref<SingleType::javaobject> expectedType
291 ) {
292 auto parameterExpectedType = expectedType->getFirstParameterType();
293 parameterType = parameterExpectedType->getCombinedTypes();
294 parameterConverter = FrontendConverterProvider::instance()->obtainConverter(
295 parameterExpectedType
296 );
297 javaType = parameterExpectedType->getJClassString();
298 }
299
300 template<typename T, typename A>
createPrimitiveArray(jsi::Runtime & rt,JNIEnv * env,const jsi::Array & jsArray,A (JNIEnv::* arrayConstructor)(jsize),void (JNIEnv::* setRegion)(A,jsize,jsize,const T *))301 jobject createPrimitiveArray(
302 jsi::Runtime &rt,
303 JNIEnv *env,
304 const jsi::Array &jsArray,
305 A (JNIEnv::*arrayConstructor)(jsize),
306 void (JNIEnv::*setRegion)(A, jsize, jsize, const T *)
307 ) {
308 size_t size = jsArray.size(rt);
309 std::vector<T> tmpVector(size);
310 for (size_t i = 0; i < size; i++) {
311 tmpVector[i] = (T) jsArray.getValueAtIndex(rt, i).asNumber();
312 }
313 auto result = std::invoke(arrayConstructor, env, size);
314 std::invoke(setRegion, env, result, 0, size, tmpVector.data());
315 return result;
316 }
317
convert(jsi::Runtime & rt,JNIEnv * env,JSIInteropModuleRegistry * moduleRegistry,const jsi::Value & value) const318 jobject PrimitiveArrayFrontendConverter::convert(
319 jsi::Runtime &rt,
320 JNIEnv *env,
321 JSIInteropModuleRegistry *moduleRegistry,
322 const jsi::Value &value
323 ) const {
324 auto jsArray = value.asObject(rt).asArray(rt);
325 auto _createPrimitiveArray = [&rt, env, &jsArray](
326 auto arrayConstructor, auto setRegion
327 ) -> jobject {
328 return createPrimitiveArray(rt, env, jsArray, arrayConstructor, setRegion);
329 };
330
331 if (parameterType == CppType::INT) {
332 return _createPrimitiveArray(
333 &JNIEnv::NewIntArray,
334 &JNIEnv::SetIntArrayRegion
335 );
336 }
337 if (parameterType == CppType::LONG) {
338 return _createPrimitiveArray(
339 &JNIEnv::NewLongArray,
340 &JNIEnv::SetLongArrayRegion
341 );
342 }
343 if (parameterType == CppType::DOUBLE) {
344 return _createPrimitiveArray(
345 &JNIEnv::NewDoubleArray,
346 &JNIEnv::SetDoubleArrayRegion
347 );
348 }
349 if (parameterType == CppType::FLOAT) {
350 return _createPrimitiveArray(
351 &JNIEnv::NewFloatArray,
352 &JNIEnv::SetFloatArrayRegion
353 );
354 }
355 if (parameterType == CppType::BOOLEAN) {
356 return _createPrimitiveArray(
357 &JNIEnv::NewBooleanArray,
358 &JNIEnv::SetBooleanArrayRegion
359 );
360 }
361
362 size_t size = jsArray.size(rt);
363 auto result = env->NewObjectArray(
364 size,
365 JavaReferencesCache::instance()->getOrLoadJClass(env, javaType).clazz,
366 nullptr
367 );
368 for (size_t i = 0; i < size; i++) {
369 auto convertedElement = parameterConverter->convert(
370 rt, env, moduleRegistry, jsArray.getValueAtIndex(rt, i)
371 );
372 env->SetObjectArrayElement(result, i, convertedElement);
373 env->DeleteLocalRef(convertedElement);
374 }
375 return result;
376 }
377
canConvert(jsi::Runtime & rt,const jsi::Value & value) const378 bool PrimitiveArrayFrontendConverter::canConvert(jsi::Runtime &rt, const jsi::Value &value) const {
379 return value.isObject() && value.asObject(rt).isArray(rt);
380 }
381
ListFrontendConverter(jni::local_ref<SingleType::javaobject> expectedType)382 ListFrontendConverter::ListFrontendConverter(
383 jni::local_ref<SingleType::javaobject> expectedType
384 ) : parameterConverter(
385 FrontendConverterProvider::instance()->obtainConverter(
386 expectedType->getFirstParameterType()
387 )
388 ) {}
389
convert(jsi::Runtime & rt,JNIEnv * env,JSIInteropModuleRegistry * moduleRegistry,const jsi::Value & value) const390 jobject ListFrontendConverter::convert(
391 jsi::Runtime &rt,
392 JNIEnv *env,
393 JSIInteropModuleRegistry *moduleRegistry,
394 const jsi::Value &value
395 ) const {
396 auto jsArray = value.asObject(rt).asArray(rt);
397 size_t size = jsArray.size(rt);
398
399 auto arrayList = java::ArrayList<jobject>::create(size);
400 for (size_t i = 0; i < size; i++) {
401 auto jsValue = jsArray.getValueAtIndex(rt, i);
402
403 // TODO(@lukmccall): pass information to CPP if the underlying type is nullable or not.
404 if (jsValue.isNull() || jsValue.isUndefined()) {
405 arrayList->add(nullptr);
406 continue;
407 }
408
409 auto convertedElement = parameterConverter->convert(
410 rt, env, moduleRegistry, jsValue
411 );
412 arrayList->add(convertedElement);
413 env->DeleteLocalRef(convertedElement);
414 }
415
416 return arrayList.release();
417 }
418
canConvert(jsi::Runtime & rt,const jsi::Value & value) const419 bool ListFrontendConverter::canConvert(jsi::Runtime &rt, const jsi::Value &value) const {
420 return value.isObject() && value.asObject(rt).isArray(rt);
421 }
422
MapFrontendConverter(jni::local_ref<SingleType::javaobject> expectedType)423 MapFrontendConverter::MapFrontendConverter(
424 jni::local_ref<SingleType::javaobject> expectedType
425 ) : valueConverter(
426 FrontendConverterProvider::instance()->obtainConverter(
427 expectedType->getFirstParameterType()
428 )
429 ) {}
430
convert(jsi::Runtime & rt,JNIEnv * env,JSIInteropModuleRegistry * moduleRegistry,const jsi::Value & value) const431 jobject MapFrontendConverter::convert(
432 jsi::Runtime &rt,
433 JNIEnv *env,
434 JSIInteropModuleRegistry *moduleRegistry,
435 const jsi::Value &value
436 ) const {
437 auto jsObject = value.asObject(rt);
438 auto propertyNames = jsObject.getPropertyNames(rt);
439 size_t size = propertyNames.size(rt);
440 auto map = java::LinkedHashMap<jobject, jobject>::create(size);
441
442 for (size_t i = 0; i < size; i++) {
443 auto key = propertyNames.getValueAtIndex(rt, i).getString(rt);
444 auto jsValue = jsObject.getProperty(rt, key);
445
446 auto convertedKey = env->NewStringUTF(key.utf8(rt).c_str());
447
448 // TODO(@lukmccall): pass information to CPP if the underlying type is nullable or not.
449 if (jsValue.isNull() || jsValue.isUndefined()) {
450 map->put(convertedKey, nullptr);
451 continue;
452 }
453
454 auto convertedValue = valueConverter->convert(
455 rt, env, moduleRegistry, jsValue
456 );
457
458 map->put(convertedKey, convertedValue);
459
460 env->DeleteLocalRef(convertedKey);
461 env->DeleteLocalRef(convertedValue);
462 }
463
464 return map.release();
465 }
466
canConvert(jsi::Runtime & rt,const jsi::Value & value) const467 bool MapFrontendConverter::canConvert(
468 jsi::Runtime &rt,
469 const jsi::Value &value
470 ) const {
471 return value.isObject();
472 }
473
convert(jsi::Runtime & rt,JNIEnv * env,JSIInteropModuleRegistry * moduleRegistry,const jsi::Value & value) const474 jobject ViewTagFrontendConverter::convert(jsi::Runtime &rt, JNIEnv *env,
475 JSIInteropModuleRegistry *moduleRegistry,
476 const jsi::Value &value) const {
477 auto nativeTag = value.getObject(rt).getProperty(rt, "nativeTag");
478 if (nativeTag.isNull()) {
479 return nullptr;
480 }
481
482 auto viewTag = (int) nativeTag.getNumber();
483 auto &integerClass = JavaReferencesCache::instance()
484 ->getJClass("java/lang/Integer");
485 jmethodID integerConstructor = integerClass.getMethod("<init>", "(I)V");
486 return env->NewObject(integerClass.clazz, integerConstructor, viewTag);
487 }
488
canConvert(jsi::Runtime & rt,const jsi::Value & value) const489 bool ViewTagFrontendConverter::canConvert(jsi::Runtime &rt, const jsi::Value &value) const {
490 return value.isObject() && value.getObject(rt).hasProperty(rt, "nativeTag");
491 }
492
convert(jsi::Runtime & rt,JNIEnv * env,JSIInteropModuleRegistry * moduleRegistry,const jsi::Value & value) const493 jobject SharedObjectIdConverter::convert(jsi::Runtime &rt, JNIEnv *env,
494 JSIInteropModuleRegistry *moduleRegistry,
495 const jsi::Value &value) const {
496 auto objectId = value.getObject(rt).getProperty(rt, "__expo_shared_object_id__");
497 if (objectId.isNull()) {
498 return nullptr;
499 }
500
501 auto viewTag = (int) objectId.getNumber();
502 auto &integerClass = JavaReferencesCache::instance()
503 ->getJClass("java/lang/Integer");
504 jmethodID integerConstructor = integerClass.getMethod("<init>", "(I)V");
505 return env->NewObject(integerClass.clazz, integerConstructor, viewTag);
506 }
507
canConvert(jsi::Runtime & rt,const jsi::Value & value) const508 bool SharedObjectIdConverter::canConvert(jsi::Runtime &rt, const jsi::Value &value) const {
509 return value.isObject() && value.getObject(rt).hasProperty(rt, "__expo_shared_object_id__");
510 }
511 } // namespace expo
512