com.github.bjoernpetersen.jmusicbot.config.Config.kt Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of musicbot Show documentation
Show all versions of musicbot Show documentation
Core library of MusicBot, which plays music from various providers.
package com.github.bjoernpetersen.jmusicbot.config
import com.github.bjoernpetersen.jmusicbot.Loggable
import com.github.bjoernpetersen.jmusicbot.MusicBot
import com.github.bjoernpetersen.jmusicbot.config.ui.CheckBox
import com.github.bjoernpetersen.jmusicbot.config.ui.PasswordBox
import com.github.bjoernpetersen.jmusicbot.config.ui.TextBox
import com.github.bjoernpetersen.jmusicbot.config.ui.UiNode
import com.github.bjoernpetersen.jmusicbot.platform.HostServices
import java.util.*
import java.util.logging.Logger
/**
* Some global settings of the MusicBot.
*/
interface Defaults {
/**
* The path to the plugin folder.
*/
val pluginFolder: Config.StringEntry
/**
* A list of all default entries.
*/
val entries: List
get() = listOf(pluginFolder)
}
/**
* A persistent config holding Boolean and String entries.
* @param adapter a ConfigStorageAdapter to load and save the config with
* @param hostServices the HostServices implementation to use in a session with this config.
*/
class Config(private val adapter: ConfigStorageAdapter, val hostServices: HostServices) : Loggable {
private val logger: Logger = createLogger()
private val config: MutableMap
private val secrets: MutableMap
private val entries: MutableMap
init {
this.config = HashMap(adapter.loadPlaintext())
this.secrets = HashMap(adapter.loadSecrets())
this.entries = HashMap()
}
override fun getLogger(): Logger {
return logger
}
private fun qualify(base: Class<*>, key: String): String = base.name + "." + key
private fun getValue(base: Class<*>, key: String): String? {
return config[qualify(base, key)]
}
private fun setValue(base: Class<*>, key: String, value: String?) {
val old = getValue(base, key)
if (old != value) {
val qualified = qualify(base, key)
logFiner("Config entry '%s' changed from '%s' to '%s'", qualified, getValue(base, key), value)
if (value == null) {
config.remove(qualified)
} else {
config.put(qualified, value)
}
adapter.storePlaintext(config)
}
}
private fun getSecret(base: Class<*>, key: String): String? {
return secrets[qualify(base, key)]
}
private fun setSecret(base: Class<*>, key: String, value: String?) {
val old = getSecret(base, key)
if (old != value) {
val qualified = qualify(base, key)
logFiner("Secret '%s' changed.", qualified)
if (value == null) {
secrets.remove(qualified)
} else {
secrets.put(qualified, value)
}
adapter.storeSecrets(secrets)
}
}
private fun addEntry(base: Class<*>, key: String, entry: Entry) {
entries.put(qualify(base, key), entry)
}
private fun removeEntry(base: Class<*>, key: String) {
entries.remove(qualify(base, key))
}
private fun getEntry(base: Class<*>, key: String): Entry? {
return entries[qualify(base, key)]
}
private fun checkExists(type: Class<*>, key: String) {
if (getEntry(type, key) != null)
throw IllegalArgumentException("Config entry already exists: ${type.name}.$key")
}
/**
*
* A config entry, holding it's key and description.
*
* There are two implementations of Config.Entry:
*
* * [StringEntry] for String entries and secrets
* * [BooleanEntry] for Boolean entries
*
* @param base the base part of the config key
* @param key the actual config key, which will be qualified by the base
* @param description a user-friendly description of this config entry
* @param isSecret whether this entry is a secret
* @param ui a UiNode to show to configure this entry
*/
abstract inner class Entry protected constructor(
val base: Class<*>,
val key: String,
val description: String,
val isSecret: Boolean,
val ui: UiNode<*, *, *>) {
init {
checkExists(base, key)
addEntry(base, key, this)
}
protected open fun set(value: String?) {
val actual = if (value != null && value.trim().isEmpty()) {
logFinest("Replacing empty new value with null")
null
} else value
if (isSecret) {
setSecret(base, key, actual)
} else {
setValue(base, key, actual)
}
}
fun destruct() {
removeEntry(base, key)
}
}
/**
*
* Read-only implementation of Entry supporting string values.
*
* This implementation provides various possibilities to access the entry value and/or the
* default value.
*/
abstract inner class ReadOnlyStringEntry internal constructor(
base: Class<*>,
key: String,
description: String,
isSecret: Boolean,
ui: UiNode,
val defaultValue: String?,
private val checker: ConfigChecker) : Entry(base, key, description, isSecret, ui) {
private val listeners: MutableSet>
init {
this.listeners = HashSet()
}
/**
* Registers a ConfigListener for this entry which will be called for every value change.
* @param listener a listener
*/
fun addListener(listener: ConfigListener) {
listeners.add(listener)
}
/**
* Removes a listener registered in [addListener].
*/
fun removeListener(listener: ConfigListener) {
listeners.remove(listener)
}
override fun set(value: String?) {
val oldValue = valueWithoutDefault
if (oldValue != value) {
super.set(value)
listeners.forEach { c -> c.onChange(oldValue, value) }
}
}
/**
* Calls the config checker passed to the constructor with the current value, if the value is not null.
* @return an error message, or null
*/
fun checkError(): String? {
return value?.let { checker.check(it) }
}
/**
* Checks whether the current value is null or, if it isn't, whether [checkError] returns an error.
*/
fun isNullOrError() = value == null || checkError() != null
/**
* Gets the value of the entry. If a default value is present, it is ignored.
*
* @return optional of the entry value
*/
val valueWithoutDefault: String?
get() {
val config = this@Config
var value: String? = if (isSecret) config.getSecret(base, key) else config.getValue(base,
key)
if (value != null && value.isEmpty()) {
logFinest("Replacing empty value with null")
value = null
}
return value
}
/**
* Gets the value of the entry. If there is no value, but a default value, it will be present.
* If there is neither, null will be returned.
*
* @return the entry value or the default value, or null if there is no default
*/
val value: String?
get() {
return valueWithoutDefault ?: defaultValue
}
}
/**
* An extension of [ReadOnlyStringEntry] providing a [set] method.
*
* Secret string entries are stored separately from plaintext string entries. They should also
* not be visible to the user.
*
* @param base the class defining the entry. Its fully qualified name is prepended to the key for
* uniqueness
* @param key the entry key
* @param description a description for the user what this entry does
* @param isSecret whether this entry is a secret
* @param default a default value. Must not be present if this entry is secret
* @param ui the UI node to show in the configuration window
* @param checker a config checker
* @return a string config entry
* @throws IllegalArgumentException if the entry has already been defined
*/
inner class StringEntry @JvmOverloads constructor(
base: Class<*>,
key: String,
description: String,
isSecret: Boolean,
default: String? = null,
ui: UiNode = if (isSecret) PasswordBox() else TextBox(),
checker: ConfigChecker = ConfigChecker { null }) :
ReadOnlyStringEntry(base, key, description, isSecret, ui, default, checker) {
init {
if (isSecret && default != null) {
throw IllegalArgumentException("Secret entries can't have default values")
}
}
/**
*
* Changes the entry value to the specified new one.
*
*
* The new value will immediately be persistently stored.
*
* @param value the new value, or null
*/
public override fun set(value: String?) {
super.set(value)
}
}
/**
*
* Read-only implementation of Entry supporting boolean values.
*
*
* This implementation must have a default value.
*
*
* This implementation provides a [get] method to access the current value.
*
*
* **Note:** a BooleanEntry will actually be stored as a string. [Boolean.parseBoolean] will be used to parse a value
* from the config, which means `false` will be parsed if the value is anything other than "true".
*/
abstract inner class ReadOnlyBooleanEntry internal constructor(
base: Class<*>,
key: String,
description: String,
val defaultValue: Boolean,
ui: UiNode) : Entry(base, key, description, false, ui) {
private val listeners: MutableSet>
init {
this.listeners = HashSet()
}
/**
* Registers a ConfigListener for this entry which will be called for every value change.
* @param listener a listener
*/
fun addListener(listener: ConfigListener) {
listeners.add(listener)
}
/**
* Removes a ConfigListener registered with [addListener].
*/
fun removeListener(listener: ConfigListener) {
listeners.remove(listener)
}
override fun set(value: String?) {
val oldValue = this.value
super.set(value)
val newValue = this.value
if (oldValue != newValue) {
listeners.forEach { c -> c.onChange(oldValue, newValue) }
}
}
/**
* Gets the current entry value. If no current value is present, the default value is returned.
*
* @return the entry value
*/
val value: Boolean
get() {
val config = this@Config
val value = if (isSecret) config.getSecret(base, key) else config.getValue(base, key)
return if (value == null || value.trim { it <= ' ' } == "") {
logFinest("Returning default for missing or empty value: $key")
defaultValue
} else {
logFinest("Getting bool from string: '$value'")
value.toBoolean()
}
}
}
/**
* An extension of [ReadOnlyBooleanEntry] providing a [set] method.
*
* Defines and returns a new boolean config entry.
*
* @param base the class defining the entry. Its fully qualified name is prepended to the key for
* uniqueness
* @param key the entry key
* @param description a description for the user what this entry does
* @param defaultValue a default value
* @param ui a UI node to show in the configuration window
* @return a boolean config entry
* @throws IllegalArgumentException if the entry has already been defined, but is a StringEntry
*/
inner class BooleanEntry @JvmOverloads constructor(
base: Class<*>,
key: String,
description: String,
defaultValue: Boolean,
ui: UiNode = CheckBox()) :
ReadOnlyBooleanEntry(base, key, description, defaultValue, ui) {
/**
* Sets the entry value to the specified new one.
*
* The new value will immediately be persistently stored.
*
* @param value the new value
*/
fun set(value: Boolean?) {
super.set(value?.toString())
}
}
val defaults = object : Defaults {
override val pluginFolder = StringEntry(
MusicBot::class.java,
"pluginFolder",
"This is where the application looks for plugin files",
false,
"plugins"
)
}
}
@FunctionalInterface
interface ConfigListener {
fun onChange(oldValue: T, newValue: T)
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy