From 8d8708d14b3ad40d8fe0a9ea79775790f1be83d5 Mon Sep 17 00:00:00 2001 From: NorbiPeti Date: Fri, 17 Mar 2023 16:45:38 +0100 Subject: [PATCH] Improved config handling, updated read-only and list configs - The read-only configs are no longer using a separate class, it seems unnecessary - The list configs got refactored so that the entire list doesn't get recreated each time it changes - Also added support for getters/setters for the list config, just to be consistent --- .../lib/architecture/ConfigData.kt | 111 ++------ .../lib/architecture/IHaveConfig.kt | 148 ++++------ .../lib/architecture/ListConfigData.kt | 254 +++++++++++------- .../lib/architecture/ReadOnlyConfigData.kt | 12 - .../lib/architecture/config/IConfigData.kt | 9 + 5 files changed, 240 insertions(+), 294 deletions(-) delete mode 100644 Chroma-Core/src/main/java/buttondevteam/lib/architecture/ReadOnlyConfigData.kt create mode 100644 Chroma-Core/src/main/java/buttondevteam/lib/architecture/config/IConfigData.kt diff --git a/Chroma-Core/src/main/java/buttondevteam/lib/architecture/ConfigData.kt b/Chroma-Core/src/main/java/buttondevteam/lib/architecture/ConfigData.kt index b5ee6a4..c979c3c 100644 --- a/Chroma-Core/src/main/java/buttondevteam/lib/architecture/ConfigData.kt +++ b/Chroma-Core/src/main/java/buttondevteam/lib/architecture/ConfigData.kt @@ -2,6 +2,7 @@ package buttondevteam.lib.architecture import buttondevteam.core.MainPlugin import buttondevteam.lib.ChromaUtils +import buttondevteam.lib.architecture.config.IConfigData import org.bukkit.Bukkit import org.bukkit.configuration.Configuration import org.bukkit.scheduler.BukkitTask @@ -14,14 +15,14 @@ import java.util.function.Function * @param getter The parameter is of a primitive type as returned by [Configuration.get] * @param setter The result should be a primitive type or string that can be retrieved correctly later */ -open class ConfigData internal constructor( - private val config: IHaveConfig?, - val path: String, - def: T?, +class ConfigData internal constructor( + val config: IHaveConfig?, + override val path: String, primitiveDef: Any?, private val getter: Function, - private val setter: Function -) { + private val setter: Function, + private val readOnly: Boolean +) : IConfigData { private val pdef: Any? /** @@ -30,7 +31,7 @@ open class ConfigData internal constructor( private var value: T? = null init { - this.pdef = primitiveDef ?: def?.let { setter.apply(it) } + this.pdef = primitiveDef ?: throw IllegalArgumentException("Either def or primitiveDef must be set. A getter and setter must be present when using primitiveDef.") get() //Generate config automatically } @@ -39,11 +40,11 @@ open class ConfigData internal constructor( return "ConfigData{path='$path', value=$value}" } - fun reset() { + override fun reset() { value = null } - fun get(): T? { + override fun get(): T? { if (value != null) return value //Speed things up val config = config?.config var `val`: Any? @@ -65,8 +66,8 @@ open class ConfigData internal constructor( return getter.apply(convert(`val`, pdef)).also { value = it } } - fun set(value: T?) { - if (this is ReadOnlyConfigData<*>) return //Safety for Discord channel/role data + override fun set(value: T?) { + if (readOnly) return //Safety for Discord channel/role data val `val` = value?.let { setter.apply(value) } setInternal(`val`) this.value = value @@ -80,88 +81,30 @@ open class ConfigData internal constructor( private class SaveTask(val task: BukkitTask, val saveAction: Runnable) - class ConfigDataBuilder internal constructor(private val config: IHaveConfig, private val path: String) { - private var def: T? = null - private var primitiveDef: Any? = null - - @Suppress("UNCHECKED_CAST") - private var getter: Function = Function { it as T } - private var setter: Function = Function { it } - - /** - * The default value to use, as used in code. If not a primitive type, use the [.getter] and [.setter] methods. - *

