mirror of
https://github.com/AdrianKuta/Tree-Data-Structure.git
synced 2026-06-19 19:00:14 +02:00
feat: Kotlin 2.x/K2, version catalog, serialization & coroutines modules, docs
v3.4 modernization (continued): - Migrate to Kotlin 2.x (K2); introduce gradle/libs.versions.toml version catalog; simplify the JS/Wasm/Node and iOS source-set wiring for the K2 hierarchy template. - Apply binary-compatibility-validator and Kover plugins. - New published module tree-structure-serialization: @Serializable TreeNodeDto with toDto()/toTreeNode() round-trip (kotlinx.serialization). - New published module tree-structure-coroutines: asFlow()/pre/post/levelOrderFlow() (kotlinx.coroutines Flow traversal). - Docs: README examples for Sequence/navigation/functional APIs, class-level KDoc (thread-safety/complexity), and a CHANGELOG.md. - Ignore subproject build/ directories. - Bump version to 3.4.0. All JVM tests green (core + both modules).
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -9,6 +9,7 @@
|
||||
/.idea/assetWizardSettings.xml
|
||||
.DS_Store
|
||||
/build
|
||||
build/
|
||||
/captures
|
||||
.externalNativeBuild
|
||||
.cxx
|
||||
|
||||
51
CHANGELOG.md
Normal file
51
CHANGELOG.md
Normal file
@@ -0,0 +1,51 @@
|
||||
# Changelog
|
||||
|
||||
All notable changes to this project are documented here. The format is based on
|
||||
[Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to
|
||||
[Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
## [3.4.0]
|
||||
|
||||
### Added
|
||||
- Lazy `Sequence` traversal: `asSequence(order)`, `preOrderSequence()`, `postOrderSequence()`,
|
||||
`levelOrderSequence()` — composes with the Kotlin stdlib and short-circuits.
|
||||
- Navigation extensions: `isLeaf`, `degree`, `root()`, `ancestors()`, `siblings()`, `leaves()`,
|
||||
`descendants()`.
|
||||
- Functional extensions: `findNode`, `filterNodes`, `anyNode`, `allNodes`, `countNodes`,
|
||||
`foldNodes`, `mapValues`, `deepCopy`, `structurallyEquals` (all stack-safe).
|
||||
- New optional modules published as separate artifacts:
|
||||
- `tree-structure-serialization` — `kotlinx.serialization` support via a `TreeNodeDto`.
|
||||
- `tree-structure-coroutines` — `Flow` traversal (`asFlow`, `preOrderFlow`, …).
|
||||
- `CHANGELOG.md`, expanded README examples, and class-level KDoc (thread-safety / complexity).
|
||||
|
||||
### Changed
|
||||
- `nodeCount()`, `height()`, `clear()` and the post-order iterator are now iterative — deep or
|
||||
degenerate (linear) trees no longer throw `StackOverflowError`.
|
||||
- Migrated to Kotlin 2.x (K2 compiler) and introduced a Gradle version catalog.
|
||||
- Build now uses `binary-compatibility-validator` (committed `.api` baselines) and Kover.
|
||||
|
||||
## [3.1.5]
|
||||
|
||||
### Fixed
|
||||
- Removed a stray `println` in `TreeNode.removeChild()` that printed to stdout on every removal.
|
||||
|
||||
### Removed
|
||||
- Deleted the `Example.ws.kts` worksheet and the `kotlin("script-runtime")` dependency from the
|
||||
published JVM artifact, plus the leftover `ExampleUnitTest` template test.
|
||||
|
||||
### Changed
|
||||
- Bumped `actions/checkout` v2 → v4 in CI workflows.
|
||||
|
||||
## [3.1.4]
|
||||
- Updated Kotlin and JS dependencies; added the `wasmJs` target.
|
||||
|
||||
## [3.1.3]
|
||||
- iOS targets and Maven Central (Sonatype Central Portal) publishing.
|
||||
|
||||
[Unreleased]: https://github.com/AdrianKuta/Tree-Data-Structure/compare/v3.4.0...HEAD
|
||||
[3.4.0]: https://github.com/AdrianKuta/Tree-Data-Structure/compare/v3.1.5...v3.4.0
|
||||
[3.1.5]: https://github.com/AdrianKuta/Tree-Data-Structure/compare/v3.1.3...v3.1.5
|
||||
[3.1.4]: https://github.com/AdrianKuta/Tree-Data-Structure/releases/tag/v3.1.4
|
||||
[3.1.3]: https://github.com/AdrianKuta/Tree-Data-Structure/releases/tag/v3.1.3
|
||||
55
README.md
55
README.md
@@ -5,8 +5,12 @@
|
||||
|
||||
Lightweight Kotlin Multiplatform tree data structure for Kotlin and Java. Includes a small DSL, multiple traversal iterators, and pretty-print support.
|
||||
|
||||
- Kotlin Multiplatform (JVM, JS, iOS, and Native host)
|
||||
- Kotlin Multiplatform (JVM, JS, Wasm, iOS, and Native host)
|
||||
- Pre-order, Post-order, and Level-order iteration
|
||||
- Lazy `Sequence` traversal that composes with the Kotlin stdlib (`map`/`filter`/`firstOrNull`…)
|
||||
- Navigation helpers: `root()`, `ancestors()`, `siblings()`, `leaves()`, `descendants()`, `isLeaf`, `degree`
|
||||
- Functional helpers: `findNode`, `filterNodes`, `anyNode`, `allNodes`, `foldNodes`, `mapValues`, `deepCopy`, `structurallyEquals`
|
||||
- Stack-safe: traversal and `height()`/`nodeCount()`/`clear()` handle arbitrarily deep trees without `StackOverflowError`
|
||||
- Simple DSL: tree { child(...) }
|
||||
- Utilities: nodeCount(), height(), depth(), path(), prettyString(), clear(), removeChild()
|
||||
|
||||
@@ -116,6 +120,55 @@ root.removeChild(child)
|
||||
root.clear() // remove entire subtree
|
||||
```
|
||||
|
||||
### Lazy traversal with Sequence
|
||||
|
||||
Traversal is exposed as a lazy `Sequence`, so it composes with the Kotlin standard library and
|
||||
short-circuits (it never materializes the whole tree just to find one node):
|
||||
|
||||
```kotlin
|
||||
val tree = tree("World") {
|
||||
child("North America") { child("USA") }
|
||||
child("Europe") {
|
||||
child("Poland")
|
||||
child("Germany")
|
||||
}
|
||||
}
|
||||
|
||||
// Pick an order explicitly — no need to mutate the node's state.
|
||||
tree.preOrderSequence().map { it.value }.toList() // [World, North America, USA, Europe, Poland, Germany]
|
||||
tree.levelOrderSequence().first { it.value == "USA" } // stops as soon as it's found
|
||||
tree.asSequence(TreeNodeIterators.PostOrder).count() // 6
|
||||
```
|
||||
|
||||
### Navigation
|
||||
|
||||
```kotlin
|
||||
val usa = tree.findNode { it == "USA" }!!
|
||||
|
||||
usa.isLeaf // true
|
||||
usa.depth() // 2
|
||||
usa.root().value // "World"
|
||||
usa.ancestors().map { it.value } // [North America, World]
|
||||
tree.leaves().map { it.value } // [USA, Poland, Germany]
|
||||
val europe = tree.findNode { it == "Europe" }!!
|
||||
europe.children.first().siblings().map { it.value } // [Germany]
|
||||
```
|
||||
|
||||
### Functional operations
|
||||
|
||||
```kotlin
|
||||
tree.anyNode { it == "Poland" } // true
|
||||
tree.filterNodes { it.length > 5 } // nodes whose value is longer than 5 chars
|
||||
tree.countNodes { it.startsWith("U") } // 1
|
||||
|
||||
// Transform values into a brand-new tree (the original is untouched); stack-safe.
|
||||
val lengths: TreeNode<Int> = tree.mapValues { it.length }
|
||||
|
||||
// Deep copy + structural comparison.
|
||||
val copy = tree.deepCopy()
|
||||
tree.structurallyEquals(copy) // true (same values, same shape, different nodes)
|
||||
```
|
||||
|
||||
## Publishing to Maven Central (central.sonatype.com)
|
||||
|
||||
This project is configured to publish artifacts to Maven Central via the Sonatype Central Portal.
|
||||
|
||||
@@ -1,19 +1,17 @@
|
||||
import org.jetbrains.kotlin.gradle.targets.js.dsl.ExperimentalWasmDsl
|
||||
import org.jetbrains.kotlin.gradle.targets.js.dsl.KotlinWasmTargetDsl
|
||||
import org.jetbrains.kotlin.gradle.targets.js.ir.KotlinJsIrTarget
|
||||
import org.jetbrains.kotlin.gradle.targets.js.nodejs.NodeJsRootExtension
|
||||
import org.jetbrains.kotlin.gradle.targets.js.nodejs.NodeJsRootPlugin
|
||||
import org.jetbrains.kotlin.gradle.ExperimentalWasmDsl
|
||||
|
||||
plugins {
|
||||
kotlin("multiplatform") version "1.9.24"
|
||||
id("org.jetbrains.dokka") version "1.9.20"
|
||||
id("com.vanniktech.maven.publish") version "0.34.0"
|
||||
alias(libs.plugins.kotlinMultiplatform)
|
||||
alias(libs.plugins.dokka)
|
||||
alias(libs.plugins.mavenPublish)
|
||||
alias(libs.plugins.binaryCompatibilityValidator)
|
||||
alias(libs.plugins.kover)
|
||||
signing
|
||||
}
|
||||
|
||||
val PUBLISH_GROUP_ID = "com.github.adriankuta"
|
||||
val PUBLISH_ARTIFACT_ID = "tree-structure" // base artifact; KMP will add -jvm, -ios*, etc.
|
||||
val PUBLISH_VERSION = "3.1.5"
|
||||
val PUBLISH_VERSION = "3.4.0"
|
||||
|
||||
val snapshot: String? by project
|
||||
|
||||
@@ -29,7 +27,10 @@ mavenPublishing {
|
||||
|
||||
pom {
|
||||
name.set("Tree Data Structure")
|
||||
description.set("Simple implementation to store object in tree structure.")
|
||||
description.set(
|
||||
"Lightweight n-ary tree data structure for Kotlin Multiplatform (JVM, JS, Wasm, iOS, " +
|
||||
"Native). DSL, pre/post/level-order traversal, lazy Sequence traversal, and pretty-print.",
|
||||
)
|
||||
url.set("https://github.com/AdrianKuta/Tree-Data-Structure")
|
||||
|
||||
licenses {
|
||||
@@ -54,28 +55,15 @@ mavenPublishing {
|
||||
}
|
||||
}
|
||||
|
||||
// No legacy publishing {} block or s01 repos — Central Portal handles it.
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
java {
|
||||
toolchain {
|
||||
languageVersion.set(JavaLanguageVersion.of(21))
|
||||
}
|
||||
}
|
||||
kotlin {
|
||||
jvmToolchain(21);
|
||||
jvm {
|
||||
compilations.all {
|
||||
kotlinOptions {
|
||||
jvmTarget = "21" // <- was "1.8"
|
||||
}
|
||||
}
|
||||
}
|
||||
jvmToolchain(21)
|
||||
|
||||
jvm()
|
||||
|
||||
// JS targets (IR) for publishing
|
||||
js(IR) {
|
||||
browser()
|
||||
nodejs()
|
||||
@@ -87,21 +75,7 @@ kotlin {
|
||||
nodejs()
|
||||
}
|
||||
|
||||
rootProject.plugins.withType<NodeJsRootPlugin> {
|
||||
rootProject.extensions.getByType<NodeJsRootExtension>().nodeVersion = "22.0.0"
|
||||
}
|
||||
|
||||
kotlin.targets.withType<KotlinJsIrTarget> {
|
||||
if (name == "wasmJs") {
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
(this as KotlinWasmTargetDsl).apply {
|
||||
nodejs {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// iOS targets
|
||||
// Apple targets
|
||||
iosX64()
|
||||
iosArm64()
|
||||
iosSimulatorArm64()
|
||||
@@ -109,7 +83,7 @@ kotlin {
|
||||
// Native host target
|
||||
val hostOs = System.getProperty("os.name")
|
||||
val isMingwX64 = hostOs.startsWith("Windows")
|
||||
val nativeTarget = when {
|
||||
when {
|
||||
hostOs == "Mac OS X" -> macosX64("native")
|
||||
hostOs == "Linux" -> linuxX64("native")
|
||||
isMingwX64 -> mingwX64("native")
|
||||
@@ -117,25 +91,8 @@ kotlin {
|
||||
}
|
||||
|
||||
sourceSets {
|
||||
val commonMain by getting
|
||||
val commonTest by getting { dependencies { implementation(kotlin("test")) } }
|
||||
val jvmMain by getting
|
||||
val jvmTest by getting
|
||||
val jsMain by getting
|
||||
val jsTest by getting
|
||||
val wasmJsMain by getting
|
||||
val wasmJsTest by getting
|
||||
val nativeMain by getting
|
||||
val nativeTest by getting
|
||||
|
||||
// Shared iOS source sets
|
||||
val iosMain by creating { dependsOn(commonMain) }
|
||||
val iosTest by creating { dependsOn(commonTest) }
|
||||
val iosX64Main by getting { dependsOn(iosMain) }
|
||||
val iosArm64Main by getting { dependsOn(iosMain) }
|
||||
val iosSimulatorArm64Main by getting { dependsOn(iosMain) }
|
||||
val iosX64Test by getting { dependsOn(iosTest) }
|
||||
val iosArm64Test by getting { dependsOn(iosTest) }
|
||||
val iosSimulatorArm64Test by getting { dependsOn(iosTest) }
|
||||
commonTest.dependencies {
|
||||
implementation(kotlin("test"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,2 +1 @@
|
||||
kotlin.code.style=official
|
||||
kotlin.js.compiler=ir
|
||||
|
||||
21
gradle/libs.versions.toml
Normal file
21
gradle/libs.versions.toml
Normal file
@@ -0,0 +1,21 @@
|
||||
[versions]
|
||||
kotlin = "2.1.0"
|
||||
dokka = "1.9.20"
|
||||
mavenPublish = "0.34.0"
|
||||
binaryCompatibilityValidator = "0.16.3"
|
||||
kover = "0.8.3"
|
||||
coroutines = "1.9.0"
|
||||
kotlinxSerialization = "1.7.3"
|
||||
|
||||
[plugins]
|
||||
kotlinMultiplatform = { id = "org.jetbrains.kotlin.multiplatform", version.ref = "kotlin" }
|
||||
kotlinSerialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" }
|
||||
dokka = { id = "org.jetbrains.dokka", version.ref = "dokka" }
|
||||
mavenPublish = { id = "com.vanniktech.maven.publish", version.ref = "mavenPublish" }
|
||||
binaryCompatibilityValidator = { id = "org.jetbrains.kotlinx.binary-compatibility-validator", version.ref = "binaryCompatibilityValidator" }
|
||||
kover = { id = "org.jetbrains.kotlinx.kover", version.ref = "kover" }
|
||||
|
||||
[libraries]
|
||||
kotlinx-coroutines-core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "coroutines" }
|
||||
kotlinx-coroutines-test = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-test", version.ref = "coroutines" }
|
||||
kotlinx-serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "kotlinxSerialization" }
|
||||
@@ -1,3 +1,4 @@
|
||||
|
||||
rootProject.name = "tree-structure"
|
||||
|
||||
include(":tree-structure-serialization")
|
||||
include(":tree-structure-coroutines")
|
||||
|
||||
@@ -9,7 +9,23 @@ import com.github.adriankuta.datastructure.tree.iterators.TreeNodeIterators.*
|
||||
import kotlin.jvm.JvmSynthetic
|
||||
|
||||
/**
|
||||
* @param treeIterator Choose one of available iterators from [TreeNodeIterators]
|
||||
* A node in a generic, mutable n-ary tree. Each node holds a [value], a reference to its [parent]
|
||||
* and an ordered list of [children].
|
||||
*
|
||||
* Iterating a node (via [iterator], or the lazy [asSequence]/[preOrderSequence] extensions) visits
|
||||
* the node and all of its descendants. Traversal and the [height]/[nodeCount]/[clear] helpers are
|
||||
* implemented iteratively, so they are safe on arbitrarily deep trees.
|
||||
*
|
||||
* **Not thread-safe.** Nodes are mutable ([addChild]/[removeChild]/[clear] mutate the structure and
|
||||
* parent pointers). Sharing a tree across threads requires external synchronization, and the tree
|
||||
* must not be modified while it is being iterated.
|
||||
*
|
||||
* Equality is by reference (identity); use the `structurallyEquals` extension to compare two trees
|
||||
* by value and shape.
|
||||
*
|
||||
* @param value the value stored in this node.
|
||||
* @param treeIterator the default traversal order used by [iterator]. Prefer the
|
||||
* `asSequence(order)` / `preOrderSequence()` extensions to choose an order without mutating state.
|
||||
*/
|
||||
open class TreeNode<T>(val value: T, var treeIterator: TreeNodeIterators = PreOrder) : Iterable<TreeNode<T>>, ChildDeclarationInterface<T> {
|
||||
|
||||
|
||||
88
tree-structure-coroutines/build.gradle.kts
Normal file
88
tree-structure-coroutines/build.gradle.kts
Normal file
@@ -0,0 +1,88 @@
|
||||
import org.jetbrains.kotlin.gradle.ExperimentalWasmDsl
|
||||
|
||||
plugins {
|
||||
alias(libs.plugins.kotlinMultiplatform)
|
||||
alias(libs.plugins.dokka)
|
||||
alias(libs.plugins.mavenPublish)
|
||||
signing
|
||||
}
|
||||
|
||||
group = "com.github.adriankuta"
|
||||
version = rootProject.version
|
||||
|
||||
mavenPublishing {
|
||||
publishToMavenCentral(automaticRelease = false)
|
||||
signAllPublications()
|
||||
|
||||
coordinates("com.github.adriankuta", "tree-structure-coroutines", version.toString())
|
||||
|
||||
pom {
|
||||
name.set("Tree Data Structure — coroutines")
|
||||
description.set("kotlinx.coroutines Flow traversal for the tree-structure library.")
|
||||
url.set("https://github.com/AdrianKuta/Tree-Data-Structure")
|
||||
licenses {
|
||||
license {
|
||||
name.set("MIT License")
|
||||
url.set("https://opensource.org/licenses/MIT")
|
||||
distribution.set("repo")
|
||||
}
|
||||
}
|
||||
developers {
|
||||
developer {
|
||||
id.set("AdrianKuta")
|
||||
name.set("Adrian Kuta")
|
||||
email.set("adrian.kuta93@gmail.com")
|
||||
}
|
||||
}
|
||||
scm {
|
||||
url.set("https://github.com/AdrianKuta/Tree-Data-Structure")
|
||||
connection.set("scm:git:https://github.com/AdrianKuta/Tree-Data-Structure.git")
|
||||
developerConnection.set("scm:git:ssh://git@github.com/AdrianKuta/Tree-Data-Structure.git")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
kotlin {
|
||||
jvmToolchain(21)
|
||||
|
||||
jvm()
|
||||
|
||||
js(IR) {
|
||||
browser()
|
||||
nodejs()
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalWasmDsl::class)
|
||||
wasmJs {
|
||||
browser()
|
||||
nodejs()
|
||||
}
|
||||
|
||||
iosX64()
|
||||
iosArm64()
|
||||
iosSimulatorArm64()
|
||||
|
||||
val hostOs = System.getProperty("os.name")
|
||||
val isMingwX64 = hostOs.startsWith("Windows")
|
||||
when {
|
||||
hostOs == "Mac OS X" -> macosX64("native")
|
||||
hostOs == "Linux" -> linuxX64("native")
|
||||
isMingwX64 -> mingwX64("native")
|
||||
else -> throw GradleException("Host OS is not supported in Kotlin/Native.")
|
||||
}
|
||||
|
||||
sourceSets {
|
||||
commonMain.dependencies {
|
||||
api(project(":"))
|
||||
api(libs.kotlinx.coroutines.core)
|
||||
}
|
||||
commonTest.dependencies {
|
||||
implementation(kotlin("test"))
|
||||
implementation(libs.kotlinx.coroutines.test)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package com.github.adriankuta.datastructure.tree.coroutines
|
||||
|
||||
import com.github.adriankuta.datastructure.tree.TreeNode
|
||||
import com.github.adriankuta.datastructure.tree.asSequence
|
||||
import com.github.adriankuta.datastructure.tree.iterators.TreeNodeIterators
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.asFlow
|
||||
|
||||
/**
|
||||
* Emits this node and all of its descendants as a cold [Flow], traversed in the given [order].
|
||||
* Useful for plugging tree traversal into coroutine/Flow pipelines (e.g. in a ViewModel).
|
||||
*/
|
||||
public fun <T> TreeNode<T>.asFlow(
|
||||
order: TreeNodeIterators = TreeNodeIterators.PreOrder,
|
||||
): Flow<TreeNode<T>> = asSequence(order).asFlow()
|
||||
|
||||
/** Pre-order traversal as a cold [Flow]. */
|
||||
public fun <T> TreeNode<T>.preOrderFlow(): Flow<TreeNode<T>> = asFlow(TreeNodeIterators.PreOrder)
|
||||
|
||||
/** Post-order traversal as a cold [Flow]. */
|
||||
public fun <T> TreeNode<T>.postOrderFlow(): Flow<TreeNode<T>> = asFlow(TreeNodeIterators.PostOrder)
|
||||
|
||||
/** Level-order (breadth-first) traversal as a cold [Flow]. */
|
||||
public fun <T> TreeNode<T>.levelOrderFlow(): Flow<TreeNode<T>> = asFlow(TreeNodeIterators.LevelOrder)
|
||||
@@ -0,0 +1,35 @@
|
||||
package com.github.adriankuta.datastructure.tree.coroutines
|
||||
|
||||
import com.github.adriankuta.datastructure.tree.iterators.TreeNodeIterators
|
||||
import com.github.adriankuta.datastructure.tree.tree
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.toList
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertEquals
|
||||
|
||||
class TreeNodeFlowTest {
|
||||
|
||||
private fun sample() = tree(1) {
|
||||
child(2) {
|
||||
child(4)
|
||||
child(5)
|
||||
}
|
||||
child(3) {
|
||||
child(6)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun preOrderFlowEmitsInPreOrder() = runTest {
|
||||
assertEquals(listOf(1, 2, 4, 5, 3, 6), sample().preOrderFlow().map { it.value }.toList())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun levelOrderFlowEmitsInLevelOrder() = runTest {
|
||||
assertEquals(
|
||||
listOf(1, 2, 3, 4, 5, 6),
|
||||
sample().asFlow(TreeNodeIterators.LevelOrder).map { it.value }.toList(),
|
||||
)
|
||||
}
|
||||
}
|
||||
88
tree-structure-serialization/build.gradle.kts
Normal file
88
tree-structure-serialization/build.gradle.kts
Normal file
@@ -0,0 +1,88 @@
|
||||
import org.jetbrains.kotlin.gradle.ExperimentalWasmDsl
|
||||
|
||||
plugins {
|
||||
alias(libs.plugins.kotlinMultiplatform)
|
||||
alias(libs.plugins.kotlinSerialization)
|
||||
alias(libs.plugins.dokka)
|
||||
alias(libs.plugins.mavenPublish)
|
||||
signing
|
||||
}
|
||||
|
||||
group = "com.github.adriankuta"
|
||||
version = rootProject.version
|
||||
|
||||
mavenPublishing {
|
||||
publishToMavenCentral(automaticRelease = false)
|
||||
signAllPublications()
|
||||
|
||||
coordinates("com.github.adriankuta", "tree-structure-serialization", version.toString())
|
||||
|
||||
pom {
|
||||
name.set("Tree Data Structure — kotlinx.serialization")
|
||||
description.set("kotlinx.serialization support (TreeNodeDto round-trip) for the tree-structure library.")
|
||||
url.set("https://github.com/AdrianKuta/Tree-Data-Structure")
|
||||
licenses {
|
||||
license {
|
||||
name.set("MIT License")
|
||||
url.set("https://opensource.org/licenses/MIT")
|
||||
distribution.set("repo")
|
||||
}
|
||||
}
|
||||
developers {
|
||||
developer {
|
||||
id.set("AdrianKuta")
|
||||
name.set("Adrian Kuta")
|
||||
email.set("adrian.kuta93@gmail.com")
|
||||
}
|
||||
}
|
||||
scm {
|
||||
url.set("https://github.com/AdrianKuta/Tree-Data-Structure")
|
||||
connection.set("scm:git:https://github.com/AdrianKuta/Tree-Data-Structure.git")
|
||||
developerConnection.set("scm:git:ssh://git@github.com/AdrianKuta/Tree-Data-Structure.git")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
kotlin {
|
||||
jvmToolchain(21)
|
||||
|
||||
jvm()
|
||||
|
||||
js(IR) {
|
||||
browser()
|
||||
nodejs()
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalWasmDsl::class)
|
||||
wasmJs {
|
||||
browser()
|
||||
nodejs()
|
||||
}
|
||||
|
||||
iosX64()
|
||||
iosArm64()
|
||||
iosSimulatorArm64()
|
||||
|
||||
val hostOs = System.getProperty("os.name")
|
||||
val isMingwX64 = hostOs.startsWith("Windows")
|
||||
when {
|
||||
hostOs == "Mac OS X" -> macosX64("native")
|
||||
hostOs == "Linux" -> linuxX64("native")
|
||||
isMingwX64 -> mingwX64("native")
|
||||
else -> throw GradleException("Host OS is not supported in Kotlin/Native.")
|
||||
}
|
||||
|
||||
sourceSets {
|
||||
commonMain.dependencies {
|
||||
api(project(":"))
|
||||
api(libs.kotlinx.serialization.json)
|
||||
}
|
||||
commonTest.dependencies {
|
||||
implementation(kotlin("test"))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package com.github.adriankuta.datastructure.tree.serialization
|
||||
|
||||
import com.github.adriankuta.datastructure.tree.TreeNode
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
/**
|
||||
* A serializable, acyclic view of a [TreeNode] subtree. [TreeNode] itself holds a back-reference to
|
||||
* its parent (a cycle), so it cannot be `@Serializable` directly — convert to/from this DTO instead.
|
||||
*
|
||||
* ```
|
||||
* val json = Json.encodeToString(tree.toDto())
|
||||
* val restored = Json.decodeFromString<TreeNodeDto<String>>(json).toTreeNode()
|
||||
* ```
|
||||
*/
|
||||
@Serializable
|
||||
public data class TreeNodeDto<T>(
|
||||
val value: T,
|
||||
val children: List<TreeNodeDto<T>> = emptyList(),
|
||||
)
|
||||
|
||||
/** Converts this subtree into a serializable [TreeNodeDto], preserving values and shape. */
|
||||
public fun <T> TreeNode<T>.toDto(): TreeNodeDto<T> =
|
||||
TreeNodeDto(value, children.map { it.toDto() })
|
||||
|
||||
/** Rebuilds a mutable [TreeNode] tree from this DTO. */
|
||||
public fun <T> TreeNodeDto<T>.toTreeNode(): TreeNode<T> {
|
||||
val node = TreeNode(value)
|
||||
children.forEach { node.addChild(it.toTreeNode()) }
|
||||
return node
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
package com.github.adriankuta.datastructure.tree.serialization
|
||||
|
||||
import com.github.adriankuta.datastructure.tree.structurallyEquals
|
||||
import com.github.adriankuta.datastructure.tree.tree
|
||||
import kotlinx.serialization.encodeToString
|
||||
import kotlinx.serialization.json.Json
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertTrue
|
||||
|
||||
class TreeNodeSerializationTest {
|
||||
|
||||
@Test
|
||||
fun roundTripsThroughJson() {
|
||||
val original = tree("World") {
|
||||
child("North America") { child("USA") }
|
||||
child("Europe") {
|
||||
child("Poland")
|
||||
child("Germany")
|
||||
}
|
||||
}
|
||||
|
||||
val json = Json.encodeToString(original.toDto())
|
||||
val restored = Json.decodeFromString<TreeNodeDto<String>>(json).toTreeNode()
|
||||
|
||||
assertTrue(original.structurallyEquals(restored))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun dtoMirrorsTreeShape() {
|
||||
val dto = tree(1) {
|
||||
child(2)
|
||||
child(3) { child(4) }
|
||||
}.toDto()
|
||||
|
||||
assertEquals(1, dto.value)
|
||||
assertEquals(2, dto.children.size)
|
||||
assertEquals(4, dto.children[1].children[0].value)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user