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 loadVersionnull37 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 assignnull54 fun assign(obj: Any?) { 55 if (obj != null) { 56 clazz = obj.javaClass 57 } 58 instance = obj 59 } 60 getnull61 fun get(): Any? { 62 return instance 63 } 64 rnClassnull65 fun rnClass(): Class<*>? { 66 return clazz 67 } 68 versionnull69 fun version(): String { 70 return versionForClassname(clazz!!.name) 71 } 72 constructnull73 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 callnull88 fun call(name: String, vararg args: Any?): Any? { 89 return callWithReceiver(instance, false, name, *args) 90 } 91 92 /** Similar to [call] but without capturing reflection [InvocationTargetException] */ callWithThrowablenull93 fun callWithThrowable(name: String, vararg args: Any?): Any? { 94 return callWithReceiver(instance, true, name, *args) 95 } 96 callRecursivenull97 fun callRecursive(name: String, vararg args: Any?): RNObject? { 98 val result = call(name, *args) ?: return null 99 return wrap(result) 100 } 101 callStaticnull102 fun callStatic(name: String, vararg args: Any?): Any? { 103 return callWithReceiver(null, false, name, *args) 104 } 105 callStaticRecursivenull106 fun callStaticRecursive(name: String, vararg args: Any?): RNObject? { 107 val result = callStatic(name, *args) ?: return null 108 return wrap(result) 109 } 110 setFieldnull111 fun setField(name: String, value: Any) { 112 setFieldWithReceiver(instance, name, value) 113 } 114 setStaticFieldnull115 fun setStaticField(name: String, value: Any) { 116 setFieldWithReceiver(null, name, value) 117 } 118 callWithReceivernull119 private fun callWithReceiver(receiver: Any?, rethrow: Boolean, name: String, vararg args: Any?): Any? { 120 try { 121 return getMethodWithArgumentClassTypes(clazz, name, *objectsToJavaClassTypes(*args)).invoke(receiver, *args) 122 } catch (e: IllegalAccessException) { 123 EXL.e(TAG, e) 124 e.printStackTrace() 125 } catch (e: InvocationTargetException) { 126 EXL.e(TAG, e) 127 e.printStackTrace() 128 if (rethrow) { 129 e.cause?.let { 130 throw it 131 } 132 } 133 } catch (e: NoSuchMethodException) { 134 EXL.e(TAG, e) 135 e.printStackTrace() 136 } catch (e: NoSuchMethodError) { 137 EXL.e(TAG, e) 138 e.printStackTrace() 139 } catch (e: Throwable) { 140 EXL.e(TAG, "Runtime exception in RNObject when calling method $name: $e") 141 } 142 return null 143 } 144 setFieldWithReceivernull145 private fun setFieldWithReceiver(receiver: Any?, name: String, value: Any) { 146 try { 147 getFieldWithType(clazz, name, value.javaClass)[receiver] = value 148 } catch (e: IllegalAccessException) { 149 EXL.e(TAG, e) 150 e.printStackTrace() 151 } catch (e: NoSuchFieldException) { 152 EXL.e(TAG, e) 153 e.printStackTrace() 154 } catch (e: NoSuchMethodError) { 155 EXL.e(TAG, e) 156 e.printStackTrace() 157 } catch (e: Throwable) { 158 EXL.e(TAG, "Runtime exception in RNObject when setting field $name: $e") 159 } 160 } 161 onHostResumenull162 fun onHostResume(one: Any?, two: Any?) { 163 call("onHostResume", one, two) 164 } 165 onHostPausenull166 fun onHostPause() { 167 call("onHostPause") 168 } 169 onHostDestroynull170 fun onHostDestroy() { 171 call("onHostDestroy") 172 } 173 174 companion object { 175 private val TAG = RNObject::class.java.simpleName 176 177 const val UNVERSIONED = "UNVERSIONED" 178 wrapnull179 @JvmStatic fun wrap(obj: Any): RNObject { 180 return RNObject(obj) 181 } 182 versionedEnumnull183 fun versionedEnum(sdkVersion: String, className: String, value: String): Any { 184 return try { 185 RNObject(className).loadVersion(sdkVersion).rnClass()!!.getDeclaredField(value)[null] 186 } catch (e: IllegalAccessException) { 187 EXL.e(TAG, e) 188 throw IllegalStateException("Unable to create enum: $className.value", e) 189 } catch (e: NoSuchFieldException) { 190 EXL.e(TAG, e) 191 throw IllegalStateException("Unable to create enum: $className.value", e) 192 } 193 } 194 versionForClassnamenull195 fun versionForClassname(classname: String): String { 196 return if (classname.startsWith("abi")) { 197 val abiVersion = classname.split(".").toTypedArray()[0] 198 abiVersion.substring(3) 199 } else { 200 UNVERSIONED 201 } 202 } 203 removeVersionFromClassnull204 private fun removeVersionFromClass(clazz: Class<*>?): String { 205 val name = clazz!!.name 206 return if (name.startsWith("abi")) { 207 name.substring(name.indexOf('.') + 1) 208 } else name 209 } 210 objectsToJavaClassTypesnull211 private fun objectsToJavaClassTypes(vararg objects: Any?): Array<Class<*>?> { 212 val classes: Array<Class<*>?> = arrayOfNulls(objects.size) 213 for (i in objects.indices) { 214 if (objects[i] != null) { 215 classes[i] = objects[i]!!::class.java 216 } 217 } 218 return classes 219 } 220 221 // Allow types that are too specific so that we don't have to specify exact classes 222 @Throws(NoSuchMethodException::class) getMethodWithArgumentClassTypesnull223 private fun getMethodWithArgumentClassTypes(clazz: Class<*>?, name: String, vararg argumentClassTypes: Class<*>?): Method { 224 val methods = clazz!!.methods 225 for (i in methods.indices) { 226 val method = methods[i] 227 if (method.name != name) { 228 continue 229 } 230 val currentMethodParameterTypes = method.parameterTypes 231 if (currentMethodParameterTypes.size != argumentClassTypes.size) { 232 continue 233 } 234 var isValid = true 235 for (j in currentMethodParameterTypes.indices) { 236 if (!isAssignableFrom(currentMethodParameterTypes[j], argumentClassTypes[j])) { 237 isValid = false 238 break 239 } 240 } 241 if (!isValid) { 242 continue 243 } 244 return method 245 } 246 throw NoSuchMethodException() 247 } 248 249 // Allow boxed -> unboxed assignments isAssignableFromnull250 private fun isAssignableFrom(methodParameterClassType: Class<*>, argumentClassType: Class<*>?): Boolean { 251 if (argumentClassType == null) { 252 // There's not really a good way to handle this. 253 return true 254 } 255 if (methodParameterClassType.isAssignableFrom(argumentClassType)) { 256 return true 257 } 258 if (methodParameterClassType == Boolean::class.javaPrimitiveType && (argumentClassType == java.lang.Boolean::class.java || argumentClassType == Boolean::class.java)) { 259 return true 260 } else if (methodParameterClassType == Byte::class.javaPrimitiveType && (argumentClassType == java.lang.Byte::class.java || argumentClassType == Byte::class.java)) { 261 return true 262 } else if (methodParameterClassType == Char::class.javaPrimitiveType && (argumentClassType == java.lang.Character::class.java || argumentClassType == Char::class.java)) { 263 return true 264 } else if (methodParameterClassType == Float::class.javaPrimitiveType && (argumentClassType == java.lang.Float::class.java || argumentClassType == Float::class.java)) { 265 return true 266 } else if (methodParameterClassType == Int::class.javaPrimitiveType && (argumentClassType == java.lang.Integer::class.java || argumentClassType == Int::class.java)) { 267 return true 268 } else if (methodParameterClassType == Long::class.javaPrimitiveType && (argumentClassType == java.lang.Long::class.java || argumentClassType == Long::class.java)) { 269 return true 270 } else if (methodParameterClassType == Short::class.javaPrimitiveType && (argumentClassType == java.lang.Short::class.java || argumentClassType == Short::class.java)) { 271 return true 272 } else if (methodParameterClassType == Double::class.javaPrimitiveType && (argumentClassType == java.lang.Double::class.java || argumentClassType == Double::class.java)) { 273 return true 274 } 275 return false 276 } 277 278 // Allow types that are too specific so that we don't have to specify exact classes 279 @Throws(NoSuchMethodException::class) getConstructorWithArgumentClassTypesnull280 private fun getConstructorWithArgumentClassTypes(clazz: Class<*>?, vararg argumentClassTypes: Class<*>?): Constructor<*> { 281 val constructors = clazz!!.constructors 282 for (i in constructors.indices) { 283 val constructor = constructors[i] 284 val currentConstructorParameterTypes = constructor.parameterTypes 285 if (currentConstructorParameterTypes.size != argumentClassTypes.size) { 286 continue 287 } 288 var isValid = true 289 for (j in currentConstructorParameterTypes.indices) { 290 if (!isAssignableFrom(currentConstructorParameterTypes[j], argumentClassTypes[j])) { 291 isValid = false 292 break 293 } 294 } 295 if (!isValid) { 296 continue 297 } 298 return constructor 299 } 300 throw NoSuchMethodError() 301 } 302 303 @Throws(NoSuchFieldException::class) getFieldWithTypenull304 private fun getFieldWithType(clazz: Class<*>?, name: String, type: Class<*>): Field { 305 val fields = clazz!!.fields 306 for (i in fields.indices) { 307 val field = fields[i] 308 if (field.name != name) { 309 continue 310 } 311 val currentFieldType = field.type 312 if (isAssignableFrom(currentFieldType, type)) { 313 return field 314 } 315 } 316 throw NoSuchFieldException() 317 } 318 } 319 } 320