All Downloads are FREE. Search and download functionalities are using the official Maven repository.

commonMain.com.russhwolf.settings.MockSettings.kt Maven / Gradle / Ivy

There is a newer version: 1.3.0
Show newest version
/*
 * Copyright 2020 Russell Wolf
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.russhwolf.settings

/**
 * 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 `MockSettings` implementation is intended for use in unit tests. The mock persisted state is represented by a
 * [Map] and can be injected at construction time.
 *
 * Operator extensions are defined in order to simplify usage. In addition, property delegates are provided for cleaner
 * syntax and better type-safety when interacting with values stored in a `Settings` instance.
 *
 * This class can be instantiated by wrapping a [MutableMap] or set of [Pair] entries, or via a [Factory].
 *
 * 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.
 */
@OptIn(ExperimentalSettingsApi::class)
public class MockSettings public constructor(private val delegate: MutableMap = mutableMapOf()) :
    ObservableSettings {
    private val listeners = mutableListOf<() -> Any>()
    private fun invokeListeners() = listeners.forEach { it() }

    public constructor(vararg items: Pair) : this(mutableMapOf(*items))

    /**
     * A factory that can produce [Settings] instances.
     *
     * This implementation will use the same backing [Map] if the same `name` parameter is passed to [create].
     *
     * By default the backing maps produced by this `Factory` are created using the [mutableMapOf] function, but this
     * is configurable by changing the [mapFactory] parameter. The [delegateCache] parameter can be used to control the
     * `Map` implementation used by the cache that stores these delegates.
     */
    public class Factory(
        private val mapFactory: () -> MutableMap = ::mutableMapOf,
        private val delegateCache: MutableMap> = mutableMapOf()
    ) : Settings.Factory {

        /**
         * Assigns the values in [delegate] to the cache that will be used to back any [MockSettings] this factory
         * creates named [name]
         */
        public fun setCacheValues(name: String?, delegate: Map) {
            val map = delegateCache.getOrPut(name, mapFactory)
            map.clear()
            map.putAll(delegate)
        }

        /**
         * Assigns the values in [items] to the cache that will be used to back any [MockSettings] this factory
         * creates named [name]
         */
        public fun setCacheValues(name: String?, vararg items: Pair) {
            setCacheValues(name, mapFactory().apply { putAll(items) })
        }

        public override fun create(name: String?): Settings {
            val delegate = delegateCache.getOrPut(name, mapFactory)
            return MockSettings(delegate)
        }
    }

    public override val keys: Set get() = delegate.keys
    public override val size: Int get() = delegate.size

    public override fun clear() {
        delegate.clear()
        invokeListeners()
    }

    public override fun remove(key: String) {
        delegate -= key
        invokeListeners()
    }

    public override fun hasKey(key: String): Boolean = key in delegate

    public override fun putInt(key: String, value: Int) {
        delegate[key] = value
        invokeListeners()
    }

    public override fun getInt(key: String, defaultValue: Int): Int = delegate[key] as? Int ?: defaultValue

    public override fun getIntOrNull(key: String): Int? = delegate[key] as? Int

    public override fun putLong(key: String, value: Long) {
        delegate[key] = value
        invokeListeners()
    }

    public override fun getLong(key: String, defaultValue: Long): Long = delegate[key] as? Long ?: defaultValue

    public override fun getLongOrNull(key: String): Long? = delegate[key] as? Long

    public override fun putString(key: String, value: String) {
        delegate[key] = value
        invokeListeners()
    }

    public override fun getString(key: String, defaultValue: String): String = delegate[key] as? String ?: defaultValue

    public override fun getStringOrNull(key: String): String? = delegate[key] as? String

    public override fun putFloat(key: String, value: Float) {
        delegate[key] = value
        invokeListeners()
    }

    public override fun getFloat(key: String, defaultValue: Float): Float = delegate[key] as? Float ?: defaultValue

    public override fun getFloatOrNull(key: String): Float? = delegate[key] as? Float

    public override fun putDouble(key: String, value: Double) {
        delegate[key] = value
        invokeListeners()
    }

    public override fun getDouble(key: String, defaultValue: Double): Double = delegate[key] as? Double ?: defaultValue

    public override fun getDoubleOrNull(key: String): Double? = delegate[key] as? Double

    public override fun putBoolean(key: String, value: Boolean) {
        delegate[key] = value
        invokeListeners()
    }

    public override fun getBoolean(key: String, defaultValue: Boolean): Boolean =
        delegate[key] as? Boolean ?: defaultValue

    public override fun getBooleanOrNull(key: String): Boolean? = delegate[key] as? Boolean

    @ExperimentalSettingsApi
    public override fun addListener(key: String, callback: () -> Unit): SettingsListener {
        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 created in [addListener] so it can be passed to [removeListener]
     *
     * In the [MockSettings] 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 `MockSettings` instance that originally created the listener.
     */
    @ExperimentalSettingsApi
    public class Listener internal constructor(
        private val listeners: MutableList<() -> Any>,
        private val listener: () -> Unit
    ) : SettingsListener {
        public override fun deactivate() {
            listeners -= listener
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy