Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions sdds-core/config-codec/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
plugins {
id("convention.kotlin-java-version-sync")
alias(libs.plugins.kotlin.jvm)
alias(libs.plugins.kotlin.serialization)
}

dependencies {
implementation(libs.base.kotlin.serialization.json)
testImplementation(libs.base.test.unit.jUnit)
testImplementation(libs.base.test.unit.mockk)
}

tasks.register<JavaExec>("runConfigCodec") {
group = "application"
description = "Run Config Codec with parameters"
classpath = sourceSets.main.get().runtimeClasspath
mainClass.set("com.sdds.utils.config.codec.AppKt")

args = listOf(
"/Users/21934234/data/plasma-android/sdds-core/config-codec/src/test/resources/text_field_common.json",
"/Users/21934234/data/plasma-android/sdds-core/config-codec/src/test/resources/text_field_native.json",
"encode"
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package com.sdds.utils.config.codec

import com.sdds.utils.config.codec.internal.CommonConfig
import com.sdds.utils.config.codec.internal.ConfigCodec
import com.sdds.utils.config.codec.internal.NativeConfig
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
import java.io.File

private val serializer = Json {
ignoreUnknownKeys = true
}

fun main(args: Array<String>) {
if (args.size != 3) {
println("Usage: <input_json_path> <output_dir_path> <mode: encode|decode>")
return
}

val inputPath = args[0]
val outputPath = args[1]
val mode = args[2]

val inputFile = File(inputPath)
val outputFile = File(outputPath)

if (!inputFile.exists()) {
println("Input file does not exist: $inputPath")
return
}

val inputJson = inputFile.readText()

val outputJson = when (mode.lowercase()) {
"encode" -> ConfigCodec.encode(serializer.decodeFromString(inputJson)).let {
serializer.encodeToString<NativeConfig>(it)
}
"decode" -> ConfigCodec.decode(serializer.decodeFromString(inputJson)).let {
serializer.encodeToString<CommonConfig>(it)
}
else -> {
println("Invalid mode: $mode. Use 'encode' or 'decode'.")
return
}
}

outputFile.parentFile.mkdirs()
outputFile.writeText(outputJson)
println("Operation '$mode' completed. Output written to $outputPath")
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package com.sdds.utils.config.codec.internal

import kotlinx.serialization.Serializable
import kotlinx.serialization.json.JsonObject

@Serializable
internal data class CommonConfig(
val rootVariationId: String = "",
val colorSchemeVariationId: String = "",
val invariants: JsonObject? = null,
val variations: List<Variation> = emptyList(),
val defaults: Set<DefaultVariationValue> = emptySet(),
)

@Serializable
internal data class Variation(
val id: String,
val name: String,
val values: Set<VariationValue>
)

@Serializable
internal data class VariationValue(
val name: String,
val targets: Set<VariationValueTarget>? = null,
val props: JsonObject? = null,
)

@Serializable
internal data class VariationValueTarget(
val properties: Set<TargetInfo>
)

@Serializable
internal data class TargetInfo(
val id: String,
val value: String,
)

@Serializable
internal data class DefaultVariationValue(
val id: String,
val value: String,
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,204 @@
package com.sdds.utils.config.codec.internal

internal object ConfigCodec {

fun encode(config: CommonConfig): NativeConfig {
val nativeConfig = NativeConfig(props = config.invariants)

val viewVariationId = config.colorSchemeVariationId
val viewVariation = config.variations.findLast { it.id == viewVariationId }
val rootsViews = viewVariation?.values
?.filter { it.targets == null }
?.associate { it.name to NativeViewVariation(it.props) }
.orEmpty()

val targetRegistry = config.variations.flatMap { prop ->
prop.values.map { value ->
TargetInfo(prop.id, value.name)
}
}.groupBy { it.id }

val targetViewRegistry = viewVariation?.values
?.filter { it.targets != null }
?.flatMap { viewValue ->
viewValue.targets!!.map { target -> viewValue.copy(targets = setOf(target)) }
}
?.groupBy { it.targets!!.first() }
?.mapValues { entry ->
entry.value.associate { viewValue -> viewValue.name to NativeViewVariation(viewValue.props) }
}
?.mapKeys { entry ->
entry.key.properties.joinToString(".") { it.value }
}
.orEmpty()
.also { println("target views $it") }

val variations = config.variations
.filter { it.id != viewVariationId }
.flatMap { property ->
property.values
.flatMap { value ->
value.toNativeVariation(
property.id,
config.rootVariationId,
targetRegistry,
targetViewRegistry
)
}
}.filter { it.props != null }


return nativeConfig.copy(view = rootsViews, variations = variations)
}

fun decode(config: NativeConfig): CommonConfig {
val variationRegistry = config.variations.associateBy { it.id }

val viewPropertyValues = config.view.map { (name, view) ->
VariationValue(
name = name,
props = view.props,
targets = null
)
}

val targetViews = config.variations
.flatMap { variation ->
variation.view.map { (name, view) ->
var targetId = ""
val targets = variation.id.split(".")
.map { id ->
TargetInfo(
id = variationRegistry["$targetId$id"]?.kind.orEmpty(),
value = id
).also {
targetId = "$targetId$id."
}
}
.toSet()
.let { VariationValueTarget(it) }
VariationValue(
name = name,
props = view.props,
targets = setOf(targets)
)
}
}

val viewValues = (viewPropertyValues + targetViews).toSet()
val viewProperty = Variation(
id = NATIVE_VIEW_VARIATION_NAME,
name = NATIVE_VIEW_VARIATION_NAME,
values = viewValues
)
.takeIf { viewValues.isNotEmpty() }
.let { listOfNotNull(it) }
.mergeConfigProperty()

val variationPropertyValues = config.variations.map { variation ->
var targetId = ""
val targets = variation.id.split(".")
.dropLast(1)
.takeIf { it.isNotEmpty() }
?.map { id ->
TargetInfo(
id = variationRegistry["$targetId$id"]?.kind.orEmpty(),
value = id
).also { targetId = "$targetId$id." }
}
?.toSet()
?.let { setOf(VariationValueTarget(it)) }

val value = VariationValue(
name = variation.id.removePrefix("${variation.parent}."),
props = variation.props,
targets = targets
)
Variation(
id = variation.kind,
name = variation.kind,
values = setOf(value)
)
}.mergeConfigProperty()

return CommonConfig(
invariants = config.props,
variations = viewProperty + variationPropertyValues,
rootVariationId = NATIVE_SIZE_VARIATION_NAME,
colorSchemeVariationId = NATIVE_VIEW_VARIATION_NAME,
)
}
private const val NATIVE_VIEW_VARIATION_NAME = "view"
private const val NATIVE_SIZE_VARIATION_NAME = "size"
}

private fun VariationValue.toNativeVariation(
propertyId: String,
rootPropertyId: String,
targetRegistry: Map<String, List<TargetInfo>>,
targetViewRegistry: Map<String, Map<String, NativeViewVariation>> = emptyMap(),
): List<NativeVariation> {

if (targets != null) {
return targets.map { target ->
val parent = target.properties.joinToString(".") { it.value }
val id = "$parent.$name"
NativeVariation(
id = id,
kind = propertyId,
parent = parent,
props = props,
view = targetViewRegistry[id].orEmpty()
)
}
}
return targetRegistry[rootPropertyId]
?.takeIf { rootPropertyId != propertyId }
?.map { info ->
val parent = info.value
val id = "$parent.$name"
NativeVariation(
id = id,
kind = propertyId,
parent = parent,
props = props,
view = targetViewRegistry[id].orEmpty()
)
}
?: listOf(
NativeVariation(
id = name,
kind = propertyId,
props = props,
view = targetViewRegistry[name].orEmpty()
)
)
}

private fun List<Variation>.mergeConfigProperty(): List<Variation> {
return groupBy { it.id }.map { (id, group) ->
val name = group.first().name

val mergedValues = group.flatMap { it.values }
.groupBy { it.name to it.props }
.map { (key, valuesGroup) ->
val mergedTargets = valuesGroup
.mapNotNull { it.targets }
.flatten()
.toSet()
.takeIf { it.isNotEmpty() }

VariationValue(
name = key.first,
props = key.second,
targets = mergedTargets
)
}.toSet()

Variation(
id = id,
name = name,
values = mergedValues
)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package com.sdds.utils.config.codec.internal

import kotlinx.serialization.Serializable
import kotlinx.serialization.json.JsonObject

@Serializable
internal data class NativeConfig(
var view: Map<String, NativeViewVariation> = emptyMap(),
var props: JsonObject? = null,
var variations: List<NativeVariation> = emptyList()
)

@Serializable
internal data class NativeViewVariation(
val props: JsonObject?
)

@Serializable
internal data class NativeVariation(
val id: String,
val kind: String,
val parent: String? = null,
val view: Map<String, NativeViewVariation> = emptyMap(),
val props: JsonObject? = null
)
Loading
Loading