Skip to content

Instantly share code, notes, and snippets.

@ntoskrnl
Last active October 7, 2017 19:48
Show Gist options
  • Save ntoskrnl/01570793df6f829d80c3cdd157a6c9ce to your computer and use it in GitHub Desktop.
Save ntoskrnl/01570793df6f829d80c3cdd157a6c9ce to your computer and use it in GitHub Desktop.
Creating a retrofit-like service based on annotations.
fun <T> createJsonRpcService(service: Class<T>, client: JsonRpcClient): T {
val classLoader = service.classLoader
val interfaces = arrayOf<Class<*>>(service)
val invocationHandler = createInvocationHandler(service, client)
@Suppress("UNCHECKED_CAST")
return Proxy.newProxyInstance(classLoader, interfaces, invocationHandler) as T
}
private fun <T, B> createInvocationHandler(service: Class<T>, client: JsonRpcClient<B>): InvocationHandler {
return object : InvocationHandler {
val requestId = AtomicLong(0)
override fun invoke(proxy: Any, method: Method, args: Array<Any?>?): Any {
val methodAnnotation =
method.getAnnotation(JsonRpc::class.java) ?: return method.invoke(this, args)
if (!method.returnsSingle) {
error("Only io.reactivex.Single<T> is supported as return type")
}
val id = requestId.incrementAndGet()
val methodName = methodAnnotation.value
val parameters = method.jsonRpcParameters(args, service)
val request = JsonRpcRequest(id, methodName, parameters)
val returnType = method.resultGenericTypeArgument
return client.call<Any>(request, returnType)
}
}
}
private fun Method.jsonRpcParameters(args: Array<Any?>?, service: Class<*>): Map<String, Any?> {
return parameterAnnotations
.map { it?.firstOrNull { JsonRpcParam::class.java.isInstance(it) } }
.mapIndexed { i, a ->
when (a) {
is JsonRpc -> a.value
else -> error("Argument #$i of ${service.name}#$name()" +
" must be annotated with @${JsonRpc::class.java.simpleName}")
}
}
.mapIndexed { i, name -> name to args?.get(i) }
.associate { it }
}
private val Method.returnsSingle: Boolean
get() = returnType.canonicalName == Single::class.java.canonicalName
private val Method.resultGenericTypeArgument: Type
@Suppress("CAST_NEVER_SUCCEEDS")
get() = (this.genericReturnType as ParameterizedType).actualTypeArguments.first()
private class NullJsonRpcCallResultException : Exception()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment