Merge pull request #8 from InsanusMokrassar/0.4.0

0.4.0
This commit is contained in:
InsanusMokrassar 2021-02-19 19:34:14 +06:00 committed by GitHub
commit 99965b3bbb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 205 additions and 287 deletions

View File

@ -1,5 +1,10 @@
# SDI changelogs
## 0.4.0
* Fix of [#6](https://github.com/InsanusMokrassar/SDI/issues/6)
* Fix of [#7](https://github.com/InsanusMokrassar/SDI/issues/7)
## 0.4.0-rc2
* `Kotlin`: `1.4.21` -> `1.4.30`

View File

@ -9,4 +9,4 @@ klassindex_version=4.1.0-rc.1
github_release_plugin_version=2.2.12
group=dev.inmo
version=0.4.0-rc2
version=0.4.0

View File

@ -1 +1 @@
{"bintrayConfig":{"repo":"InsanusMokrassar","packageName":"${project.name}","packageVcs":"https://git.insanusmokrassar.com/InsanusMokrassar/SDI.git","autoPublish":true,"overridePublish":true},"licenses":[{"id":"Apache-2.0","title":"Apache Software License 2.0","url":"https://git.insanusmokrassar.com/PostsSystem/SDI/src/master/LICENSE"}],"mavenConfig":{"name":"Simple Dependency Injector","description":"Simple library for creating dependencies tree using Kotlin Serialization","url":"https://git.insanusmokrassar.com/InsanusMokrassar/SDI","vcsUrl":"https://git.insanusmokrassar.com/InsanusMokrassar/SDI.git","includeGpgSigning":true,"publishToMavenCentral":true,"developers":[{"id":"InsanusMokrassar","name":"Ovsiannikov Aleksei","eMail":"ovsyannikov.alexey95@gmail.com"}]}}
{"licenses":[{"id":"Apache-2.0","title":"Apache Software License 2.0","url":"https://git.insanusmokrassar.com/PostsSystem/SDI/src/master/LICENSE"}],"mavenConfig":{"name":"Simple Dependency Injector","description":"Simple library for creating dependencies tree using Kotlin Serialization","url":"https://git.insanusmokrassar.com/InsanusMokrassar/SDI","vcsUrl":"https://git.insanusmokrassar.com/InsanusMokrassar/SDI.git","includeGpgSigning":true,"publishToMavenCentral":true,"developers":[{"id":"InsanusMokrassar","name":"Ovsiannikov Aleksei","eMail":"ovsyannikov.alexey95@gmail.com"}]}}

View File

@ -38,17 +38,7 @@ publishing {
}
}
repositories {
maven {
name = "bintray"
url = uri("https://api.bintray.com/maven/${project.hasProperty('BINTRAY_USER') ? project.property('BINTRAY_USER') : System.getenv('BINTRAY_USER')}/InsanusMokrassar/${project.name}/;publish=1;override=1")
credentials {
username = project.hasProperty('BINTRAY_USER') ? project.property('BINTRAY_USER') : System.getenv('BINTRAY_USER')
password = project.hasProperty('BINTRAY_KEY') ? project.property('BINTRAY_KEY') : System.getenv('BINTRAY_KEY')
}
}
maven {
name = "sonatype"
url = uri("https://oss.sonatype.org/service/local/staging/deploy/maven2/")
@ -57,9 +47,7 @@ publishing {
password = project.hasProperty('SONATYPE_PASSWORD') ? project.property('SONATYPE_PASSWORD') : System.getenv('SONATYPE_PASSWORD')
}
}
}
}
}

View File

@ -21,9 +21,9 @@ fun Json.loadModule(
moduleBuilder: (SerializersModuleBuilder.() -> Unit)? = null
): Module = decodeFromString(
if (moduleBuilder != null) {
ModuleDeserializerStrategy(moduleBuilder, *additionalClassesToInclude)
ModuleSerializer(moduleBuilder, *additionalClassesToInclude)
} else {
ModuleDeserializerStrategy(additionalClassesToInclude = *additionalClassesToInclude)
ModuleSerializer(additionalClassesToInclude = *additionalClassesToInclude)
},
json
)

View File

