1 // Copyright 2015-present 650 Industries. All rights reserved. 2 package host.exp.exponent 3 4 import host.exp.exponent.analytics.EXL 5 import host.exp.expoview.BuildConfig 6 import java.lang.reflect.Constructor 7 import java.lang.reflect.Field 8 import java.lang.reflect.InvocationTargetException 9 import java.lang.reflect.Method 10 11 // TODO: add type checking in DEBUG 12 class RNObject { 13 private val className: String // Unversioned 14 private var clazz: Class<*>? = null // Versioned 15 private var instance: Any? = null // Versioned 16 17 // We ignore the version of clazz 18 constructor(clazz: Class<*>?) { 19 className = removeVersionFromClass(clazz) 20 } 21 22 constructor(className: String) { 23 this.className = className 24 } 25 26 private constructor(obj: Any?) { 27 assign(obj) 28 className = removeVersionFromClass(clazz) 29 } 30 31 val isNull: Boolean 32 get() = instance == null 33 val isNotNull: Boolean 34 get() = instance != null 35 36 // required for "unversioned" flavor check 37 fun loadVersion(version: String): RNObject { 38 try { 39 clazz = if (version == UNVERSIONED || BuildConfig.FLAVOR == "unversioned") { 40 if (className.startsWith("host.exp.exponent")) { 41 Class.forName("versioned.$className") 42 } else { 43 Class.forName(className) 44 } 45 } else { 46 Class.forName("abi${version.replace('.', '_')}.$className") 47 } 48 } catch (e: ClassNotFoundException) { 49 EXL.e(TAG, e) 50 } 51 return this 52 } 53 54 fun assign(obj: Any?) { 55 if (obj != null) { 56 clazz = obj.javaClass 57 } 58 instance = obj 59 } 60 61 fun get(): Any? { 62 return instance 63 } 64 65 fun rnClass(): Class<*>? { 66 return clazz 67 } 68 69 fun version(): String { 70 return versionForClassname(clazz!!.name) 71 } 72 73 fun construct(vararg args: Any?): RNObject { 74 try { 75 instance = getConstructorWithArgumentClassTypes(clazz, *objectsToJavaClassTypes(*args)).newInstance(*args) 76 } catch (e: NoSuchMethodException) { 77 EXL.e(TAG, e) 78 } catch (e: InvocationTargetException) { 79 EXL.e(TAG, e) 80 } catch (e: InstantiationException) { 81 EXL.e(TAG, e) 82 } catch (e: IllegalAccessException) { 83 EXL.e(TAG, e) 84 } 85 return this 86 } 87 88 fun call(name: String, vararg args: Any?): Any? { 89 return callWithReceiver(instance, name, *args) 90 } 91 92 fun callRecursive(name: String, vararg args: Any?): RNObject? { 93 val result = call(name, *args) ?: return null 94 return wrap(result) 95 } 96 97 fun callStatic(name: String, vararg args: Any?): Any? { 98 return callWithReceiver(null, name, *args) 99 } 100 101 fun callStaticRecursive(name: String, vararg args: Any?): RNObject? { 102 val result = callStatic(name, *args) ?: return null 103 return wrap(result) 104 } 105 106 fun setField(name: String, value: Any) { 107 setFieldWithReceiver(instance, name, value) 108 } 109 110 fun setStaticField(name: String, value: Any) { 111 setFieldWithReceiver(null, name, value) 112 } 113 114 private fun callWithReceiver(receiver: Any?, name: String, vararg args: Any?): Any? { 115 try { 116 return getMethodWithArgumentClassTypes(clazz, name, *objectsToJavaClassTypes(*args)).invoke(receiver, *args) 117 } catch (e: IllegalAccessException) { 118 EXL.e(TAG, e) 119 e.printStackTrace() 120 } catch (e: InvocationTargetException) { 121 EXL.e(TAG, e) 122 e.printStackTrace() 123 } catch (e: NoSuchMethodException) { 124 EXL.e(TAG, e) 125 e.printStackTrace() 126 } catch (e: NoSuchMethodError) { 127 EXL.e(TAG, e) 128 e.printStackTrace() 129 } catch (e: Throwable) { 130 EXL.e(TAG, "Runtime exception in RNObject when calling method $name: $e") 131 } 132 return null 133 } 134 135 private fun setFieldWithReceiver(receiver: Any?, name: String, value: Any) { 136 try { 137 getFieldWithType(clazz, name, value.javaClass)[receiver] = value 138 } catch (e: IllegalAccessException) { 139 EXL.e(TAG, e) 140 e.printStackTrace() 141 } catch (e: NoSuchFieldException) { 142 EXL.e(TAG, e) 143 e.printStackTrace() 144 } catch (e: NoSuchMethodError) { 145 EXL.e(TAG, e) 146 e.printStackTrace() 147 } catch (e: Throwable) { 148 EXL.e(TAG, "Runtime exception in RNObject when setting field $name: $e") 149 } 150 } 151 152 fun onHostResume(one: Any?, two: Any?) { 153 call("onHostResume", one, two) 154 } 155 156 fun onHostPause() { 157 call("onHostPause") 158 } 159 160 fun onHostDestroy() { 161 call("onHostDestroy") 162 } 163 164 companion object { 165 private val TAG = RNObject::class.java.simpleName 166 167 const val UNVERSIONED = "UNVERSIONED" 168 169 @JvmStatic fun wrap(obj: Any): RNObject { 170 return RNObject(obj) 171 } 172 173 fun versionedEnum(sdkVersion: String, className: String, value: String): Any { 174 return try { 175 RNObject(className).loadVersion(sdkVersion).rnClass()!!.getDeclaredField(value)[null] 176 } catch (e: IllegalAccessException) { 177 EXL.e(TAG, e) 178 throw IllegalStateException("Unable to create enum: $className.value", e) 179 } catch (e: NoSuchFieldException) { 180 EXL.e(TAG, e) 181 throw IllegalStateException("Unable to create enum: $className.value", e) 182 } 183 } 184 185 fun versionForClassname(classname: String): String { 186 return if (classname.startsWith("abi")) { 187 val abiVersion = classname.split(".").toTypedArray()[0] 188 abiVersion.substring(3) 189 } else { 190 UNVERSIONED 191 } 192 } 193 194 private fun removeVersionFromClass(clazz: Class<*>?): String { 195 val name = clazz!!.name 196 return if (name.startsWith("abi")) { 197 name.substring(name.indexOf('.') + 1) 198 } else name 199 } 200 201 private fun objectsToJavaClassTypes(vararg objects: Any?): Array<Class<*>?> { 202 val classes: Array<Class<*>?> = arrayOfNulls(objects.size) 203 for (i in objects.indices) { 204 if (objects[i] != null) { 205 classes[i] = objects[i]!!::class.java 206 } 207 } 208 return classes 209 } 210 211 // Allow types that are too specific so that we don't have to specify exact classes 212 @Throws(NoSuchMethodException::class) 213 private fun getMethodWithArgumentClassTypes(clazz: Class<*>?, name: String, vararg argumentClassTypes: Class<*>?): Method { 214 val methods = clazz!!.methods 215 for (i in methods.indices) { 216 val method = methods[i] 217 if (method.name != name) { 218 continue 219 } 220 val currentMethodParameterTypes = method.parameterTypes 221 if (currentMethodParameterTypes.size != argumentClassTypes.size) { 222 continue 223 } 224 var isValid = true 225 for (j in currentMethodParameterTypes.indices) { 226 if (!isAssignableFrom(currentMethodParameterTypes[j], argumentClassTypes[j])) { 227 isValid = false 228 break 229 } 230 } 231 if (!isValid) { 232 continue 233 } 234 return method 235 } 236 throw NoSuchMethodException() 237 } 238 239 // Allow boxed -> unboxed assignments 240 private fun isAssignableFrom(methodParameterClassType: Class<*>, argumentClassType: Class<*>?): Boolean { 241 if (argumentClassType == null) { 242 // There's not really a good way to handle this. 243 return true 244 } 245 if (methodParameterClassType.isAssignableFrom(argumentClassType)) { 246 return true 247 } 248 if (methodParameterClassType == Boolean::class.javaPrimitiveType && (argumentClassType == java.lang.Boolean::class.java || argumentClassType == Boolean::class.java)) { 249 return true 250 } else if (methodParameterClassType == Byte::class.javaPrimitiveType && (argumentClassType == java.lang.Byte::class.java || argumentClassType == Byte::class.java)) { 251 return true 252 } else if (methodParameterClassType == Char::class.javaPrimitiveType && (argumentClassType == java.lang.Character::class.java || argumentClassType == Char::class.java)) { 253 return true 254 } else if (methodParameterClassType == Float::class.javaPrimitiveType && (argumentClassType == java.lang.Float::class.java || argumentClassType == Float::class.java)) { 255 return true 256 } else if (methodParameterClassType == Int::class.javaPrimitiveType && (argumentClassType == java.lang.Integer::class.java || argumentClassType == Int::class.java)) { 257 return true 258 } else if (methodParameterClassType == Long::class.javaPrimitiveType && (argumentClassType == java.lang.Long::class.java || argumentClassType == Long::class.java)) { 259 return true 260 } else if (methodParameterClassType == Short::class.javaPrimitiveType && (argumentClassType == java.lang.Short::class.java || argumentClassType == Short::class.java)) { 261 return true 262 } else if (methodParameterClassType == Double::class.javaPrimitiveType && (argumentClassType == java.lang.Double::class.java || argumentClassType == Double::class.java)) { 263 return true 264 } 265 return false 266 } 267 268 // Allow types that are too specific so that we don't have to specify exact classes 269 @Throws(NoSuchMethodException::class) 270 private fun getConstructorWithArgumentClassTypes(clazz: Class<*>?, vararg argumentClassTypes: Class<*>?): Constructor<*> { 271 val constructors = clazz!!.constructors 272 for (i in constructors.indices) { 273 val constructor = constructors[i] 274 val currentConstructorParameterTypes = constructor.parameterTypes 275 if (currentConstructorParameterTypes.size != argumentClassTypes.size) { 276 continue 277 } 278 var isValid = true 279 for (j in currentConstructorParameterTypes.indices) { 280 if (!isAssignableFrom(currentConstructorParameterTypes[j], argumentClassTypes[j])) { 281 isValid = false 282 break 283 } 284 } 285 if (!isValid) { 286 continue 287 } 288 return constructor 289 } 290 throw NoSuchMethodError() 291 } 292 293 @Throws(NoSuchFieldException::class) 294 private fun getFieldWithType(clazz: Class<*>?, name: String, type: Class<*>): Field { 295 val fields = clazz!!.fields 296 for (i in fields.indices) { 297 val field = fields[i] 298 if (field.name != name) { 299 continue 300 } 301 val currentFieldType = field.type 302 if (isAssignableFrom(currentFieldType, type)) { 303 return field 304 } 305 } 306 throw NoSuchFieldException() 307 } 308 } 309 } 310