- * To set the value as it is stored, use [.primitiveDef]. - * - * @param def The default value - * @return This builder - */ - fun def(def: T): ConfigDataBuilder { - this.def = def - return this - } - - /** - * The default value to use, as stored in yaml. Must be a primitive type. Make sure to use the [.getter] and [.setter] methods. - *

- * To set the value as used in the code, use [.def]. - * - * @param primitiveDef The default value - * @return This builder - */ - fun primitiveDef(primitiveDef: Any?): ConfigDataBuilder { - this.primitiveDef = primitiveDef - return this - } - - /** - * A function to use to obtain the runtime object from the yaml representation (usually string). - * The [.setter] must also be set. - * - * @param getter A function that receives the primitive type and returns the runtime type - * @return This builder - */ - fun getter(getter: Function): ConfigDataBuilder { - this.getter = getter - return this - } - - /** - * A function to use to obtain the yaml representation (usually string) from the runtime object. - * The [.getter] must also be set. - * - * @param setter A function that receives the runtime type and returns the primitive type - * @return This builder - */ - fun setter(setter: Function): ConfigDataBuilder { - this.setter = setter - return this - } - + class ConfigDataBuilder internal constructor( + private val config: IHaveConfig, + private val path: String, + private val primitiveDef: Any?, + private val getter: Function, + private val setter: Function + ) { /** * Builds a modifiable config representation. Use if you want to change the value *in code*. * * @return A ConfigData instance. */ - fun build(): ConfigData { - val config = ConfigData(config, path, def, primitiveDef, getter, setter) + fun build(readOnly: Boolean = false): ConfigData { + val config = ConfigData(config, path, primitiveDef, getter, setter, readOnly) this.config.onConfigBuild(config) return config } - /** - * Builds a read-only config representation. Use if you only want the value to be changed *in the config*. - * - * @return A ReadOnlyConfigData instance. - */ - fun buildReadOnly(): ReadOnlyConfigData { - val config = ReadOnlyConfigData(config, path, def, primitiveDef, getter, setter) - this.config.onConfigBuild(config) - return config - } - - override fun toString(): String { - return "ConfigData.ConfigDataBuilder(config=$config, path=$path, def=$def, primitiveDef=$primitiveDef, getter=$getter, setter=$setter)" + fun buildList(readOnly: Boolean = false): ListConfigData { + if (primitiveDef is List<*>) { + val config = ListConfigData(config, path, primitiveDef, getter, setter, readOnly) + this.config.onConfigBuild(config) + return config + } } } diff --git a/Chroma-Core/src/main/java/buttondevteam/lib/architecture/IHaveConfig.kt b/Chroma-Core/src/main/java/buttondevteam/lib/architecture/IHaveConfig.kt index 5b9d012..eb0370f 100644 --- a/Chroma-Core/src/main/java/buttondevteam/lib/architecture/IHaveConfig.kt +++ b/Chroma-Core/src/main/java/buttondevteam/lib/architecture/IHaveConfig.kt @@ -3,6 +3,7 @@ package buttondevteam.lib.architecture import buttondevteam.core.MainPlugin import buttondevteam.lib.TBMCCoreAPI import buttondevteam.lib.architecture.ConfigData.ConfigDataBuilder +import buttondevteam.lib.architecture.config.IConfigData import org.bukkit.Bukkit import org.bukkit.configuration.ConfigurationSection import org.bukkit.plugin.java.JavaPlugin @@ -16,90 +17,72 @@ import java.util.stream.Collectors /** * A config system * May be used in testing. - * - * @param saveAction What to do to save the config to disk. Don't use get methods until it's non-null. */ -class IHaveConfig(var saveAction: Runnable?) { // TODO: Make non-nullable after adding component builder - private val datamap = HashMap>() - +class IHaveConfig( + /** + * The way the underlying configuration gets saved to disk + */ + val saveAction: Runnable, /** * Returns the Bukkit ConfigurationSection. Use [.signalChange] after changing it. */ - var config: ConfigurationSection? = null // TODO: Make non-nullable after removing reset() method + val config: ConfigurationSection +) { + private val datamap = HashMap>() /** - * Gets a config object for the given path. The def or primitiveDef must be set. If a getter is present, a setter must be present as well. + * You may use this method with any data type, but always provide getters and setters that convert to primitive types + * if you're not using primitive types directly. + * These primitive types are strings, numbers, characters, booleans and lists of these things. * - * @param path The dot-separated path relative to this config instance - * @param The runtime type of the config value - * @return A ConfigData builder to set how to obtain the value - */ - fun getConfig(path: String?): ConfigDataBuilder { + * @param path The path in config to use + * @param def The value to use by default + * @param getter A function that converts a primitive representation to the correct value + * @param setter A function that converts a value to a primitive representation + * @param primDef Whether the default value is a primitive value that needs to be converted to the correct type using the getter + * @param T The type of this variable (can be any class) + * @return The data object that can be used to get or set the value + */ + fun getConfig( + path: String, + def: T, + getter: Function? = null, + setter: Function? = null + ): ConfigDataBuilder { return ConfigData.builder(this, path) } - fun onConfigBuild(config: ConfigData<*>) { + fun onConfigBuild(config: IConfigData<*>) { datamap[config.path] = config } /** - * This method overload should only be used with primitives or String. + * You may use this method with any data type, but always provide getters and setters that convert to primitive types + * if you're not using primitive types directly. + * These primitive types are strings, numbers, characters, booleans and lists of these things. * - * @param path The path in config to use - * @param def The value to use by default - * @param The type of this variable (only use primitives or String) - * @return The data object that can be used to get or set the value - */ - fun getData(path: String, def: T): ConfigData { - var data = datamap[path] - if (data == null) datamap[path] = ConfigData(this, path, def, def, null, null).also { data = it } - @Suppress("UNCHECKED_CAST") - return data as ConfigData - } - - /** - * This method overload may be used with any class. - * - * @param path The path in config to use - * @param def The value to use by default - * @param getter A function that converts a primitive representation to the correct value - * @param setter A function that converts a value to a primitive representation + * @param path The path in config to use + * @param def The value to use by default + * @param getter A function that converts a primitive representation to the correct value + * @param setter A function that converts a value to a primitive representation + * @param primDef Whether the default value is a primitive value that needs to be converted to the correct type using the getter * @param The type of this variable (can be any class) * @return The data object that can be used to get or set the value */ - fun getData(path: String, def: T, getter: Function?, setter: Function): ConfigData { - var data = datamap[path] - if (data == null) datamap[path] = - ConfigData(this, path, def, setter.apply(def), getter, setter).also { data = it } - @Suppress("UNCHECKED_CAST") - return data as ConfigData - } - - /** - * This method overload may be used with any class. The given default value will be run through the getter. - * - * @param path The path in config to use - * @param primitiveDef The **primitive** value to use by default - * @param getter A function that converts a primitive representation to the correct value - * @param setter A function that converts a value to a primitive representation - * @param The type of this variable (can be any class) - * @return The data object that can be used to get or set the value - */ - fun getDataPrimDef( + @Suppress("UNCHECKED_CAST") + fun getData( path: String, - primitiveDef: Any?, - getter: Function, - setter: Function? + def: T, + getter: Function? = null, + setter: Function? = null, + readOnly: Boolean = false ): ConfigData { - var data = datamap[path] - if (data == null) datamap[path] = - ConfigData(this, path, getter.apply(primitiveDef), primitiveDef, getter, setter).also { data = it } - @Suppress("UNCHECKED_CAST") - return data as ConfigData + return getData(path, getter ?: Function { it as T }, setter ?: Function { it }, def) } /** - * This method overload may be used with any class. The given default value will be run through the getter. + * You may use this method with any data type and provide a primitive default value. + * These primitive types are strings, numbers, characters, booleans and lists of these things. * * @param path The path in config to use * @param primitiveDef The **primitive** value to use by default @@ -108,17 +91,17 @@ class IHaveConfig(var saveAction: Runnable?) { // TODO: Make non-nullable after * @param The type of this variable (can be any class) * @return The data object that can be used to get or set the value */ - fun getReadOnlyDataPrimDef( + @Suppress("UNCHECKED_CAST") + fun getData( path: String, - primitiveDef: Any?, getter: Function, - setter: Function? - ): ReadOnlyConfigData { - var data = datamap[path] - if (data == null) datamap[path] = - ReadOnlyConfigData(this, path, getter.apply(primitiveDef), primitiveDef, getter, setter).also { data = it } - @Suppress("UNCHECKED_CAST") - return data as ReadOnlyConfigData + setter: Function, + primitiveDef: Any?, + readOnly: Boolean = false + ): ConfigData { + val data = + datamap[path] ?: ConfigData(this, path, primitiveDef, getter, setter, readOnly).also { datamap[path] = it } + return data as ConfigData } /** @@ -139,31 +122,6 @@ class IHaveConfig(var saveAction: Runnable?) { // TODO: Make non-nullable after return data as ConfigData } - /** - * This method overload may be used with any class. - * - * @param path The path in config to use - * @param def The value to use by default - * @param getter A function that converts a primitive representation to the correct value - * @param setter A function that converts a value to a primitive representation - * @param The type of this variable (can be any class) - * @return The data object that can be used to get or set the value - */ - fun getData( - path: String, - def: Supplier, - getter: Function?, - setter: Function - ): ConfigData { - var data = datamap[path] - if (data == null) { - val defval = def.get() - datamap[path] = ConfigData(this, path, defval, setter.apply(defval), getter, setter).also { data = it } - } - @Suppress("UNCHECKED_CAST") - return data as ConfigData - } - /** * This method overload should only be used with primitves or String. * diff --git a/Chroma-Core/src/main/java/buttondevteam/lib/architecture/ListConfigData.kt b/Chroma-Core/src/main/java/buttondevteam/lib/architecture/ListConfigData.kt index 5c37e20..41860fa 100644 --- a/Chroma-Core/src/main/java/buttondevteam/lib/architecture/ListConfigData.kt +++ b/Chroma-Core/src/main/java/buttondevteam/lib/architecture/ListConfigData.kt @@ -1,124 +1,172 @@ -package buttondevteam.lib.architecture; +package buttondevteam.lib.architecture -import lombok.val; -import org.jetbrains.annotations.NotNull; +import buttondevteam.lib.architecture.config.IConfigData +import java.util.function.Function +import java.util.function.Predicate +import java.util.function.UnaryOperator +import kotlin.collections.List as KList -import java.util.ArrayList; -import java.util.Collection; -import java.util.Comparator; -import java.util.function.Predicate; -import java.util.function.UnaryOperator; +class ListConfigData internal constructor( + config: IHaveConfig?, + path: String, + primitiveDef: kotlin.collections.List<*>, + private val elementGetter: Function, + private val elementSetter: Function, + readOnly: Boolean +) : IConfigData.List> { + val listConfig: ConfigData = + ConfigData(config, path, primitiveDef, { List((it as KList<*>).toMutableList()) }, { it }, readOnly) -public class ListConfigData extends ConfigData> { - @SuppressWarnings("unchecked") - ListConfigData(IHaveConfig config, String path, List def) { - super(config, path, def, new ArrayList<>(def), list -> { - var l = new List<>((ArrayList) list); - l.listConfig = def.listConfig; - return l; - }, ArrayList::new); - def.listConfig = this; //Can't make the List class non-static or pass this in the super() constructor - } + override val path: String get() = listConfig.path - public static class List extends ArrayList { - private ListConfigData listConfig; + override fun reset() { + listConfig.reset() + } - public List(@NotNull Collection c) { - super(c); - } + override fun get(): List? { + return listConfig.get() + } - public List() { - } + override fun set(value: List?) { + listConfig.set(value) + } - private void update() { - listConfig.set(this); //Update the config model and start save task if needed - } + inner class List(backingList: MutableList) : MutableList { + private val primitiveList = backingList + override val size: Int get() = primitiveList.size + private fun update() { + val config = listConfig.config + if (config != null) { + ConfigData.signalChange(config) //Update the config model and start save task if needed + } + } - @Override - public T set(int index, T element) { - T ret = super.set(index, element); - update(); - return ret; - } + override fun set(index: Int, element: T): T { + val ret = primitiveList.set(index, elementSetter.apply(element)) + update() + return elementGetter.apply(ret) + } - @Override - public boolean add(T t) { - val ret = super.add(t); - update(); - return ret; - } + override fun add(element: T): Boolean { + val ret = primitiveList.add(elementSetter.apply(element)) + update() + return ret + } - @Override - public void add(int index, T element) { - super.add(index, element); - update(); - } + override fun add(index: Int, element: T) { + primitiveList.add(index, elementSetter.apply(element)) + update() + } - @Override - public T remove(int index) { - T ret = super.remove(index); - update(); - return ret; - } + override fun removeAt(index: Int): T { + val ret = primitiveList.removeAt(index) + update() + return elementGetter.apply(ret) + } - @Override - public boolean remove(Object o) { - val ret = super.remove(o); - update(); - return ret; - } + override fun subList(fromIndex: Int, toIndex: Int): MutableList = + List(primitiveList.subList(fromIndex, toIndex)) - @Override - public boolean addAll(Collection c) { - val ret = super.addAll(c); - update(); - return ret; - } + override fun remove(element: T): Boolean { + val ret = primitiveList.remove(elementSetter.apply(element)) + update() + return ret + } - @Override - public boolean addAll(int index, Collection c) { - val ret = super.addAll(index, c); - update(); - return ret; - } + override fun addAll(elements: Collection): Boolean { + val ret = primitiveList.addAll(elements.map { elementSetter.apply(it) }) + update() + return ret + } - @Override - protected void removeRange(int fromIndex, int toIndex) { - super.removeRange(fromIndex, toIndex); - update(); - } + override fun addAll(index: Int, elements: Collection): Boolean { + val ret = primitiveList.addAll(index, elements.map { elementSetter.apply(it) }) + update() + return ret + } - @Override - public boolean removeAll(Collection c) { - val ret = super.removeAll(c); - update(); - return ret; - } + override fun removeAll(elements: Collection): Boolean { + val ret = primitiveList.removeAll(elements.map { elementSetter.apply(it) }) + update() + return ret + } - @Override - public boolean retainAll(Collection c) { - val ret = super.retainAll(c); - update(); - return ret; - } + override fun retainAll(elements: Collection): Boolean { + val ret = primitiveList.retainAll(elements.map { elementSetter.apply(it) }) + update() + return ret + } - @Override - public boolean removeIf(Predicate filter) { - val ret = super.removeIf(filter); - update(); - return ret; - } + override fun removeIf(filter: Predicate): Boolean { + val ret = primitiveList.removeIf { filter.test(elementGetter.apply(it)) } + update() + return ret + } - @Override - public void replaceAll(UnaryOperator operator) { - super.replaceAll(operator); - update(); - } + override fun replaceAll(operator: UnaryOperator) { + primitiveList.replaceAll { elementSetter.apply(operator.apply(elementGetter.apply(it))) } + update() + } - @Override - public void sort(Comparator c) { - super.sort(c); - update(); - } - } + override fun sort(c: Comparator) { + primitiveList.sortWith { o1, o2 -> c.compare(elementGetter.apply(o1), elementGetter.apply(o2)) } + update() + } + + override fun clear() { + primitiveList.clear() + update() + } + + override fun get(index: Int): T = elementGetter.apply(primitiveList[index]) + override fun isEmpty(): Boolean = primitiveList.isEmpty() + + override fun lastIndexOf(element: T): Int = primitiveList.lastIndexOf(elementSetter.apply(element)) + override fun indexOf(element: T): Int = primitiveList.indexOf(elementSetter.apply(element)) + override fun containsAll(elements: Collection): Boolean = + primitiveList.containsAll(elements.map { elementSetter.apply(it) }) + + override fun contains(element: T): Boolean = primitiveList.contains(elementSetter.apply(element)) + override fun iterator(): MutableIterator { + return object : MutableIterator { + private val iterator = primitiveList.iterator() + override fun hasNext(): Boolean = iterator.hasNext() + override fun next(): T = elementGetter.apply(iterator.next()) + override fun remove() { + iterator.remove() + update() + } + } + } + + override fun listIterator(): MutableListIterator { + return listIterator(0) + } + + override fun listIterator(index: Int): MutableListIterator { + return object : MutableListIterator { + private val iterator = primitiveList.listIterator(index) + override fun hasNext(): Boolean = iterator.hasNext() + override fun next(): T = elementGetter.apply(iterator.next()) + override fun remove() { + iterator.remove() + update() + } + + override fun hasPrevious(): Boolean = iterator.hasPrevious() + override fun nextIndex(): Int = iterator.nextIndex() + override fun previous(): T = elementGetter.apply(iterator.previous()) + override fun previousIndex(): Int = iterator.previousIndex() + override fun add(element: T) { + iterator.add(elementSetter.apply(element)) + update() + } + + override fun set(element: T) { + iterator.set(elementSetter.apply(element)) + update() + } + } + } + } } diff --git a/Chroma-Core/src/main/java/buttondevteam/lib/architecture/ReadOnlyConfigData.kt b/Chroma-Core/src/main/java/buttondevteam/lib/architecture/ReadOnlyConfigData.kt deleted file mode 100644 index 9fb4bd6..0000000 --- a/Chroma-Core/src/main/java/buttondevteam/lib/architecture/ReadOnlyConfigData.kt +++ /dev/null @@ -1,12 +0,0 @@ -package buttondevteam.lib.architecture - -import java.util.function.Function - -class ReadOnlyConfigData internal constructor( - config: IHaveConfig?, - path: String, - def: T?, - primitiveDef: Any?, - getter: Function, - setter: Function -) : ConfigData(config, path, def, primitiveDef, getter, setter) \ No newline at end of file diff --git a/Chroma-Core/src/main/java/buttondevteam/lib/architecture/config/IConfigData.kt b/Chroma-Core/src/main/java/buttondevteam/lib/architecture/config/IConfigData.kt new file mode 100644 index 0000000..0f48e48 --- /dev/null +++ b/Chroma-Core/src/main/java/buttondevteam/lib/architecture/config/IConfigData.kt @@ -0,0 +1,9 @@ +package buttondevteam.lib.architecture.config + +interface IConfigData { + fun reset() + fun get(): T? + fun set(value: T?) + + val path: String +} \ No newline at end of file