@ -3,5 +3,8 @@ package dev.inmo.sdi
import kotlinx.serialization.Contextual
import kotlinx.serialization.Serializable
@Serializable(ModuleFullSerializer::class)
class Module internal constructor(base: Map<String, @Contextual Any>) : Map<String, Any> by base
@Serializable(ModuleSerializer::class)
class Module internal constructor(
base: Map<String, @Contextual Any>,
internal val serialContext: SerializationContext
) : Map<String, Any> by base

View File

@ -1,50 +0,0 @@
package dev.inmo.sdi
import dev.inmo.sdi.utils.createModuleBasedOnConfigRoot
import kotlinx.serialization.*
import kotlinx.serialization.builtins.MapSerializer
import kotlinx.serialization.builtins.serializer
import kotlinx.serialization.descriptors.SerialDescriptor
import kotlinx.serialization.encoding.Decoder
import kotlinx.serialization.encoding.Encoder
import kotlinx.serialization.json.*
import kotlinx.serialization.modules.SerializersModuleBuilder
import kotlin.reflect.KClass
internal class ModuleDeserializerStrategy(
private val moduleBuilder: (SerializersModuleBuilder.() -> Unit)? = null,
private vararg val additionalClassesToInclude: KClass<*>
) : DeserializationStrategy<Module> {
constructor() : this(null)
private val internalSerializer = MapSerializer(String.serializer(), ContextualSerializer(Any::class))
override val descriptor: SerialDescriptor
get() = internalSerializer.descriptor
@InternalSerializationApi
override fun deserialize(decoder: Decoder): Module {
val json = JsonObject.serializer().deserialize(decoder)
val jsonSerialFormat = createModuleBasedOnConfigRoot(
json,
moduleBuilder,
decoder.serializersModule,
*additionalClassesToInclude
)
val resultJson = JsonObject(
json.keys.associateWith { JsonPrimitive(it) }
)
val map = jsonSerialFormat.decodeFromJsonElement(internalSerializer, resultJson)
return Module(map)
}
}
@Serializer(Module::class)
internal class ModuleFullSerializer(
private val moduleBuilder: (SerializersModuleBuilder.() -> Unit)? = null,
private vararg val additionalClassesToInclude: KClass<*>
) : KSerializer<Module>,
DeserializationStrategy<Module> by ModuleDeserializerStrategy(moduleBuilder, *additionalClassesToInclude) {
constructor() : this(null)
override fun serialize(encoder: Encoder, value: Module) = throw NotImplementedError("Currently there is no support for serialization of modules")
}

View File

@ -0,0 +1,156 @@
package dev.inmo.sdi
import dev.inmo.sdi.utils.*
import dev.inmo.sdi.utils.resolveKClassByPackageName
import kotlinx.serialization.*
import kotlinx.serialization.descriptors.SerialDescriptor
import kotlinx.serialization.encoding.Decoder
import kotlinx.serialization.encoding.Encoder
import kotlinx.serialization.json.*
import kotlinx.serialization.modules.*
import kotlin.reflect.KClass
@Suppress("UNCHECKED_CAST")
@InternalSerializationApi
private fun <T : Any> T.serialize(encoder: Encoder) = (
this::class.serializer() as KSerializer<T>
).serialize(encoder, this)
private fun JsonElement.detectType(valueKey: String) = when (this) {
is JsonObject -> this["type"] ?.jsonPrimitive ?.contentOrNull
is JsonPrimitive -> contentOrNull
is JsonArray -> getOrNull(0) ?.jsonPrimitive ?.contentOrNull
} ?: valueKey
@InternalSerializationApi
private data class TypeSerializer<T : Any>(
val serializersModule: SerializersModuleBuilder,
private val kClass: KClass<T>,
private val otherDependencyResolver: (String) -> Any?
) : KSerializer<T> {
private val deserializedByLink = mutableMapOf<T, String>()
private val jsonElementSerializer = JsonElement.serializer()
@InternalSerializationApi
private val originalSerializer = kClass.serializer()
override val descriptor: SerialDescriptor
get() = jsonElementSerializer.descriptor
init {
fun <T : Any> KClass<T>.contextual() {
serializersModule.optionalContextual(this, this@TypeSerializer as KSerializer<T>)
}
serializersModule.contextual(kClass, this)
kClass.superclasses.forEach {
it.contextual()
}
}
@InternalSerializationApi
override fun deserialize(decoder: Decoder): T {
return when (val element = jsonElementSerializer.deserialize(decoder)) {
is JsonPrimitive -> (otherDependencyResolver(element.content) as T).also {
deserializedByLink[it] = element.content
}
else -> ((decoder as? JsonDecoder) ?.json ?: nonStrictJson).decodeFromJsonElement(
originalSerializer,
when (element) {
is JsonArray -> element[1].jsonObject
else -> element
}
)
}
}
@InternalSerializationApi
override fun serialize(encoder: Encoder, value: T) {
deserializedByLink[value] ?.also {
encoder.encodeSerializableValue(JsonPrimitive.serializer(), JsonPrimitive(it))
} ?: value.serialize(encoder)
}
@InternalSerializationApi
fun forceSerialization(json: Json, value: T) = json.encodeToJsonElement(originalSerializer, value).let { encoded ->
when (encoded) {
is JsonObject -> JsonObject(
encoded + ("type" to JsonPrimitive(value::class.qualifiedName))
)
is JsonArray -> JsonArray(
listOf(JsonPrimitive(value::class.qualifiedName)) + encoded
)
else -> encoded
}
}
}
internal data class SerializationContext(
val json: Json,
val keysSerializers: Map<String, KSerializer<*>>
) {
@InternalSerializationApi
fun <T : Any> serialize(key: String, value: T) = (keysSerializers.getValue(key) as TypeSerializer<T>).let {
it.forceSerialization(json, value)
}
}
@Serializer(Module::class)
class ModuleSerializer(
private val moduleBuilder: (SerializersModuleBuilder.() -> Unit)? = null,
private vararg val additionalClassesToInclude: KClass<*>
) : KSerializer<Module> {
private val jsonObjectSerializer = JsonObject.serializer()
override val descriptor: SerialDescriptor = jsonObjectSerializer.descriptor
@InternalSerializationApi
override fun deserialize(decoder: Decoder): Module {
val json = jsonObjectSerializer.deserialize(decoder)
lateinit var newFormat: Json
val cacheMap = mutableMapOf<String, Any>()
val serializers = mutableMapOf<String, KSerializer<*>>()
val dependencyResolver: (String) -> Any? = {
cacheMap[it] ?: newFormat.decodeFromJsonElement(
serializers.getValue(it),
json.getValue(it)
)
}
val newSerializersModule = decoder.serializersModule.overwriteWith(
SerializersModule {
moduleBuilder ?.invoke(this)
json.forEach { (key, value) ->
val kclass = resolveKClassByPackageName(value.detectType(key))
serializers[key] = TypeSerializer(this, kclass, dependencyResolver)
}
additionalClassesToInclude.forEach {
TypeSerializer(this, it, dependencyResolver)
}
}
)
newFormat = Json((decoder as? JsonDecoder) ?.json ?: nonStrictJson) {
serializersModule = newSerializersModule
}
return Module(
json.mapNotNull { (key) ->
key to (dependencyResolver(key) ?: return@mapNotNull null)
}.toMap(),
SerializationContext(newFormat, serializers.toMap())
)
}
@InternalSerializationApi
override fun serialize(encoder: Encoder, value: Module) {
val serialContext = value.serialContext
jsonObjectSerializer.serialize(
encoder,
JsonObject(
value.map { (key, data) ->
key to serialContext.serialize(key, data)
}.toMap()
)
)
}
}
val DefaultModuleSerializer = ModuleSerializer()

View File

@ -1,69 +0,0 @@
package dev.inmo.sdi.utils
import kotlinx.serialization.*
import kotlinx.serialization.descriptors.SerialDescriptor
import kotlinx.serialization.encoding.Decoder
import kotlinx.serialization.encoding.Encoder
import kotlinx.serialization.json.*
import kotlinx.serialization.modules.SerializersModuleBuilder
import kotlin.reflect.KClass
internal object AlreadyRegisteredException : Exception()
internal class DependencyResolver<T : Any>(
serialModuleBuilder: SerializersModuleBuilder,
kClass: KClass<T>,
private val formatterGetter: () -> Json,
private val dependencyGetter: (String) -> Any
) : KSerializer<T> {
@InternalSerializationApi
private val originalSerializer: KSerializer<T> = kClass.serializerOrNull() ?: ContextualSerializer(kClass)
private val objectsCache = mutableMapOf<String, T>()
@InternalSerializationApi
override val descriptor: SerialDescriptor = originalSerializer.descriptor
init {
serialModuleBuilder.apply {
try {
contextual(kClass, this@DependencyResolver)
} catch (e: IllegalArgumentException) {
throw AlreadyRegisteredException
}
kClass.allSubclasses.forEach { currentKClass ->
try {
DependencyResolver(serialModuleBuilder, currentKClass, formatterGetter, dependencyGetter)
} catch (e: AlreadyRegisteredException) {
// ok
}
}
}
}
@InternalSerializationApi
override fun deserialize(decoder: Decoder): T {
val decoded = decoder.decodeSerializableValue(JsonElement.serializer())
return when {
decoded is JsonPrimitive && decoded.contentOrNull != null -> decoded.content.let { dependencyName ->
@Suppress("UNCHECKED_CAST")
(dependencyGetter(dependencyName) as T).also {
objectsCache[dependencyName] = it
}
}
decoded is JsonArray -> {
val serializer = resolveSerializerByPackageName(decoded[0].jsonPrimitive.content)
@Suppress("UNCHECKED_CAST")
formatterGetter().decodeFromJsonElement(serializer, decoded[1]) as T
}
else -> formatterGetter().decodeFromJsonElement(originalSerializer, decoded)
}
}
@InternalSerializationApi
override fun serialize(encoder: Encoder, value: T) {
objectsCache.keys.firstOrNull {
objectsCache[it] === value
} ?.also { dependencyName ->
encoder.encodeString(dependencyName)
} ?: originalSerializer.serialize(encoder, value)
}
}

View File

@ -1,124 +0,0 @@
package dev.inmo.sdi.utils
import dev.inmo.sdi.getClassesForIncludingInSDI
import kotlinx.serialization.InternalSerializationApi
import kotlinx.serialization.json.*
import kotlinx.serialization.modules.*
import kotlin.reflect.KClass
private typealias PackageOrOtherDependencyNamePair = Pair<String?, String?>
private val namesToTheirClasses = getClassesForIncludingInSDI().flatMap {
(it.second + it.first.qualifiedName!!).map { name ->
name to it.first.qualifiedName!!
}
}.toMap()
private fun JsonElement.resolvePackageName(currentKey: String, otherDependenciesKeys: Set<String>): PackageOrOtherDependencyNamePair {
return when (this) {
is JsonPrimitive -> contentOrNull ?.let {
if (it in otherDependenciesKeys) {
null to it
} else {
it to null
}
} ?: throw IllegalArgumentException("Value on dependency name \"$currentKey\" is invalid: provided $this, but expected package name or other dependency name string")
is JsonObject -> if (currentKey in otherDependenciesKeys) {
null to currentKey
} else {
(namesToTheirClasses[currentKey] ?: currentKey) to null
}
is JsonArray -> return get(0).jsonPrimitive.contentOrNull ?.let { (namesToTheirClasses[it] ?: it) to null } ?: throw IllegalArgumentException("Value on first argument of dependency value must be its package as a string, but was provided ${get(0)}")
}
}
@InternalSerializationApi
internal fun createModuleBasedOnConfigRoot(
jsonObject: JsonObject,
moduleBuilder: (SerializersModuleBuilder.() -> Unit)? = null,
baseContext: SerializersModule,
vararg additionalClassesToInclude: KClass<*>
): Json {
lateinit var caches: Map<String, () -> Any>
lateinit var jsonStringFormat: Json
caches = jsonObject.keys.map { key ->
key to callback@{
val elemValue = jsonObject.get(key) ?: throw IllegalStateException("Value for key $key must be provided, but was not")
val packageName: String = elemValue.resolvePackageName(key, jsonObject.keys).let { (packageName, otherDependencyName) ->
when {
packageName != null -> packageName
otherDependencyName != null -> return@callback caches.getValue(otherDependencyName).invoke()
else -> throw IllegalStateException("Internal error: can't resolve other dependency name and package name for key $key")
}
}
val argumentsObject = when (elemValue) {
is JsonPrimitive -> {
elemValue.contentOrNull ?.let { _ ->
JsonObject(emptyMap())
} ?: throw IllegalArgumentException("Value on dependency name \"$key\" is invalid: provided $elemValue, but expected package name or other dependency name string")
}
is JsonObject -> {
elemValue
}
is JsonArray -> {
if (elemValue.size > 1) {
elemValue[1].jsonObject
} else {
JsonObject(emptyMap())
}
}
}
val serializer = resolveSerializerByPackageName(packageName)
return@callback jsonStringFormat.decodeFromJsonElement(serializer, argumentsObject) as Any
}
}.toMap()
val keysToPackages: Map<String, String> = jsonObject.mapNotNull { (key, element) ->
val packageName = element.resolvePackageName(key, jsonObject.keys).first ?: return@mapNotNull null
key to packageName
}.toMap()
val context = baseContext.overwriteWith(
SerializersModule {
keysToPackages.values.forEach {
val kclass = resolveKClassByPackageName(it)
try {
DependencyResolver(
this,
kclass,
{ jsonStringFormat }
) {
caches.getValue(it).invoke()
}
} catch (e: AlreadyRegisteredException) {
// here we are thinking that already registered
}
}
additionalClassesToInclude.forEach {
try {
DependencyResolver(
this,
it,
{ jsonStringFormat }
) {
caches.getValue(it).invoke()
}
} catch (e: AlreadyRegisteredException) {
// here we are thinking that already registered
}
}
if (moduleBuilder != null) {
moduleBuilder()
}
}
)
return Json {
useArrayPolymorphism = true
serializersModule = context
}.also {
jsonStringFormat = it
}
}

