Last active
November 13, 2019 14:20
-
-
Save resengupta/c22e0096b171d99c95000605f8b782d1 to your computer and use it in GitHub Desktop.
Example on how to use dynamic proxy design pattern. Example uses Duktape client to create a generic client provider implementation, where we can create our own interface class by using - getImplementation(clazz: Class<T>), which return Interface Instance where the methods can return Object instead of String, as Duktape default return type is Str…
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
@kotlin.annotation.Target(AnnotationTarget.VALUE_PARAMETER) | |
@kotlin.annotation.Retention(AnnotationRetention.RUNTIME) | |
annotation class Input(val value: String) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
interface JsDemoEndpoint { | |
fun getAggregatedData(dataList: List<Data>?): DataView? | |
fun getAggregatedDataByMultipleInput(@Input("first") dataList: List<Data>?, | |
@Input("second") data: Data?): DataView? | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import android.content.Context | |
import android.util.Log | |
import com.google.gson.Gson | |
import com.myapps.jsdemo.BuildConfig | |
import com.myapps.jsdemo.extensions.log | |
import com.squareup.duktape.Duktape | |
import java.lang.reflect.Proxy | |
import java.lang.reflect.Type | |
class JsEngineProvider(private val context: Context, | |
private val gson: Gson) { | |
// Synchronized | |
private val duktape by lazy { Duktape.create() } | |
private lateinit var jsBinder: JsBinder | |
fun evaluate(jsFile: String, jsScope: String) { | |
val script = context.assets.open(jsFile).bufferedReader().use { it.readText() } | |
duktape.evaluate(script) | |
jsBinder = duktape.get(jsScope, JsBinder::class.java) | |
} | |
fun close() { | |
duktape.close() | |
} | |
/** | |
* This method get the endpoint, and does the default implementation where it needs to call | |
* the correct method with appropriate input. As of now we send only 1 input via JS engine. | |
* | |
* If return type is list, then extra [Type] parameter is required in the end of all parameters. | |
*/ | |
fun <T> getImplementation(clazz: Class<T>): T = Proxy.newProxyInstance( | |
clazz.classLoader, | |
arrayOf<Class<*>>(clazz) | |
) { _, method, args -> | |
// args can be null when no param is passed | |
val type: Type? = args?.find { it is Type } as? Type // Type should always be the last parameter | |
val parameterAnnotationsArray: Array<Array<Annotation>> = method.parameterAnnotations | |
val input: Any? = if (parameterAnnotationsArray.isEmpty() || parameterAnnotationsArray.all { it.isEmpty() }) { | |
args?.firstOrNull() | |
} else { | |
parameterAnnotationsArray.mapIndexedNotNull { index, arrayOfAnnotations -> | |
val key = (arrayOfAnnotations.firstOrNull() as? Input)?.value | |
key?.let { Pair(key, args?.getOrNull(index)) } | |
}.associate { it.first to it.second } | |
} | |
if (type != null) { | |
get(method.name, input, type) | |
} else { | |
get(method.name, input, method.returnType) | |
} | |
} as T | |
private fun <T> get(functionName: String, input: Any?, returnType: Class<T>): T? { | |
return try { | |
if (BuildConfig.DEBUG) log("TAG", "JS::: $functionName --> " + gson.toJson(input)) | |
val results: String? = jsBinder.androidWrapper(functionName, gson.toJson(input)) | |
if (BuildConfig.DEBUG) log("TAG", "JS::: $functionName <-- $results") | |
gson.fromJson(results, returnType) | |
} catch (e: Exception) { | |
if (BuildConfig.DEBUG) Log.e("TAG", "JS::: $functionName --> ", e) | |
null | |
} | |
} | |
private fun <T> get(functionName: String, input: Any?, typeToken: Type): T? { | |
return try { | |
if (BuildConfig.DEBUG) log("TAG", "JS::: $functionName --> " + gson.toJson(input)) | |
val results: String? = jsBinder.androidWrapper(functionName, gson.toJson(input)) | |
if (BuildConfig.DEBUG) log("TAG", "JS::: $functionName <-- $results") | |
gson.fromJson(results, typeToken) | |
} catch (e: Exception) { | |
if (BuildConfig.DEBUG) Log.e("TAG", "JS::: $functionName --> ", e) | |
null | |
} | |
} | |
interface JsBinder { | |
fun androidWrapper(functionName: String, input: String): String | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment