custom objects instead of dependencies

This commit is contained in:
InsanusMokrassar 2019-11-28 11:30:48 +06:00
parent 41eb2341f7
commit dc71996546
5 changed files with 131 additions and 17 deletions

6
CHANGELOG.md Normal file
View File

@ -0,0 +1,6 @@
# SDI changelogs
## 0.1.1
* Added opportunity to create objects inside of config:
* Now it is possible to construct object without usage of dependency on `@ContextualSerialization` place

View File

@ -4,7 +4,7 @@ kotlin_serialisation_runtime_version=0.14.0
gradle_bintray_plugin_version=1.8.4 gradle_bintray_plugin_version=1.8.4
project_public_version=0.1.0 project_public_version=0.1.1
project_public_group=com.insanusmokrassar project_public_group=com.insanusmokrassar
project_public_description=Simple DI library project_public_description=Simple DI library

View File

@ -1,31 +1,32 @@
package com.insanusmokrassar.sdi.utils package com.insanusmokrassar.sdi.utils
import kotlinx.serialization.* import kotlinx.serialization.*
import kotlinx.serialization.internal.StringDescriptor import kotlinx.serialization.json.*
import kotlinx.serialization.modules.SerializerAlreadyRegisteredException import kotlinx.serialization.modules.*
import kotlinx.serialization.modules.SerializersModuleBuilder
import kotlin.reflect.KClass import kotlin.reflect.KClass
@ImplicitReflectionSerializer @ImplicitReflectionSerializer
internal class DependencyResolver<T : Any>( internal class DependencyResolver<T : Any>(
serialModuleBuilder: SerializersModuleBuilder, serialModuleBuilder: SerializersModuleBuilder,
kClass: KClass<T>, kClass: KClass<T>,
private val formatterGetter: () -> Json,
private val dependencyGetter: (String) -> Any private val dependencyGetter: (String) -> Any
) : KSerializer<T> { ) : KSerializer<T> {
private val originalSerializer: KSerializer<T>? = try { private val originalSerializer: KSerializer<T> = try {
resolveSerializerByKClass(kClass) resolveSerializerByKClass(kClass)
} catch (e: Exception) { } catch (e: Exception) {
null PolymorphicSerializer(kClass)
} }
private val objectsCache = mutableMapOf<String, T>() private val objectsCache = mutableMapOf<String, T>()
override val descriptor: SerialDescriptor = originalSerializer ?.descriptor ?: StringDescriptor.withName("DependencyResolver") override val descriptor: SerialDescriptor = originalSerializer.descriptor
init { init {
serialModuleBuilder.apply { serialModuleBuilder.apply {
contextual(kClass, this@DependencyResolver) contextual(kClass, this@DependencyResolver)
kClass.allSubclasses.forEach { kClass -> polymorphic(kClass, originalSerializer)
kClass.allSubclasses.forEach { currentKClass ->
try { try {
DependencyResolver(serialModuleBuilder, kClass, dependencyGetter) DependencyResolver(serialModuleBuilder, currentKClass, formatterGetter, dependencyGetter)
} catch (e: SerializerAlreadyRegisteredException) { } catch (e: SerializerAlreadyRegisteredException) {
// ok // ok
} }
@ -34,13 +35,18 @@ internal class DependencyResolver<T : Any>(
} }
override fun deserialize(decoder: Decoder): T { override fun deserialize(decoder: Decoder): T {
return try { val decoded = decoder.decodeSerializableValue(JsonElementSerializer)
val dependencyName = decoder.decodeString() return when {
(dependencyGetter(dependencyName) as T).also { decoded is JsonPrimitive && decoded.contentOrNull != null -> decoded.content.let { dependencyName ->
objectsCache[dependencyName] = it (dependencyGetter(dependencyName) as T).also {
objectsCache[dependencyName] = it
}
} }
} catch (e: Exception) { decoded is JsonArray -> {
originalSerializer ?.deserialize(decoder) ?: throw IllegalStateException("Can't resolve dependency", e) val serializer = resolveSerializerByPackageName(decoded.getPrimitive(0).content)
formatterGetter().fromJson(serializer, decoded[1]) as T
}
else -> formatterGetter().fromJson(originalSerializer, decoded)
} }
} }
@ -49,6 +55,6 @@ internal class DependencyResolver<T : Any>(
objectsCache[it] === obj objectsCache[it] === obj
} ?.also { dependencyName -> } ?.also { dependencyName ->
encoder.encodeString(dependencyName) encoder.encodeString(dependencyName)
} ?: originalSerializer ?.serialize(encoder, obj) ?: throw IllegalStateException("Can't resolve dependency") } ?: originalSerializer.serialize(encoder, obj)
} }
} }

View File

@ -64,12 +64,17 @@ internal fun createModuleBasedOnConfigRoot(jsonObject: JsonObject): Json {
}.toMap() }.toMap()
return Json( return Json(
configuration = JsonConfiguration(useArrayPolymorphism = true),
context = SerializersModule { context = SerializersModule {
keysToPackages.values.forEach { keysToPackages.values.forEach {
val kclass = resolveKClassByPackageName(it) val kclass = resolveKClassByPackageName(it)
try { try {
DependencyResolver(this, kclass) { DependencyResolver(
this,
kclass,
{ jsonStringFormat }
) {
caches.getValue(it).invoke() caches.getValue(it).invoke()
} }
} catch (e: SerializerAlreadyRegisteredException) { } catch (e: SerializerAlreadyRegisteredException) {

View File

@ -0,0 +1,97 @@
package com.insanusmokrassar.sdi
import kotlinx.serialization.*
import kotlinx.serialization.json.Json
import kotlin.test.*
interface SimpleCustomObject_ControllerAPI {
fun showUp()
}
interface SimpleCustomObject_ServiceAPI {
val names: List<String>
}
@Serializable
class SimpleCustomObject_Controller(@ContextualSerialization val service: SimpleCustomObject_ServiceAPI) : SimpleCustomObject_ControllerAPI {
override fun showUp() {
println("Inited with name \"${service.names}\"")
}
}
@Serializable
class SimpleCustomObject_CustomController1(@ContextualSerialization val service: SimpleCustomObject_ServiceAPI) : SimpleCustomObject_ControllerAPI {
override fun showUp() {
println("Inited with name \"${service.names}\"")
}
}
@Serializable
class SimpleCustomObject_CustomController2(@ContextualSerialization val service: SimpleCustomObject_BusinessService) : SimpleCustomObject_ControllerAPI {
override fun showUp() {
println("Inited with name \"${service.names}\"")
}
}
@Serializable
class SimpleCustomObject_BusinessService(override val names: List<String>) : SimpleCustomObject_ServiceAPI
@ImplicitReflectionSerializer
class SimpleCustomObjectTest {
@Test
fun test_that_simple_config_correctly_work() {
val names = arrayOf("nameOne", "nameTwo")
val customNames = arrayOf("customNameOne", "customNameTwo")
val controllerName = "controller"
val customController1Name = "controller1"
val customController2Name = "controller2"
val input = """
{
"service": [
"${SimpleCustomObject_BusinessService::class.qualifiedName}",
{
"names": ${names.joinToString(prefix = "[", postfix = "]") { "\"$it\"" }}
}
],
"$controllerName": [
"${SimpleCustomObject_Controller::class.qualifiedName}",
{
"service": "service"
}
],
"$customController1Name": [
"${SimpleCustomObject_CustomController1::class.qualifiedName}",
{
"service": [
"${SimpleCustomObject_BusinessService::class.qualifiedName}",
{
"names": ${customNames.joinToString(prefix = "[", postfix = "]") { "\"$it\"" }}
}
]
}
]
"$customController2Name": [
"${SimpleCustomObject_CustomController2::class.qualifiedName}",
{
"service": {
"names": "${customNames.joinToString(prefix = "[", postfix = "]") { "\"$it\"" }}
}
}
]
}
""".trimIndent()
val module = Json.plain.parse(Module.serializer(), input)
(module[controllerName] as SimpleCustomObject_ControllerAPI)
val controller = (module["controller"] as SimpleCustomObject_Controller)
assertEquals(names.toList(), controller.service.names)
(module[customController1Name] as SimpleCustomObject_ControllerAPI)
val customController1 = (module[customController1Name] as SimpleCustomObject_CustomController1)
assertEquals(customNames.toList(), customController1.service.names)
(module[customController2Name] as SimpleCustomObject_ControllerAPI)
val customController2 = (module[customController2Name] as SimpleCustomObject_CustomController2)
assertEquals(customNames.toList(), customController2.service.names)
}
}