View File

@ -12,9 +12,6 @@ fun <T : Any> SerializersModuleBuilder.optionalContextual(
true
} catch (e: SerializationException) {
false
} catch (e: IllegalArgumentException) { // can be a SerializerAlreadyRegisteredException
false
}
@InternalSerializationApi
inline fun <reified T : Any> SerializersModuleBuilder.optionalContextual(
kSerializer: KSerializer<T>
) = optionalContextual(T::class, kSerializer)

View File

@ -1,22 +1,13 @@
package dev.inmo.sdi.utils
import kotlinx.serialization.*
import kotlin.reflect.KClass
import kotlin.reflect.KType
internal expect fun resolveKClassByPackageName(packageName: String): KClass<*>
@InternalSerializationApi
internal fun <T : Any> resolveSerializerByKClass(kClass: KClass<T>): KSerializer<T> = kClass.serializer()
@InternalSerializationApi
internal fun resolveSerializerByPackageName(packageName: String): KSerializer<*> = resolveSerializerByKClass(
resolveKClassByPackageName(packageName)
)
internal expect val <T : Any> KClass<T>.supertypes: List<KType>
internal val KClass<*>.allSubclasses: Set<KClass<*>>
internal val KClass<*>.superclasses: Set<KClass<*>>
get() {
val subclasses = mutableSetOf<KClass<*>>()
val leftToVisit = supertypes.mapNotNull { it.classifier as? KClass<*> }.toMutableList()
@ -24,7 +15,7 @@ internal val KClass<*>.allSubclasses: Set<KClass<*>>
val top = leftToVisit.removeAt(0)
if (subclasses.add(top)) {
leftToVisit.addAll(
top.allSubclasses
top.superclasses
)
}
}

View File

@ -1,6 +1,7 @@
package dev.inmo.sdi
import kotlinx.serialization.*
import kotlinx.serialization.json.JsonObject
import kotlin.test.Test
import kotlin.test.assertEquals
@ -12,9 +13,9 @@ interface List_ChildAPI {
}
@Serializable
class List_Parent(override val services: List<@Contextual List_ChildAPI>) : List_ParentalAPI
data class List_Parent(override val services: List<@Contextual List_ChildAPI>) : List_ParentalAPI
@Serializable
class List_Child(override val names: List<String>) : List_ChildAPI
data class List_Child(override val names: List<String>) : List_ChildAPI
class ListTest {
val servicesNum = 10
@ -50,5 +51,7 @@ class ListTest {
controller.services.forEachIndexed { i, service ->
assertEquals(names[i].second.toList(), service.names)
}
testModuleSerialization(module)
}
}

