diff --git a/settings.gradle.kts b/settings.gradle.kts
index 36aaa59..dbeb7f9 100644
--- a/settings.gradle.kts
+++ b/settings.gradle.kts
@@ -66,6 +66,8 @@ include(
"backbone",
+ "weak",
+
"spine",
"spine-ktor",
"spine-ktor:spine-ktor-server",
diff --git a/weak/README.md b/weak/README.md
new file mode 100644
index 0000000..74cbdfe
--- /dev/null
+++ b/weak/README.md
@@ -0,0 +1,7 @@
+# Module Weak
+
+Weak references and maps for Kotlin Multiplatform.
+
+
+
+
diff --git a/weak/build.gradle.kts b/weak/build.gradle.kts
new file mode 100644
index 0000000..42a33f3
--- /dev/null
+++ b/weak/build.gradle.kts
@@ -0,0 +1,31 @@
+plugins {
+ alias(opensavvyConventions.plugins.base)
+ alias(opensavvyConventions.plugins.kotlin.library)
+}
+
+kotlin {
+ jvm()
+ js(IR) {
+ browser()
+ nodejs()
+ }
+ iosSimulatorArm64()
+ iosArm64()
+ iosX64()
+ linuxX64()
+
+ sourceSets.commonTest.dependencies {
+ implementation(libs.bundles.prepared)
+ }
+}
+
+library {
+ name.set("Weak")
+ description.set("Weak references and maps for Kotlin Multiplatform")
+ homeUrl.set("https://opensavvy.gitlab.io/groundwork/pedestal/api-docs/weak/index.html")
+
+ license.set {
+ name.set("Apache 2.0")
+ url.set("https://www.apache.org/licenses/LICENSE-2.0.txt")
+ }
+}
--
2.51.2
From a0b38bfbdb9f8fd1c65d86baa6ba9ccefd4edd66 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Ivan=20=E2=80=9CCLOVIS=E2=80=9D=20Canet?=
Date: Fri, 6 Sep 2024 21:42:02 +0200
Subject: [PATCH 02/10] feat(weak): Create the ExperimentalWeakApi
---
weak/src/commonMain/kotlin/ExperimentalWeakApi.kt | 5 +++++
1 file changed, 5 insertions(+)
create mode 100644 weak/src/commonMain/kotlin/ExperimentalWeakApi.kt
diff --git a/weak/src/commonMain/kotlin/ExperimentalWeakApi.kt b/weak/src/commonMain/kotlin/ExperimentalWeakApi.kt
new file mode 100644
index 0000000..63a47aa
--- /dev/null
+++ b/weak/src/commonMain/kotlin/ExperimentalWeakApi.kt
@@ -0,0 +1,5 @@
+package opensavvy.pedestal.weak
+
+@RequiresOptIn("This API is waiting for feedback before stabilization. Please send us feedback at https://gitlab.com/opensavvy/groundwork/pedestal/-/issues/150", RequiresOptIn.Level.ERROR)
+@MustBeDocumented
+annotation class ExperimentalWeakApi
--
2.51.2
From ee9aca1bd2d9e82326d1b51307dc653f27bfc82d Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Ivan=20=E2=80=9CCLOVIS=E2=80=9D=20Canet?=
Date: Fri, 6 Sep 2024 21:45:55 +0200
Subject: [PATCH 03/10] feat(weak): Create WeakRef and SoftRef
Closes #152
---
gradle/libs.versions.toml | 3 +
weak/build.gradle.kts | 4 +
weak/src/commonMain/kotlin/WeakRef.kt | 83 ++++++++++++++++++++
weak/src/commonTest/kotlin/WeakRefTest.kt | 1 +
weak/src/jsMain/kotlin/WeakRef.js.kt | 34 ++++++++
weak/src/jvmMain/kotlin/WeakRef.jvm.kt | 37 +++++++++
weak/src/nativeMain/kotlin/WeakRef.native.kt | 37 +++++++++
7 files changed, 199 insertions(+)
create mode 100644 weak/src/commonMain/kotlin/WeakRef.kt
create mode 100644 weak/src/commonTest/kotlin/WeakRefTest.kt
create mode 100644 weak/src/jsMain/kotlin/WeakRef.js.kt
create mode 100644 weak/src/jvmMain/kotlin/WeakRef.jvm.kt
create mode 100644 weak/src/nativeMain/kotlin/WeakRef.native.kt
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index e1c228b..c952213 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -9,6 +9,7 @@ kotlinx-datetime = "0.6.0" # https://github.com/Kotlin/kotlinx-dateti
kotlinx-coroutines = "1.8.1" # https://github.com/Kotlin/kotlinx.coroutines/releases
kotlinx-serialization = "1.7.1" # https://github.com/Kotlin/kotlinx.serialization/releases
prepared = "1.3.0" # https://gitlab.com/opensavvy/prepared/-/releases
+kotlin-js = "1.0.0-pre.801" # https://central.sonatype.com/artifact/org.jetbrains.kotlin-wrappers/kotlin-js
[plugins]
@@ -19,6 +20,8 @@ kotlinx-coroutines-test = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-t
kotlinx-serialization-core = { module = "org.jetbrains.kotlinx:kotlinx-serialization-core", version.ref = "kotlinx-serialization" }
kotlinx-serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "kotlinx-serialization" }
+kotlinJs = { module = "org.jetbrains.kotlin-wrappers:kotlin-js", version.ref = "kotlin-js" }
+
ktor-server-core = { module = "io.ktor:ktor-server-core", version.ref = "ktor" }
ktor-server-testHost = { module = "io.ktor:ktor-server-test-host", version.ref = "ktor" }
ktor-server-contentNegotiation = { module = "io.ktor:ktor-server-content-negotiation", version.ref = "ktor" }
diff --git a/weak/build.gradle.kts b/weak/build.gradle.kts
index 42a33f3..91fd4f7 100644
--- a/weak/build.gradle.kts
+++ b/weak/build.gradle.kts
@@ -14,6 +14,10 @@ kotlin {
iosX64()
linuxX64()
+ sourceSets.jsMain.dependencies {
+ implementation(libs.kotlinJs)
+ }
+
sourceSets.commonTest.dependencies {
implementation(libs.bundles.prepared)
}
diff --git a/weak/src/commonMain/kotlin/WeakRef.kt b/weak/src/commonMain/kotlin/WeakRef.kt
new file mode 100644
index 0000000..8782914
--- /dev/null
+++ b/weak/src/commonMain/kotlin/WeakRef.kt
@@ -0,0 +1,83 @@
+package opensavvy.pedestal.weak
+
+/**
+ * A weak reference holds a reference to another object, without preventing that object from being garbage-collected.
+ *
+ * The [value][read] referenced by this class is eligible for garbage-collection, meaning that it could disappear
+ * **at any time**. Garbage collectors are complex machinery. One should take care _not_ to write code that depends
+ * on the behavior of a specific garbage collector, as that behavior can change between future versions.
+ *
+ * ### Obtain instances
+ *
+ * The default implementations are available via the top-level [WeakRef] and [SoftRef] functions.
+ * Other implementations are available in the `algorithms` subpackage.
+ *
+ * ### Not stable for inheritance
+ *
+ * This interface is not stable for inheritance. We may add new methods at any time.
+ */
+interface WeakRef {
+
+ /**
+ * Attempts to read the value held by this reference.
+ *
+ * If the value was garbage-collected, which may happen at any time,
+ * this accessor returns `null`.
+ *
+ * One should not assume any additional behavior. In particular, the value may continue
+ * to be returned long after it has become unreachable from anywhere else in the program,
+ * or disappear sooner than one expected.
+ *
+ * In particular, it is not possible for this class to provide a function to check whether
+ * the value is still available or not: the value could disappear between that check and
+ * the call to this function.
+ */
+ fun read(): T?
+
+ companion object
+}
+
+/**
+ * Instantiates a weak reference: a reference to a [value] that doesn't stop
+ * the garbage collector from collecting it.
+ *
+ * The value may be accessed with [WeakRef.read].
+ * However, it could disappear **at any time**.
+ *
+ * ### When should you use a weak reference?
+ *
+ * The runtime is encouraged to clear the value as soon as possible.
+ *
+ * This makes implementations ideal for writing mappers from an object to a more expensive one,
+ * when we know that we don't need the result of the mapping as soon as the initial object
+ * becomes unavailable.
+ *
+ * The exact behavior of the returned object is platform-specific.
+ *
+ * @see SoftRef For implementing caches
+ */
+@ExperimentalWeakApi
+expect fun WeakRef(value: T): WeakRef
+
+/**
+ * Instantiates a soft reference: a reference to a [value] that doesn't stop
+ * the garbage collector from collecting it.
+ *
+ * The value may be accessed with [WeakRef.read].
+ * However, it could disappear **at any time**.
+ *
+ * ### When should you use a soft reference?
+ *
+ * The runtime is encouraged to keep the value for as long as possible.
+ * For example, the runtime may decide to keep the value until memory pressure.
+ *
+ * This makes this implementation ideal for writing caches, since the value will be kept longer
+ * than strictly necessary.
+ *
+ * The exact behavior of the returned object is platform-specific.
+ *
+ * @see WeakRef For implementing mappers
+ */
+@ExperimentalWeakApi
+@Suppress("FunctionName")
+expect fun SoftRef(value: T): WeakRef
diff --git a/weak/src/commonTest/kotlin/WeakRefTest.kt b/weak/src/commonTest/kotlin/WeakRefTest.kt
new file mode 100644
index 0000000..c17fdb4
--- /dev/null
+++ b/weak/src/commonTest/kotlin/WeakRefTest.kt
@@ -0,0 +1 @@
+package opensavvy.pedestal.weak
diff --git a/weak/src/jsMain/kotlin/WeakRef.js.kt b/weak/src/jsMain/kotlin/WeakRef.js.kt
new file mode 100644
index 0000000..5a6e536
--- /dev/null
+++ b/weak/src/jsMain/kotlin/WeakRef.js.kt
@@ -0,0 +1,34 @@
+package opensavvy.pedestal.weak
+
+private class JsWeakRef(
+ value: T
+) : WeakRef {
+
+ private val reference = js.memory.WeakRef(value)
+
+ override fun read(): T? =
+ reference.deref()
+ .takeIf { it != undefined }
+
+ override fun toString(): String =
+ reference.toString()
+}
+
+/**
+ * Implementation of [WeakRef] backed by a JS [WeakRef][js.memory.WeakRef].
+ *
+ * JS doesn't make a difference between weak and soft references.
+ */
+@ExperimentalWeakApi
+actual fun WeakRef(value: T): WeakRef =
+ JsWeakRef(value)
+
+/**
+ * Implementation of [WeakRef] backed by a JS [WeakRef][js.memory.WeakRef].
+ *
+ * JS doesn't make a difference between weak and soft references.
+ */
+@ExperimentalWeakApi
+@Suppress("FunctionName")
+actual fun SoftRef(value: T): WeakRef =
+ JsWeakRef(value)
diff --git a/weak/src/jvmMain/kotlin/WeakRef.jvm.kt b/weak/src/jvmMain/kotlin/WeakRef.jvm.kt
new file mode 100644
index 0000000..910c6b3
--- /dev/null
+++ b/weak/src/jvmMain/kotlin/WeakRef.jvm.kt
@@ -0,0 +1,37 @@
+package opensavvy.pedestal.weak
+
+import java.lang.ref.Reference
+import java.lang.ref.SoftReference
+import java.lang.ref.WeakReference
+
+private class JavaReferenceHolder(
+ private val reference: Reference,
+) : WeakRef {
+
+ override fun read(): T? =
+ reference.get()
+
+ override fun toString(): String =
+ reference.toString()
+}
+
+/**
+ * Interprets a Java [reference] into a Kotlin [WeakRef].
+ */
+fun WeakRef.Companion.fromJava(reference: Reference): WeakRef =
+ JavaReferenceHolder(reference)
+
+/**
+ * Implementation of [WeakRef] backed by a JVM [WeakReference].
+ */
+@ExperimentalWeakApi
+actual fun WeakRef(value: T): WeakRef =
+ JavaReferenceHolder(WeakReference(value))
+
+/**
+ * Implementation of [WeakRef] backed by a JVM [SoftReference].
+ */
+@ExperimentalWeakApi
+@Suppress("FunctionName")
+actual fun SoftRef(value: T): WeakRef =
+ JavaReferenceHolder(SoftReference(value))
diff --git a/weak/src/nativeMain/kotlin/WeakRef.native.kt b/weak/src/nativeMain/kotlin/WeakRef.native.kt
new file mode 100644
index 0000000..7b452af
--- /dev/null
+++ b/weak/src/nativeMain/kotlin/WeakRef.native.kt
@@ -0,0 +1,37 @@
+package opensavvy.pedestal.weak
+
+import kotlin.experimental.ExperimentalNativeApi
+import kotlin.native.ref.WeakReference
+
+@ExperimentalNativeApi
+private class NativeWeakRef(
+ value: T,
+) : WeakRef {
+
+ private val reference = WeakReference(value)
+
+ override fun read(): T? =
+ reference.get()
+
+}
+
+/**
+ * Implementation of [WeakRef] backed by a native [WeakReference].
+ *
+ * Kotlin Native doesn't make a difference between weak and soft references.
+ */
+@ExperimentalWeakApi
+@ExperimentalNativeApi
+actual fun WeakRef(value: T): WeakRef =
+ NativeWeakRef(value)
+
+/**
+ * Implementation of [WeakRef] backed by a native [WeakReference].
+ *
+ * Kotlin Native doesn't make a difference between weak and soft references.
+ */
+@ExperimentalWeakApi
+@ExperimentalNativeApi
+@Suppress("FunctionName")
+actual fun SoftRef(value: T): WeakRef =
+ NativeWeakRef(value)
--
2.51.2
From bfdaa6a675dc78ca5a1285072c8a298de08fee24 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Ivan=20=E2=80=9CCLOVIS=E2=80=9D=20Canet?=
Date: Sun, 8 Sep 2024 18:48:44 +0200
Subject: [PATCH 04/10] feat(weak): Create WeakMap
Closes #153
---
weak/src/commonMain/kotlin/WeakMap.kt | 240 +++++++++++++++++++
weak/src/jsMain/kotlin/WeakMap.js.kt | 72 ++++++
weak/src/jvmMain/kotlin/WeakMap.jvm.kt | 44 ++++
weak/src/nativeMain/kotlin/WeakMap.native.kt | 11 +
4 files changed, 367 insertions(+)
create mode 100644 weak/src/commonMain/kotlin/WeakMap.kt
create mode 100644 weak/src/jsMain/kotlin/WeakMap.js.kt
create mode 100644 weak/src/jvmMain/kotlin/WeakMap.jvm.kt
create mode 100644 weak/src/nativeMain/kotlin/WeakMap.native.kt
diff --git a/weak/src/commonMain/kotlin/WeakMap.kt b/weak/src/commonMain/kotlin/WeakMap.kt
new file mode 100644
index 0000000..0f79838
--- /dev/null
+++ b/weak/src/commonMain/kotlin/WeakMap.kt
@@ -0,0 +1,240 @@
+package opensavvy.pedestal.weak
+
+import kotlin.reflect.KProperty
+
+/**
+ * A weak map is a map in which elements can be garbage-collected at any moment.
+ *
+ * The exact timing in which elements can be garbage-collected is implementation-defined.
+ * Some implementations may allow garbage-collection of keys, some may allow garbage-collection of values,
+ * or some entirely different behavior.
+ *
+ * This class expresses that an association between two values exists, but we may be capable of regenerating it
+ * if it disappears, or we simply don't have an important enough need for it to keep memory.
+ *
+ * Unlike regular [Map], this interface doesn't allow iteration.
+ * It is not possible to observe the contents of this map, since they are ever mutable
+ * and elements may disappear at any time.
+ * Some implementations may provide such observability features.
+ *
+ * ### Obtain instances
+ *
+ * The default implementation is available via the top-level [WeakMap] function.
+ * Other implementations are available in the `algorithms` subpackage.
+ */
+interface WeakMap {
+
+ /**
+ * Gets the value associated with [key] in this map.
+ *
+ * If no value is currently associated with [key], `null` is returned.
+ *
+ * @see Map.get Equivalent method for regular maps.
+ */
+ operator fun get(key: K): V?
+
+ /**
+ * Associates [value] to the [key].
+ *
+ * An implementation may remove this association at any time.
+ *
+ * @see MutableMap.set Equivalent method for regular maps.
+ */
+ operator fun set(key: K, value: V)
+
+ /**
+ * Checks whether a value is currently associated with [key].
+ *
+ * **Note.** Values may be removed at any-time.
+ * It is possible that a [get] call returns `null`,
+ * even if it is directly following a [contains] call that returned `true`.
+ *
+ * @see Map.contains Equivalent method for regular maps.
+ */
+ @ExperimentalWeakApi
+ operator fun contains(key: K): Boolean
+
+ /**
+ * Removes [key] from this map and returns the previously-stored value.
+ *
+ * If no value was previously stored, `null` is returned (same behavior as [get], but in a single operation).
+ *
+ * @see MutableMap.remove Equivalent method for regular maps.
+ */
+ @ExperimentalWeakApi
+ fun remove(key: K): V?
+
+ companion object
+}
+
+// region Constructors
+
+/**
+ * Instantiates a new, empty [WeakMap].
+ *
+ * The map returned by this function has weak keys: keys of the map
+ * may be freed if they are not referred to elsewhere in the program.
+ * When a key is freed, its value is freed as well.
+ *
+ * Values are strongly held: until a key is removed from the map,
+ * it may never be freed by the garbage-collector.
+ *
+ * The way the map decides whether two keys are identical is implementation-defined.
+ * For example, some implementations may use referential equality, some others may
+ * use the [Any.equals] function.
+ */
+@ExperimentalWeakApi
+expect fun WeakMap(): WeakMap
+
+/**
+ * Instantiates a new [WeakMap] by copying [values].
+ *
+ * The map returned by this function has weak keys: keys of the map
+ * may be freed if they are not referred to elsewhere in the program.
+ * When a key is freed, its value is freed as well.
+ *
+ * Values are strongly held: until a key is removed from the map,
+ * it may never be freed by the garbage-collector.
+ *
+ * The way the map decides whether two keys are identical is implementation-defined.
+ * For example, some implementations may use referential equality, some others may
+ * use the [Any.equals] function.
+ */
+@ExperimentalWeakApi
+expect fun WeakMap(values: Map): WeakMap
+
+// endregion
+// region getOrXXX helpers
+
+/**
+ * Attempts to find the value associated with [key], returning [defaultValue] if none is found.
+ */
+@ExperimentalWeakApi
+inline fun WeakMap.getOrDefault(key: K, defaultValue: V): V =
+ get(key) ?: defaultValue
+
+/**
+ * Attempts to find the value associated with [key], returning the result of [defaultValue] if none is found.
+ *
+ * @see Map.getOrElse Equivalent method for regular maps.
+ */
+@ExperimentalWeakApi
+inline fun WeakMap.getOrElse(key: K, defaultValue: () -> V): V =
+ get(key) ?: defaultValue()
+
+/**
+ * Attempts to find the value associated with [key].
+ *
+ * If no value is associated with [key], calls [defaultValue],
+ * stores its result back into the map as well as returns it.
+ *
+ * In a way, this is a sort of simple cache, where a new value is recomputed
+ * if the previous one has been deleted.
+ *
+ * @see MutableMap.getOrPut Equivalent method for regular maps.
+ */
+@ExperimentalWeakApi
+inline fun WeakMap.getOrPut(key: K, defaultValue: () -> V): V =
+ get(key) ?: run {
+ val new = defaultValue()
+ set(key, new)
+ new
+ }
+
+// endregion
+// region Delegation syntax for string-based access
+
+/**
+ * Allows to use a weak map for data-oriented usage.
+ *
+ * ### Example
+ *
+ * ```kotlin
+ * val map = WeakMap()
+ *
+ * var score by map
+ * var age by map
+ *
+ * score = 5
+ * println(age)
+ * ```
+ *
+ * @see Map.getValue Equivalent method for regular maps.
+ */
+@ExperimentalWeakApi
+inline operator fun WeakMap.getValue(thisRef: Any?, property: KProperty<*>): V? =
+ get(property.name)
+
+/**
+ * Allows to use a weak map for data-oriented usage.
+ *
+ * ### Example
+ *
+ * ```kotlin
+ * val map = WeakMap()
+ *
+ * var score by map
+ * var age by map
+ *
+ * score = 5
+ * println(age)
+ * ```
+ *
+ * @see MutableMap.setValue Equivalent method for regular maps.
+ */
+@ExperimentalWeakApi
+inline operator fun WeakMap.setValue(thisRef: Any?, property: KProperty<*>, value: V) {
+ set(property.name, value)
+}
+
+// endregion
+// region Collection helpers
+
+/**
+ * Sets all the values from the provided map into the current one.
+ *
+ * @see MutableMap.putAll Equivalent method for regular maps.
+ */
+@ExperimentalWeakApi
+fun WeakMap.setAll(from: Map) {
+ for ((k, v) in from) {
+ set(k, v)
+ }
+}
+
+/**
+ * Removes the specified keys from this map.
+ */
+@ExperimentalWeakApi
+fun WeakMap.removeAll(from: Iterable) {
+ for (k in from) {
+ remove(k)
+ }
+}
+
+/**
+ * Removes the specified associations from this map.
+ */
+@ExperimentalWeakApi
+fun WeakMap.removeAll(from: Map) {
+ for ((k, v) in from) {
+ remove(k, v)
+ }
+}
+
+// endregion
+// region Other extensions
+
+/**
+ * Removes the association of [key] to [value] from this map.
+ *
+ * If [key] is associated to another value than [value], nothing happens.
+ */
+@ExperimentalWeakApi
+fun WeakMap.remove(key: K, value: V) {
+ if (get(key) == value) {
+ remove(key)
+ }
+}
+
+// endregion
diff --git a/weak/src/jsMain/kotlin/WeakMap.js.kt b/weak/src/jsMain/kotlin/WeakMap.js.kt
new file mode 100644
index 0000000..b08ff70
--- /dev/null
+++ b/weak/src/jsMain/kotlin/WeakMap.js.kt
@@ -0,0 +1,72 @@
+package opensavvy.pedestal.weak
+
+import js.array.tupleOf
+
+private class JsWeakMap(
+ private val wrapped: js.collections.WeakMap
+) : WeakMap {
+ override fun get(key: K): V? {
+ if (key == null)
+ return null
+
+ return wrapped[key]
+ }
+
+ override fun set(key: K, value: V) {
+ if (key == null)
+ return
+
+ wrapped[key] = value
+ }
+
+ @ExperimentalWeakApi
+ override fun contains(key: K): Boolean {
+ if (key == null)
+ return false
+
+ return wrapped.has(key)
+ }
+
+ @ExperimentalWeakApi
+ override fun remove(key: K): V? {
+ if (key == null)
+ return null
+
+ val current = get(key)
+ wrapped.delete(key)
+ return current
+ }
+
+ override fun toString(): String =
+ wrapped.toString()
+}
+
+/**
+ * Instantiates a new, empty [WeakMap].
+ *
+ * This implementation is backed by a JS [WeakMap][js.collections.WeakMap].
+ */
+@ExperimentalWeakApi
+actual fun WeakMap(): WeakMap =
+ JsWeakMap(js.collections.WeakMap())
+
+/**
+ * Instantiates a new [WeakMap] by copying [values].
+ *
+ * This implementation is backed by a JS [WeakMap][js.collections.WeakMap].
+ */
+@ExperimentalWeakApi
+actual fun WeakMap(values: Map): WeakMap {
+ val map = js.collections.WeakMap(
+ values
+ .mapNotNull { (k, v) ->
+ if (k == null)
+ null
+ else
+ tupleOf(k, v)
+ }
+ .toTypedArray()
+ )
+
+ return JsWeakMap(map)
+}
diff --git a/weak/src/jvmMain/kotlin/WeakMap.jvm.kt b/weak/src/jvmMain/kotlin/WeakMap.jvm.kt
new file mode 100644
index 0000000..cb69d2e
--- /dev/null
+++ b/weak/src/jvmMain/kotlin/WeakMap.jvm.kt
@@ -0,0 +1,44 @@
+package opensavvy.pedestal.weak
+
+import java.util.*
+
+private class JavaWeakMap(
+ private val wrapped: WeakHashMap
+) : WeakMap {
+
+ override fun get(key: K): V? =
+ wrapped[key]
+
+ override fun set(key: K, value: V) {
+ wrapped[key] = value
+ }
+
+ @ExperimentalWeakApi
+ override fun contains(key: K): Boolean =
+ wrapped.containsKey(key)
+
+ @ExperimentalWeakApi
+ override fun remove(key: K): V? =
+ wrapped.remove(key)
+
+ override fun toString(): String =
+ wrapped.toString()
+}
+
+/**
+ * Instantiates a new, empty [WeakMap].
+ *
+ * This implementation is backed by a Java [WeakHashMap].
+ */
+@ExperimentalWeakApi
+actual fun WeakMap(): WeakMap =
+ JavaWeakMap(WeakHashMap())
+
+/**
+ * Instantiates a new [WeakMap] by copying [values].
+ *
+ * This implementation is backed by a Java [WeakHashMap].
+ */
+@ExperimentalWeakApi
+actual fun WeakMap(values: Map): WeakMap =
+ JavaWeakMap(WeakHashMap(values))
diff --git a/weak/src/nativeMain/kotlin/WeakMap.native.kt b/weak/src/nativeMain/kotlin/WeakMap.native.kt
new file mode 100644
index 0000000..3c1eca8
--- /dev/null
+++ b/weak/src/nativeMain/kotlin/WeakMap.native.kt
@@ -0,0 +1,11 @@
+package opensavvy.pedestal.weak
+
+@ExperimentalWeakApi
+actual fun WeakMap(): WeakMap {
+ TODO("Not yet implemented")
+}
+
+@ExperimentalWeakApi
+actual fun WeakMap(values: Map): WeakMap {
+ TODO("Not yet implemented")
+}
--
2.51.2
From 245640266ec90228c5589b5a93ce103a6bd7736d Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Ivan=20=E2=80=9CCLOVIS=E2=80=9D=20Canet?=
Date: Sun, 8 Sep 2024 18:50:59 +0200
Subject: [PATCH 05/10] feat(weak): Create fake implementations of WeakRef and
WeakMap
---
.../kotlin/algorithms/EmptyWeakMap.kt | 32 ++++++++++++++++
.../kotlin/algorithms/EmptyWeakRef.kt | 28 ++++++++++++++
.../kotlin/algorithms/FakeWeakMap.kt | 38 +++++++++++++++++++
.../kotlin/algorithms/FakeWeakRef.kt | 26 +++++++++++++
4 files changed, 124 insertions(+)
create mode 100644 weak/src/commonMain/kotlin/algorithms/EmptyWeakMap.kt
create mode 100644 weak/src/commonMain/kotlin/algorithms/EmptyWeakRef.kt
create mode 100644 weak/src/commonMain/kotlin/algorithms/FakeWeakMap.kt
create mode 100644 weak/src/commonMain/kotlin/algorithms/FakeWeakRef.kt
diff --git a/weak/src/commonMain/kotlin/algorithms/EmptyWeakMap.kt b/weak/src/commonMain/kotlin/algorithms/EmptyWeakMap.kt
new file mode 100644
index 0000000..ec28ae1
--- /dev/null
+++ b/weak/src/commonMain/kotlin/algorithms/EmptyWeakMap.kt
@@ -0,0 +1,32 @@
+package opensavvy.pedestal.weak.algorithms
+
+import opensavvy.pedestal.weak.ExperimentalWeakApi
+import opensavvy.pedestal.weak.WeakMap
+
+private class EmptyWeakMapImpl : WeakMap {
+ override fun get(key: K): V? = null
+ override fun set(key: K, value: V) = Unit
+
+ @ExperimentalWeakApi
+ override fun contains(key: K): Boolean = false
+
+ @ExperimentalWeakApi
+ override fun remove(key: K): V? = null
+
+ override fun toString(): String = "EmptyWeakMap"
+}
+
+/**
+ * A [WeakMap] implementation that immediately frees its elements.
+ *
+ * Values passed to [set][WeakMap.set] are never stored, so the map is always empty.
+ *
+ * Use this implementation when testing algorithms that use a weak map, to ensure they don't rely on the value
+ * still existing.
+ *
+ * @see FakeWeakMap Opposite behavior: values are never freed.
+ */
+@Suppress("FunctionName")
+@ExperimentalWeakApi
+fun EmptyWeakMap(): WeakMap =
+ EmptyWeakMapImpl()
diff --git a/weak/src/commonMain/kotlin/algorithms/EmptyWeakRef.kt b/weak/src/commonMain/kotlin/algorithms/EmptyWeakRef.kt
new file mode 100644
index 0000000..1552eba
--- /dev/null
+++ b/weak/src/commonMain/kotlin/algorithms/EmptyWeakRef.kt
@@ -0,0 +1,28 @@
+package opensavvy.pedestal.weak.algorithms
+
+import opensavvy.pedestal.weak.ExperimentalWeakApi
+import opensavvy.pedestal.weak.WeakRef
+
+private object EmptyWeakRefImpl : WeakRef {
+ override fun read(): Nothing? = null
+
+ override fun toString() = "EmptyWeakRef"
+}
+
+/**
+ * A [WeakRef] implementation that doesn't store any value.
+ *
+ * It acts as if there was a value, but it was immediately freed.
+ */
+@Suppress("FunctionName")
+@ExperimentalWeakApi
+fun EmptyWeakRef(): WeakRef =
+ EmptyWeakRefImpl
+
+/**
+ * A [WeakRef] implementation that immediately frees its [value].
+ */
+@Suppress("UNUSED_PARAMETER", "FunctionName")
+@ExperimentalWeakApi
+fun EmptyWeakRef(value: T): WeakRef =
+ EmptyWeakRefImpl
diff --git a/weak/src/commonMain/kotlin/algorithms/FakeWeakMap.kt b/weak/src/commonMain/kotlin/algorithms/FakeWeakMap.kt
new file mode 100644
index 0000000..b931078
--- /dev/null
+++ b/weak/src/commonMain/kotlin/algorithms/FakeWeakMap.kt
@@ -0,0 +1,38 @@
+package opensavvy.pedestal.weak.algorithms
+
+import opensavvy.pedestal.weak.ExperimentalWeakApi
+import opensavvy.pedestal.weak.WeakMap
+
+private class FakeWeakMapImpl : WeakMap {
+ private val map = LinkedHashMap()
+
+ override fun get(key: K): V? =
+ map[key]
+
+ override fun set(key: K, value: V) {
+ map[key] = value
+ }
+
+ @ExperimentalWeakApi
+ override fun contains(key: K): Boolean =
+ map.containsKey(key)
+
+ @ExperimentalWeakApi
+ override fun remove(key: K): V? =
+ map.remove(key)
+
+ override fun toString() = "FakeWeakMap"
+}
+
+/**
+ * A [WeakMap] implementation that isn't weak.
+ *
+ * That is, all stored elements are strongly held and are never freed automatically.
+ * Elements are only removed when [WeakMap.remove] is called by the user.
+ *
+ * @see EmptyWeakMap Opposite behavior: values are immediately freed.
+ */
+@Suppress("FunctionName")
+@ExperimentalWeakApi
+fun FakeWeakMap(): WeakMap =
+ FakeWeakMapImpl()
diff --git a/weak/src/commonMain/kotlin/algorithms/FakeWeakRef.kt b/weak/src/commonMain/kotlin/algorithms/FakeWeakRef.kt
new file mode 100644
index 0000000..006c813
--- /dev/null
+++ b/weak/src/commonMain/kotlin/algorithms/FakeWeakRef.kt
@@ -0,0 +1,26 @@
+package opensavvy.pedestal.weak.algorithms
+
+import opensavvy.pedestal.weak.ExperimentalWeakApi
+import opensavvy.pedestal.weak.WeakRef
+
+/**
+ * Fake implementation of [WeakRef].
+ *
+ * Instead of being freed by the garbage-collector, this implementation is only
+ * freed when [clear] is called.
+ *
+ * Use this implementation to help trigger edge cases in algorithms that use weak references.
+ */
+@ExperimentalWeakApi
+class FakeWeakRef(
+ value: T
+) : WeakRef {
+ private var value: T? = value
+
+ override fun read(): T? =
+ value
+
+ fun clear() {
+ value = null
+ }
+}
--
2.51.2
From 40672738e27f06ce7e9d358af3714a6700627cca Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Ivan=20=E2=80=9CCLOVIS=E2=80=9D=20Canet?=
Date: Sun, 8 Sep 2024 19:16:48 +0200
Subject: [PATCH 06/10] feat(weak): Allow null values in WeakRef
---
weak/src/commonMain/kotlin/WeakRef.kt | 11 ++++++++---
weak/src/commonMain/kotlin/algorithms/FakeWeakRef.kt | 2 +-
weak/src/jsMain/kotlin/WeakRef.js.kt | 11 +++++++----
weak/src/jvmMain/kotlin/WeakRef.jvm.kt | 6 +++---
weak/src/nativeMain/kotlin/WeakRef.native.kt | 10 ++++++----
5 files changed, 25 insertions(+), 15 deletions(-)
diff --git a/weak/src/commonMain/kotlin/WeakRef.kt b/weak/src/commonMain/kotlin/WeakRef.kt
index 8782914..bad16d5 100644
--- a/weak/src/commonMain/kotlin/WeakRef.kt
+++ b/weak/src/commonMain/kotlin/WeakRef.kt
@@ -7,6 +7,11 @@ package opensavvy.pedestal.weak
* **at any time**. Garbage collectors are complex machinery. One should take care _not_ to write code that depends
* on the behavior of a specific garbage collector, as that behavior can change between future versions.
*
+ * Storing `null` is technically allowed for convenience reasons, but doesn't make much sense.
+ * It is not possible to distinguish between a weak reference that stores `null` and one that used store a value
+ * but has been cleared.
+ * Therefore, implementations are free to not store `null` values at all.
+ *
* ### Obtain instances
*
* The default implementations are available via the top-level [WeakRef] and [SoftRef] functions.
@@ -16,7 +21,7 @@ package opensavvy.pedestal.weak
*
* This interface is not stable for inheritance. We may add new methods at any time.
*/
-interface WeakRef {
+interface WeakRef {
/**
* Attempts to read the value held by this reference.
@@ -57,7 +62,7 @@ interface WeakRef {
* @see SoftRef For implementing caches
*/
@ExperimentalWeakApi
-expect fun WeakRef(value: T): WeakRef
+expect fun WeakRef(value: T): WeakRef
/**
* Instantiates a soft reference: a reference to a [value] that doesn't stop
@@ -80,4 +85,4 @@ expect fun WeakRef(value: T): WeakRef
*/
@ExperimentalWeakApi
@Suppress("FunctionName")
-expect fun SoftRef(value: T): WeakRef
+expect fun SoftRef(value: T): WeakRef
diff --git a/weak/src/commonMain/kotlin/algorithms/FakeWeakRef.kt b/weak/src/commonMain/kotlin/algorithms/FakeWeakRef.kt
index 006c813..495e4d6 100644
--- a/weak/src/commonMain/kotlin/algorithms/FakeWeakRef.kt
+++ b/weak/src/commonMain/kotlin/algorithms/FakeWeakRef.kt
@@ -12,7 +12,7 @@ import opensavvy.pedestal.weak.WeakRef
* Use this implementation to help trigger edge cases in algorithms that use weak references.
*/
@ExperimentalWeakApi
-class FakeWeakRef(
+class FakeWeakRef(
value: T
) : WeakRef {
private var value: T? = value
diff --git a/weak/src/jsMain/kotlin/WeakRef.js.kt b/weak/src/jsMain/kotlin/WeakRef.js.kt
index 5a6e536..873ab07 100644
--- a/weak/src/jsMain/kotlin/WeakRef.js.kt
+++ b/weak/src/jsMain/kotlin/WeakRef.js.kt
@@ -1,5 +1,7 @@
package opensavvy.pedestal.weak
+import opensavvy.pedestal.weak.algorithms.EmptyWeakRef
+
private class JsWeakRef(
value: T
) : WeakRef {
@@ -20,8 +22,9 @@ private class JsWeakRef(
* JS doesn't make a difference between weak and soft references.
*/
@ExperimentalWeakApi
-actual fun WeakRef(value: T): WeakRef =
- JsWeakRef(value)
+actual fun WeakRef(value: T): WeakRef =
+ if (value == null) EmptyWeakRef(value)
+ else JsWeakRef(value)
/**
* Implementation of [WeakRef] backed by a JS [WeakRef][js.memory.WeakRef].
@@ -30,5 +33,5 @@ actual fun WeakRef(value: T): WeakRef =
*/
@ExperimentalWeakApi
@Suppress("FunctionName")
-actual fun SoftRef(value: T): WeakRef =
- JsWeakRef(value)
+actual fun SoftRef(value: T): WeakRef =
+ WeakRef(value)
diff --git a/weak/src/jvmMain/kotlin/WeakRef.jvm.kt b/weak/src/jvmMain/kotlin/WeakRef.jvm.kt
index 910c6b3..9c02cde 100644
--- a/weak/src/jvmMain/kotlin/WeakRef.jvm.kt
+++ b/weak/src/jvmMain/kotlin/WeakRef.jvm.kt
@@ -4,7 +4,7 @@ import java.lang.ref.Reference
import java.lang.ref.SoftReference
import java.lang.ref.WeakReference
-private class JavaReferenceHolder(
+private class JavaReferenceHolder(
private val reference: Reference,
) : WeakRef {
@@ -25,7 +25,7 @@ fun WeakRef.Companion.fromJava(reference: Reference): WeakRef =
* Implementation of [WeakRef] backed by a JVM [WeakReference].
*/
@ExperimentalWeakApi
-actual fun WeakRef(value: T): WeakRef =
+actual fun WeakRef(value: T): WeakRef =
JavaReferenceHolder(WeakReference(value))
/**
@@ -33,5 +33,5 @@ actual fun WeakRef(value: T): WeakRef =
*/
@ExperimentalWeakApi
@Suppress("FunctionName")
-actual fun SoftRef(value: T): WeakRef =
+actual fun SoftRef(value: T): WeakRef =
JavaReferenceHolder(SoftReference(value))
diff --git a/weak/src/nativeMain/kotlin/WeakRef.native.kt b/weak/src/nativeMain/kotlin/WeakRef.native.kt
index 7b452af..5101dd5 100644
--- a/weak/src/nativeMain/kotlin/WeakRef.native.kt
+++ b/weak/src/nativeMain/kotlin/WeakRef.native.kt
@@ -1,5 +1,6 @@
package opensavvy.pedestal.weak
+import opensavvy.pedestal.weak.algorithms.EmptyWeakRef
import kotlin.experimental.ExperimentalNativeApi
import kotlin.native.ref.WeakReference
@@ -22,8 +23,9 @@ private class NativeWeakRef(
*/
@ExperimentalWeakApi
@ExperimentalNativeApi
-actual fun WeakRef(value: T): WeakRef =
- NativeWeakRef(value)
+actual fun WeakRef(value: T): WeakRef =
+ if (value == null) EmptyWeakRef(value)
+ else NativeWeakRef(value)
/**
* Implementation of [WeakRef] backed by a native [WeakReference].
@@ -33,5 +35,5 @@ actual fun WeakRef(value: T): WeakRef =
@ExperimentalWeakApi
@ExperimentalNativeApi
@Suppress("FunctionName")
-actual fun SoftRef(value: T): WeakRef =
- NativeWeakRef(value)
+actual fun SoftRef(value: T): WeakRef =
+ WeakRef(value)
--
2.51.2
From 282637ed20843a9f4e41ecae2ce68b3693ede200 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Ivan=20=E2=80=9CCLOVIS=E2=80=9D=20Canet?=
Date: Sun, 8 Sep 2024 19:23:52 +0200
Subject: [PATCH 07/10] feat(weak): Allow null values in WeakMap
---
weak/src/commonMain/kotlin/WeakMap.kt | 24 ++++++++++----------
weak/src/jsMain/kotlin/WeakMap.js.kt | 6 ++---
weak/src/jvmMain/kotlin/WeakMap.jvm.kt | 6 ++---
weak/src/nativeMain/kotlin/WeakMap.native.kt | 4 ++--
4 files changed, 20 insertions(+), 20 deletions(-)
diff --git a/weak/src/commonMain/kotlin/WeakMap.kt b/weak/src/commonMain/kotlin/WeakMap.kt
index 0f79838..43dff0e 100644
--- a/weak/src/commonMain/kotlin/WeakMap.kt
+++ b/weak/src/commonMain/kotlin/WeakMap.kt
@@ -22,7 +22,7 @@ import kotlin.reflect.KProperty
* The default implementation is available via the top-level [WeakMap] function.
* Other implementations are available in the `algorithms` subpackage.
*/
-interface WeakMap {
+interface WeakMap {
/**
* Gets the value associated with [key] in this map.
@@ -84,7 +84,7 @@ interface WeakMap {
* use the [Any.equals] function.
*/
@ExperimentalWeakApi
-expect fun WeakMap(): WeakMap
+expect fun WeakMap(): WeakMap
/**
* Instantiates a new [WeakMap] by copying [values].
@@ -101,7 +101,7 @@ expect fun WeakMap(): WeakMap
* use the [Any.equals] function.
*/
@ExperimentalWeakApi
-expect fun WeakMap(values: Map): WeakMap
+expect fun WeakMap(values: Map): WeakMap
// endregion
// region getOrXXX helpers
@@ -110,7 +110,7 @@ expect fun WeakMap(values: Map): WeakMap
* Attempts to find the value associated with [key], returning [defaultValue] if none is found.
*/
@ExperimentalWeakApi
-inline fun WeakMap.getOrDefault(key: K, defaultValue: V): V =
+inline fun WeakMap.getOrDefault(key: K, defaultValue: V): V =
get(key) ?: defaultValue
/**
@@ -119,7 +119,7 @@ inline fun WeakMap.getOrDefault(key: K, defaultValue: V): V =
* @see Map.getOrElse Equivalent method for regular maps.
*/
@ExperimentalWeakApi
-inline fun WeakMap.getOrElse(key: K, defaultValue: () -> V): V =
+inline fun WeakMap.getOrElse(key: K, defaultValue: () -> V): V =
get(key) ?: defaultValue()
/**
@@ -134,7 +134,7 @@ inline fun WeakMap.getOrElse(key: K, defaultValue: () -> V):
* @see MutableMap.getOrPut Equivalent method for regular maps.
*/
@ExperimentalWeakApi
-inline fun WeakMap.getOrPut(key: K, defaultValue: () -> V): V =
+inline fun WeakMap.getOrPut(key: K, defaultValue: () -> V): V =
get(key) ?: run {
val new = defaultValue()
set(key, new)
@@ -162,7 +162,7 @@ inline fun WeakMap.getOrPut(key: K, defaultValue: () -> V): V
* @see Map.getValue Equivalent method for regular maps.
*/
@ExperimentalWeakApi
-inline operator fun WeakMap.getValue(thisRef: Any?, property: KProperty<*>): V? =
+inline operator fun WeakMap.getValue(thisRef: Any?, property: KProperty<*>): V? =
get(property.name)
/**
@@ -183,7 +183,7 @@ inline operator fun WeakMap.getValue(thisRef: Any?, propert
* @see MutableMap.setValue Equivalent method for regular maps.
*/
@ExperimentalWeakApi
-inline operator fun WeakMap.setValue(thisRef: Any?, property: KProperty<*>, value: V) {
+inline operator fun WeakMap.setValue(thisRef: Any?, property: KProperty<*>, value: V) {
set(property.name, value)
}
@@ -196,7 +196,7 @@ inline operator fun WeakMap.setValue(thisRef: Any?, propert
* @see MutableMap.putAll Equivalent method for regular maps.
*/
@ExperimentalWeakApi
-fun WeakMap.setAll(from: Map) {
+fun WeakMap.setAll(from: Map) {
for ((k, v) in from) {
set(k, v)
}
@@ -206,7 +206,7 @@ fun WeakMap.setAll(from: Map) {
* Removes the specified keys from this map.
*/
@ExperimentalWeakApi
-fun WeakMap.removeAll(from: Iterable) {
+fun WeakMap.removeAll(from: Iterable) {
for (k in from) {
remove(k)
}
@@ -216,7 +216,7 @@ fun WeakMap.removeAll(from: Iterable) {
* Removes the specified associations from this map.
*/
@ExperimentalWeakApi
-fun WeakMap.removeAll(from: Map) {
+fun WeakMap.removeAll(from: Map) {
for ((k, v) in from) {
remove(k, v)
}
@@ -231,7 +231,7 @@ fun WeakMap.removeAll(from: Map) {
* If [key] is associated to another value than [value], nothing happens.
*/
@ExperimentalWeakApi
-fun WeakMap.remove(key: K, value: V) {
+fun WeakMap.remove(key: K, value: V) {
if (get(key) == value) {
remove(key)
}
diff --git a/weak/src/jsMain/kotlin/WeakMap.js.kt b/weak/src/jsMain/kotlin/WeakMap.js.kt
index b08ff70..89bc5ea 100644
--- a/weak/src/jsMain/kotlin/WeakMap.js.kt
+++ b/weak/src/jsMain/kotlin/WeakMap.js.kt
@@ -2,7 +2,7 @@ package opensavvy.pedestal.weak
import js.array.tupleOf
-private class JsWeakMap(
+private class JsWeakMap(
private val wrapped: js.collections.WeakMap
) : WeakMap {
override fun get(key: K): V? {
@@ -47,7 +47,7 @@ private class JsWeakMap(
* This implementation is backed by a JS [WeakMap][js.collections.WeakMap].
*/
@ExperimentalWeakApi
-actual fun WeakMap(): WeakMap =
+actual fun WeakMap(): WeakMap =
JsWeakMap(js.collections.WeakMap())
/**
@@ -56,7 +56,7 @@ actual fun WeakMap(): WeakMap =
* This implementation is backed by a JS [WeakMap][js.collections.WeakMap].
*/
@ExperimentalWeakApi
-actual fun WeakMap(values: Map): WeakMap {
+actual fun WeakMap(values: Map): WeakMap {
val map = js.collections.WeakMap