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