View File

@ -12,7 +12,7 @@ interface SimpleCustomObject_ServiceAPI {
}
@Serializable
class SimpleCustomObject_Controller(@Contextual val service: SimpleCustomObject_ServiceAPI) :
data class SimpleCustomObject_Controller(@Contextual val service: SimpleCustomObject_ServiceAPI) :
SimpleCustomObject_ControllerAPI {
override fun showUp() {
println("Inited with name \"${service.names}\"")
@ -20,7 +20,7 @@ class SimpleCustomObject_Controller(@Contextual val service: SimpleCustomObject_
}
@Serializable
class SimpleCustomObject_CustomController1(@Contextual val service: SimpleCustomObject_ServiceAPI) :
data class SimpleCustomObject_CustomController1(@Contextual val service: SimpleCustomObject_ServiceAPI) :
SimpleCustomObject_ControllerAPI {
override fun showUp() {
println("Inited with name \"${service.names}\"")
@ -28,7 +28,7 @@ class SimpleCustomObject_CustomController1(@Contextual val service: SimpleCustom
}
@Serializable
class SimpleCustomObject_CustomController2(@Contextual val service: SimpleCustomObject_BusinessService) :
data class SimpleCustomObject_CustomController2(@Contextual val service: SimpleCustomObject_BusinessService) :
SimpleCustomObject_ControllerAPI {
override fun showUp() {
println("Inited with name \"${service.names}\"")
@ -36,16 +36,16 @@ class SimpleCustomObject_CustomController2(@Contextual val service: SimpleCustom
}
@Serializable
class SimpleCustomObject_CustomController3(@Contextual val service: SimpleCustomObject_ServiceAPI) :
data class SimpleCustomObject_CustomController3(@Contextual val service: SimpleCustomObject_ServiceAPI) :
SimpleCustomObject_ControllerAPI {
override fun showUp() {
println("Inited with name \"${service.names}\"")
}
}
@Serializable
class SimpleCustomObject_BusinessService(override val names: List<String>) : SimpleCustomObject_ServiceAPI
data class SimpleCustomObject_BusinessService(override val names: List<String>) : SimpleCustomObject_ServiceAPI
@Serializable
class SimpleCustomObject_BusinessService1(override val names: List<String>) : SimpleCustomObject_ServiceAPI
data class SimpleCustomObject_BusinessService1(override val names: List<String>) : SimpleCustomObject_ServiceAPI
class SimpleCustomObjectTest {
@InternalSerializationApi
@ -118,5 +118,7 @@ class SimpleCustomObjectTest {
(module[customController2Name] as SimpleCustomObject_ControllerAPI)
val customController2 = (module[customController2Name] as SimpleCustomObject_CustomController2)
assertEquals(customNames.toList(), customController2.service.names)
testModuleSerialization(module)
}
}

View File

@ -12,13 +12,13 @@ interface Simple_ServiceAPI {
}
@Serializable
class Simple_Controller(@Contextual val service: Simple_ServiceAPI) : Simple_ControllerAPI {
data class Simple_Controller(@Contextual val service: Simple_ServiceAPI) : Simple_ControllerAPI {
override fun showUp() {
println("Inited with name \"${service.names}\"")
}
}
@Serializable
class Simple_BusinessService(override val names: List<String>) : Simple_ServiceAPI
data class Simple_BusinessService(override val names: List<String>) : Simple_ServiceAPI
class SimpleTest {
@InternalSerializationApi
@ -46,5 +46,7 @@ class SimpleTest {
(module[controllerName] as Simple_ControllerAPI)
val controller = (module["controller"] as Simple_Controller)
assertEquals(names.toList(), controller.service.names)
testModuleSerialization(module)
}
}

View File

@ -0,0 +1,14 @@
package dev.inmo.sdi
import kotlinx.serialization.InternalSerializationApi
import kotlin.test.assertEquals
@InternalSerializationApi
fun testModuleSerialization(
module: Module
) {
val serializedModule = loadModule(nonStrictJson.encodeToString(DefaultModuleSerializer, module))
module.forEach { (key, value) ->
assertEquals(value, serializedModule.getValue(key))
}
}