commonMain.io.telereso.kmp.core.MapSettings.kt Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of core Show documentation
Show all versions of core Show documentation
Core classes and tools while developing kmp projects
The newest version!
/*
* MIT License
*
* Copyright (c) 2023 Telereso
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package io.telereso.kmp.core
import io.telereso.kmp.core.Log.logDebug
import io.telereso.kmp.core.Utils.launchPeriodicAsync
import io.telereso.kmp.core.models.ExpirableValue
import io.telereso.kmp.core.models.fromJson
import io.telereso.kmp.core.models.toJson
import kotlinx.coroutines.Deferred
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flowOf
import kotlin.time.Duration
/**
* A collection of storage-backed key-value data
*
* This class allows storage of values with the [Int], [Long], [String], [Float], [Double], or [Boolean] types, using a
* [String] reference as a key.
*
* The `CommonMapSettings` implementation is intended for use in unit tests. It differs from production implementations
* because the state exists only in-memory and has no mechanism for persistence.
*
* This class can be instantiated by wrapping a [MutableMap] or set of [Pair] entries.
*
* This implementation is verified against the same test suite as the real platform-specific implementations to ensure
* it shares the same behavior, assuming the default [mutableMapOf] delegate is used.
*/
class MapSettings constructor(
private val delegate: MutableMap = mutableMapOf(),
clearExpiredKeysDuration: Duration? = null
) :
ObservableSettings {
private val listeners = mutableListOf<() -> Any>()
private fun invokeListeners() = listeners.forEach { it() }
constructor(vararg items: Pair) : this(mutableMapOf(*items))
override var listener: Settings.Listener? = null
private var removeExpiredJob : Deferred? = null
init {
clearExpiredKeysDuration?.let {
removeExpiredJob = ContextScope.get(DispatchersProvider.Default)
.launchPeriodicAsync(it) {
removeExpiredKeys()
}
}
}
override val keys: Set get() = delegate.keys
override val size: Int get() = delegate.size
override fun clear() {
delegate.clear()
invokeListeners()
}
override fun removeExpiredKeys() {
logDebug("RemoveExpiredKeys - size: ${delegate.size}")
delegate.keys.forEach {
getExpirableString(it)
}
listener?.onRemoveExpiredKeys()
}
override fun cancelRemovingExpiredKeys() {
removeExpiredJob?.cancel()
removeExpiredJob = null
}
override fun remove(key: String) {
delegate -= key
invokeListeners()
}
override fun hasKey(key: String): Boolean = key in delegate
override fun putInt(key: String, value: Int) {
delegate[key] = value
invokeListeners()
}
override fun getInt(key: String, defaultValue: Int): Int = delegate[key] as? Int ?: defaultValue
override fun getIntOrNull(key: String): Int? = delegate[key] as? Int
override fun putLong(key: String, value: Long) {
delegate[key] = value
invokeListeners()
}
override fun getLong(key: String, defaultValue: Long): Long =
delegate[key] as? Long ?: defaultValue
override fun getLongOrNull(key: String): Long? = delegate[key] as? Long
override fun putString(key: String, value: String) {
delegate[key] = value
invokeListeners()
}
override fun getString(key: String, defaultValue: String): String =
delegate[key] as? String ?: defaultValue
override fun getStringOrNull(key: String): String? = delegate[key] as? String
override fun putFloat(key: String, value: Float) {
delegate[key] = value
invokeListeners()
}
override fun getFloat(key: String, defaultValue: Float): Float =
delegate[key] as? Float ?: defaultValue
override fun getFloatOrNull(key: String): Float? = delegate[key] as? Float
override fun putDouble(key: String, value: Double) {
delegate[key] = value
invokeListeners()
}
override fun getDouble(key: String, defaultValue: Double): Double =
delegate[key] as? Double ?: defaultValue
override fun getDoubleOrNull(key: String): Double? = delegate[key] as? Double
override fun putBoolean(key: String, value: Boolean) {
delegate[key] = value
invokeListeners()
}
override fun getBoolean(key: String, defaultValue: Boolean): Boolean =
delegate[key] as? Boolean ?: defaultValue
override fun getBooleanOrNull(key: String): Boolean? = delegate[key] as? Boolean
override fun getIntFlow(key: String, defaultValue: Int): Flow {
return flowOf(delegate[key] as? Int ?: defaultValue)
}
override fun getIntFlow(key: String): Flow {
return flowOf(delegate[key] as? Int)
}
override fun getStringFlow(key: String, defaultValue: String): Flow {
return flowOf(delegate[key] as? String ?: defaultValue)
}
override fun getStringFlow(key: String): Flow {
return flowOf(delegate[key] as? String)
}
override fun getLongFlow(key: String): Flow {
return flowOf(delegate[key] as? Long)
}
override fun getLongFlow(key: String, defaultValue: Long): Flow {
return flowOf(delegate[key] as? Long ?: defaultValue)
}
override fun getFloatFlow(key: String, defaultValue: Float): Flow {
return flowOf(delegate[key] as? Float ?: defaultValue)
}
override fun getFloatFlow(key: String): Flow {
return flowOf(delegate[key] as? Float)
}
override fun getDoubleFlow(key: String, defaultValue: Double): Flow {
return flowOf(delegate[key] as? Double ?: defaultValue)
}
override fun getDoubleFlow(key: String): Flow {
return flowOf(delegate[key] as? Double)
}
override fun getBooleanFlow(key: String, defaultValue: Boolean): Flow {
return flowOf(delegate[key] as? Boolean ?: defaultValue)
}
override fun getBooleanFlow(key: String): Flow {
return flowOf(delegate[key] as? Boolean)
}
override fun putExpirableString(key: String, value: String, exp: Long) {
putString(key, ExpirableValue(value, exp).toJson())
}
override fun getExpirableString(key: String, default: String?): String? {
val v = getStringOrNull(key) ?: return default
val ev = ExpirableValue.fromJson(v)
if (Utils.isExpired(ev.exp)) {
logDebug("Removing expired - $key")
remove(key)
return default
}
return ev.value
}
override fun getExpirableString(key: String): String? {
return getExpirableString(key, null)
}
override fun addIntListener(
key: String,
defaultValue: Int,
callback: (Int) -> Unit
): Settings.Listener =
addListener(key) { callback(getInt(key, defaultValue)) }
override fun addLongListener(
key: String,
defaultValue: Long,
callback: (Long) -> Unit
): Settings.Listener =
addListener(key) { callback(getLong(key, defaultValue)) }
override fun addStringListener(
key: String,
defaultValue: String,
callback: (String) -> Unit
): Settings.Listener =
addListener(key) { callback(getString(key, defaultValue)) }
override fun addFloatListener(
key: String,
defaultValue: Float,
callback: (Float) -> Unit
): Settings.Listener =
addListener(key) { callback(getFloat(key, defaultValue)) }
override fun addDoubleListener(
key: String,
defaultValue: Double,
callback: (Double) -> Unit
): Settings.Listener =
addListener(key) { callback(getDouble(key, defaultValue)) }
override fun addBooleanListener(
key: String,
defaultValue: Boolean,
callback: (Boolean) -> Unit
): Settings.Listener =
addListener(key) { callback(getBoolean(key, defaultValue)) }
override fun addIntOrNullListener(
key: String,
callback: (Int?) -> Unit
): Settings.Listener =
addListener(key) { callback(getIntOrNull(key)) }
override fun addLongOrNullListener(
key: String,
callback: (Long?) -> Unit
): Settings.Listener =
addListener(key) { callback(getLongOrNull(key)) }
override fun addStringOrNullListener(
key: String,
callback: (String?) -> Unit
): Settings.Listener =
addListener(key) { callback(getStringOrNull(key)) }
override fun addFloatOrNullListener(
key: String,
callback: (Float?) -> Unit
): Settings.Listener =
addListener(key) { callback(getFloatOrNull(key)) }
override fun addDoubleOrNullListener(
key: String,
callback: (Double?) -> Unit
): Settings.Listener =
addListener(key) { callback(getDoubleOrNull(key)) }
override fun addBooleanOrNullListener(
key: String,
callback: (Boolean?) -> Unit
): Settings.Listener =
addListener(key) { callback(getBooleanOrNull(key)) }
private fun addListener(key: String, callback: () -> Unit): Settings.Listener {
var prev = delegate[key]
val listener = {
val current = delegate[key]
if (prev != current) {
callback()
prev = current
}
}
listeners += listener
return Listener(listeners, listener)
}
/**
* A handle to a listener instance returned by one of the addListener methods of [ObservableSettings], so it can be
* deactivated as needed.
*
* In the [CommonMapSettings] implementation this simply wraps a lambda parameter which is being called whenever a
* mutating API is called. Unlike platform implementations, this listener will NOT be called if the underlying map
* is mutated by something other than the `MapSettings` instance that originally created the listener.
*/
class Listener internal constructor(
private val listeners: MutableList<() -> Any>,
private val listener: () -> Unit
) : Settings.Listener {
override fun deactivate() {
listeners -= listener
}
override fun onRemoveExpiredKeys() {
}
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy