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

commonMain.jetbrains.datalore.plot.config.OptionsAccessor.kt Maven / Gradle / Ivy

There is a newer version: 4.5.3-alpha1
Show newest version
/*
 * Copyright (c) 2019. JetBrains s.r.o.
 * Use of this source code is governed by the MIT license that can be found in the LICENSE file.
 */

package jetbrains.datalore.plot.config

import jetbrains.datalore.base.gcommon.collect.ClosedRange
import jetbrains.datalore.base.values.Color
import jetbrains.datalore.plot.base.Aes
import jetbrains.datalore.plot.base.render.point.PointShape
import jetbrains.datalore.plot.config.aes.AesOptionConversion
import kotlin.jvm.JvmOverloads

open class OptionsAccessor(
    private val options: Map,
    private val defaultOptions: Map = emptyMap()
) {
    val mergedOptions: Map
        get() = defaultOptions + options

    val isEmpty: Boolean
        get() = options.isEmpty() && defaultOptions.isEmpty()

    fun update(key: String, value: Any) {
        @Suppress("UNCHECKED_CAST")
        (options as MutableMap)[key] = value
    }

    protected fun update(otherOptions: Map) {
        (options as MutableMap).putAll(otherOptions)
    }

    fun has(option: String): Boolean {
        return hasOwn(option) || defaultOptions[option] != null
    }

    fun hasOwn(option: String): Boolean {
        return options[option] != null
    }

    operator fun get(option: String): Any? {
        return if (hasOwn(option)) {
            options[option]
        } else {
            defaultOptions[option]
        }
    }

    fun getSafe(option: String): Any {
        return get(option) ?: throw IllegalStateException("Option `$option` not found.")
    }

    fun getString(option: String): String? {
        return get(option)?.toString()
    }

    fun getStringSafe(option: String): String {
        return getString(option)
            ?: throw IllegalArgumentException("Can't get string value: option '$option' is not present.")
    }

    fun getList(option: String): List<*> {
        val v = get(option) ?: return ArrayList()
        require(v is List<*>) { "Not a List: $option: ${v::class.simpleName}" }
        return v
    }

    fun getDoubleList(option: String): List {
        val list = getNumList(option)
        return list.map { it.toDouble() }
    }

    fun getNumPair(option: String): Pair {
        val list = getNumList(option) { it is Number }
        @Suppress("UNCHECKED_CAST")
        return pickTwo(option, list) as Pair
    }

    fun getNumQPair(option: String): Pair {
        val list = getNumList(option) { it == null || it is Number }
        return pickTwo(option, list)
    }

    fun getNumPairDef(option: String, def: Pair): Pair {
        return if (has(option)) {
            getNumPair(option)
        } else {
            def
        }
    }

    fun getNumQPairDef(option: String, def: Pair): Pair {
        return if (has(option)) {
            getNumQPair(option)
        } else {
            def
        }
    }

    private fun  pickTwo(option: String, list: List): Pair {
        require(list.size >= 2) { "$option requires a list of 2 but was ${list.size}" }
        return Pair(list[0], list[1])
    }

    @Suppress("UNCHECKED_CAST")
    fun getNumList(option: String): List = getNumList(option) { it is Number } as List

    fun getNumQList(option: String): List = getNumList(option) { it == null || it is Number }

    private fun getNumber(option: String): Number? {
        val v = get(option) ?: return null

        require(v is Number) { "Parameter '$option' expected to be a Number, but was ${v::class.simpleName}" }

        return v;
    }

    private fun getNumList(option: String, predicate: (Any?) -> Boolean): List {
        val list = getList(option)

        requireAll(list, predicate) { "$option requires a list of numbers but not numeric encountered: $it" }

        @Suppress("UNCHECKED_CAST")
        return list as List
    }

    fun getAsList(option: String): List {
        val v = get(option) ?: emptyList()
        return if (v is List<*>) {
            v
        } else {
            listOf(v)
        }
    }

    fun getAsStringList(option: String): List {
//        val v = get(option) ?: emptyList()
//        return if (v is List<*>) {
//            v.filterNotNull().map { it.toString() }
//        } else {
//            listOf(v.toString())
//        }
        return getAsList(option).filterNotNull().map { it.toString() }
    }

    fun getStringList(option: String): List {
        val list = getList(option)

        requireAll(list, { it is String }) { "$option requires a list of strings but not string encountered: $it" }

        @Suppress("UNCHECKED_CAST")
        return list as List
    }

    internal fun getRange(option: String): ClosedRange {
        require(has(option)) { "'Range' value is expected in form: [min, max]" }

        val range = getRangeOrNull(option)

        requireNotNull(range) { "'range' value is expected in form: [min, max] but was: ${get(option)}" }

        return range
    }

    fun getRangeOrNull(option: String): ClosedRange? {
        val pair = get(option)
        if ((pair is List<*> && pair.size == 2 && pair.all { it is Number }) != true) {
            return null
        }

        val lower = (pair.first() as Number).toDouble()
        val upper = (pair.last() as Number).toDouble()

        return try {
            ClosedRange(lower, upper)
        } catch (ex: Throwable) {
            null
        }
    }

    fun getMap(option: String): Map {
        val v = get(option) ?: return emptyMap()
        require(v is Map<*, *>) { "Not a Map: " + option + ": " + v::class.simpleName }

        @Suppress("UNCHECKED_CAST")
        return v as Map
    }

    @JvmOverloads
    fun getBoolean(option: String, def: Boolean = false): Boolean {
        val v = get(option)
        return v as? Boolean ?: def
    }

    fun getDouble(option: String): Double? {
        return getNumber(option)?.toDouble()
    }

    fun getInteger(option: String): Int? {
        return getNumber(option)?.toInt()
    }

    fun getLong(option: String): Long? {
        return getNumber(option)?.toLong()
    }

    fun getDoubleDef(option: String, def: Double): Double {
        return getDouble(option) ?: def
    }

    fun getIntegerDef(option: String, def: Int): Int {
        return getInteger(option) ?: def
    }

    fun getLongDef(option: String, def: Long): Long {
        return getLong(option) ?: def
    }

    private fun  getValueOrNull(option: String, mapper: (Any?) -> T?): T? {
        val v = get(option) ?: return null
        return mapper(v)
    }

    fun getColor(option: String): Color? {
        return getValue(Aes.COLOR, option)
    }

    fun getShape(option: String): PointShape? {
        return getValue(Aes.SHAPE, option)
    }

    protected fun  getValue(aes: Aes, option: String): T? {
        val v = get(option) ?: return null
        return AesOptionConversion.apply(aes, v)
    }

    companion object {
        fun over(map: Map): OptionsAccessor {
            return OptionsAccessor(map)
        }

        private fun  requireAll(items: Iterable, predicate: (T) -> Boolean, lazy: (T) -> Any) {
            items.filterNot { predicate(it) }.firstOrNull()?.let { require(false) { lazy(it) } }
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy