Command sender and config improvements

- Use Command2Sender for getting Chroma users
- Throw exception if a proper converter doesn't exist
- Make ConfigData non-nullable altogether, but allow for nullable type + documentation
- Converter stuff is older, config stuff is from earlier today
- Command2 stuff isn't really worked out yet
This commit is contained in:
Norbi Peti 2023-05-14 03:03:51 +02:00
parent aabc2cd48c
commit b57420c72a
No known key found for this signature in database
GPG key ID: DBA4C4549A927E56
7 changed files with 92 additions and 55 deletions

View file

@ -1,7 +1,7 @@
package buttondevteam.lib package buttondevteam.lib
import buttondevteam.core.component.channel.Channel import buttondevteam.core.component.channel.Channel
import org.bukkit.command.CommandSender import buttondevteam.lib.chat.Command2Sender
import org.bukkit.event.Cancellable import org.bukkit.event.Cancellable
import org.bukkit.event.Event import org.bukkit.event.Event
import org.bukkit.event.HandlerList import org.bukkit.event.HandlerList
@ -12,7 +12,7 @@ import org.bukkit.event.HandlerList
* *
* @author NorbiPeti * @author NorbiPeti
*/ */
class TBMCChatPreprocessEvent(val sender: CommandSender, val channel: Channel, var message: String) : Event(true), class TBMCChatPreprocessEvent(val sender: Command2Sender, val channel: Channel, var message: String) : Event(true),
Cancellable { Cancellable {
private var cancelled = false private var cancelled = false
override fun getHandlers(): HandlerList { override fun getHandlers(): HandlerList {

View file

@ -15,18 +15,21 @@ import java.util.function.Function
* *
* **Note:** The instance can become outdated if the config is reloaded. * **Note:** The instance can become outdated if the config is reloaded.
* @param config May be null for testing * @param config May be null for testing
* @param getter The parameter is of a primitive type as returned by [Configuration.get] * @param path The path to the config value
* @param setter The result should be a primitive type or string that can be retrieved correctly later * @param primitiveDef The default value, as stored in the config. Non-nullable as it needs to be saved to the config
* @param getter Function to convert primtive types to [T]. The parameter is of a primitive type as returned by [Configuration.get]
* @param setter Function to convert [T] to a primitive type. The result should be a primitive type or string that can be retrieved correctly later
* @param readOnly If true, changing the value will have no effect
* @param T The type of the config value. May be nullable if the getter cannot always return a value
*/ */
class ConfigData<T> internal constructor( class ConfigData<T : Any?> internal constructor(
val config: IHaveConfig?, val config: IHaveConfig?,
override val path: String, override val path: String,
primitiveDef: Any?, private val primitiveDef: Any,
private val getter: Function<Any?, T>, private val getter: Function<Any, T>,
private val setter: Function<T, Any?>, private val setter: Function<T, Any>,
private val readOnly: Boolean private val readOnly: Boolean
) : IConfigData<T> { ) : IConfigData<T> {
private val pdef: Any?
/** /**
* The config value should not change outside this instance * The config value should not change outside this instance
@ -34,8 +37,6 @@ class ConfigData<T> internal constructor(
private var value: T? = null private var value: T? = null
init { init {
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 get() //Generate config automatically
} }
@ -47,28 +48,25 @@ class ConfigData<T> internal constructor(
val cachedValue = value val cachedValue = value
if (cachedValue != null) return cachedValue //Speed things up if (cachedValue != null) return cachedValue //Speed things up
val config = config?.config val config = config?.config
var `val`: Any? val freshValue = config?.get(path) ?: primitiveDef.also { setInternal(it) }
if (config == null || !config.isSet(path)) { return getter.apply(convertPrimitiveType(freshValue)).also { value = it }
`val` = pdef
setInternal(pdef) // Save default value even if read-only
} else `val` = config.get(path) //config==null: testing
if (`val` == null) //If it's set to null explicitly
`val` = pdef
fun convert(cval: Any?, cpdef: Any?): Any? {
return if (cpdef is Number) //If we expect a number
if (cval is Number)
ChromaUtils.convertNumber(cval, cpdef.javaClass)
else cpdef //If we didn't get a number, return default (which is a number)
else if (cval is List<*> && cpdef != null && cpdef.javaClass.isArray)
cval.toTypedArray()
else cval
}
return getter.apply(convert(`val`, pdef)).also { value = it }
} }
override fun set(value: T?) { // TODO: Have a separate method for removing the value from the config and make this non-nullable /**
* Converts a value to [T] from the representation returned by [Configuration.get].
*/
private fun convertPrimitiveType(value: Any): Any {
return if (primitiveDef is Number) //If we expect a number
if (value is Number) ChromaUtils.convertNumber(value, primitiveDef.javaClass)
else primitiveDef //If we didn't get a number, return default (which is a number)
else if (value is List<*> && primitiveDef.javaClass.isArray) // If we got a list and we expected an array
value.toTypedArray<Any?>()
else value
}
override fun set(value: T) {
if (readOnly) return //Safety for Discord channel/role data if (readOnly) return //Safety for Discord channel/role data
val `val` = value?.let { setter.apply(it) } val `val` = setter.apply(value)
setInternal(`val`) setInternal(`val`)
this.value = value this.value = value
} }
@ -86,6 +84,10 @@ class ConfigData<T> internal constructor(
companion object { companion object {
private val saveTasks = HashMap<Configuration, SaveTask>() private val saveTasks = HashMap<Configuration, SaveTask>()
/**
* Signals that the config has changed and should be saved
*/
fun signalChange(config: IHaveConfig) { fun signalChange(config: IHaveConfig) {
val cc = config.config val cc = config.config
val sa = config.saveAction val sa = config.saveAction
@ -99,7 +101,7 @@ class ConfigData<T> internal constructor(
return return
} }
if (!MainPlugin.isInitialized) { if (!MainPlugin.isInitialized) {
// If the plugin isn't initilized, we can't schedule a task - do it when the plugin is enabled // If the plugin isn't initialized, we can't schedule a task - do it when the plugin is enabled
synchronized(saveTasks) { synchronized(saveTasks) {
saveTasks.put(root, SaveTask(null, sa)) saveTasks.put(root, SaveTask(null, sa))
} }
@ -118,6 +120,11 @@ class ConfigData<T> internal constructor(
} }
} }
/**
* Saves the config immediately, if it's scheduled to be saved. Used to save configs when the plugin is disabled or reloaded.
*
* Also performs cleanup of the save task, so it must be called when the ConfigData is invalidated (which is the above two cases).
*/
@JvmStatic @JvmStatic
fun saveNow(config: Configuration): Boolean { fun saveNow(config: Configuration): Boolean {
synchronized(saveTasks) { synchronized(saveTasks) {

View file

@ -2,7 +2,6 @@ package buttondevteam.lib.architecture
import buttondevteam.lib.architecture.config.IConfigData import buttondevteam.lib.architecture.config.IConfigData
import org.bukkit.configuration.ConfigurationSection import org.bukkit.configuration.ConfigurationSection
import java.util.*
import java.util.function.Function import java.util.function.Function
/** /**
@ -26,12 +25,12 @@ class IHaveConfig(
* if you're not using primitive types directly. * if you're not using primitive types directly.
* These primitive types are strings, numbers, characters, booleans and lists of these things. * These primitive types are strings, numbers, characters, booleans and lists of these things.
* *
* @param path The path in config to use * @param path The path in config to use
* @param def The value to use by default * @param def The value to use by default
* @param getter A function that converts a primitive representation to the correct value * @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 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 readOnly If true, changing the value will have no effect
* @param <T> The type of this variable (can be any class) * @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 * @return The data object that can be used to get or set the value
</T> */ </T> */
@Suppress("UNCHECKED_CAST") @Suppress("UNCHECKED_CAST")
@ -39,11 +38,12 @@ class IHaveConfig(
fun <T> getData( fun <T> getData(
path: String, path: String,
def: T, def: T,
getter: Function<Any?, T>? = null, getter: Function<Any, T>? = null,
setter: Function<T, Any?>? = null, setter: Function<T, Any>? = null,
readOnly: Boolean = false readOnly: Boolean = false
): ConfigData<T> { ): ConfigData<T> {
return getData(path, getter ?: Function { it as T }, setter ?: Function { it }, def, readOnly) val safeSetter = setter ?: Function { it ?: throw RuntimeException("No setter specified for nullable config data $path!") }
return getData(path, getter ?: Function { it as T }, safeSetter, safeSetter.apply(def), readOnly)
} }
/** /**
@ -61,9 +61,9 @@ class IHaveConfig(
@JvmOverloads @JvmOverloads
fun <T> getData( fun <T> getData(
path: String, path: String,
getter: Function<Any?, T>, getter: Function<Any, T>,
setter: Function<T, Any?>, setter: Function<T, Any>,
primitiveDef: Any?, primitiveDef: Any,
readOnly: Boolean = false readOnly: Boolean = false
): ConfigData<T> { ): ConfigData<T> {
val data = val data =

View file

@ -1,7 +1,6 @@
package buttondevteam.lib.architecture package buttondevteam.lib.architecture
import buttondevteam.lib.architecture.config.IConfigData import buttondevteam.lib.architecture.config.IConfigData
import java.util.ArrayList
import java.util.function.Function import java.util.function.Function
import java.util.function.Predicate import java.util.function.Predicate
import java.util.function.UnaryOperator import java.util.function.UnaryOperator
@ -23,7 +22,7 @@ class ListConfigData<T> internal constructor(
return listConfig.get() return listConfig.get()
} }
override fun set(value: List?) { override fun set(value: List) {
listConfig.set(value) listConfig.set(value)
} }

View file

@ -1,8 +1,18 @@
package buttondevteam.lib.architecture.config package buttondevteam.lib.architecture.config
interface IConfigData<T> { interface IConfigData<T> {
fun get(): T? /**
fun set(value: T?) * Gets the value from the config using the getter specified for the config. If the config is not set, the default value is returned.
*/
fun get(): T
/**
* Sets the value in the config using the setter specified for the config. If the config is read-only, this does nothing.
*/
fun set(value: T)
/**
* The path to the config value.
*/
val path: String val path: String
} }

View file

@ -8,7 +8,7 @@ class ChatMessage internal constructor(
/** /**
* The sender which sends the message. * The sender which sends the message.
*/ */
val sender: CommandSender, val sender: Command2Sender,
/** /**
* The Chroma user which sends the message. * The Chroma user which sends the message.
*/ */

View file

@ -7,6 +7,8 @@ import buttondevteam.lib.TBMCCoreAPI
import buttondevteam.lib.architecture.ConfigData import buttondevteam.lib.architecture.ConfigData
import buttondevteam.lib.architecture.ConfigData.Companion.saveNow import buttondevteam.lib.architecture.ConfigData.Companion.saveNow
import buttondevteam.lib.architecture.IHaveConfig import buttondevteam.lib.architecture.IHaveConfig
import buttondevteam.lib.chat.Command2MCSender
import buttondevteam.lib.chat.Command2Sender
import org.bukkit.Bukkit import org.bukkit.Bukkit
import org.bukkit.command.CommandSender import org.bukkit.command.CommandSender
import org.bukkit.configuration.file.YamlConfiguration import org.bukkit.configuration.file.YamlConfiguration
@ -168,7 +170,7 @@ abstract class ChromaGamerBase {
companion object { companion object {
private const val TBMC_PLAYERS_DIR = "TBMC/players/" private const val TBMC_PLAYERS_DIR = "TBMC/players/"
private val senderConverters = ArrayList<Function<CommandSender, out Optional<out ChromaGamerBase>>>() private val senderConverters = ArrayList<Function<Command2Sender, out Optional<out ChromaGamerBase>>>()
/** /**
* Holds data per user class * Holds data per user class
@ -303,22 +305,41 @@ abstract class ChromaGamerBase {
*/ */
@JvmStatic @JvmStatic
fun addConverter(converter: Function<CommandSender, Optional<out ChromaGamerBase>>) { fun addConverter(converter: Function<CommandSender, Optional<out ChromaGamerBase>>) {
senderConverters.add(0, converter) senderConverters.add(0) { sender ->
when (sender) {
is Command2MCSender -> converter.apply(sender.sender)
else -> Optional.empty()
}
}
} }
/** /**
* Get from the given sender. the object's type will depend on the sender's type. May be null, but shouldn't be. * Get from the given sender. the object's type will depend on the sender's type.
* Throws an exception if the sender type is not supported.
* *
* @param sender The sender to use * @param sender The sender to use
* @return A user as returned by a converter or null if none can supply it * @return A user as returned by a converter
*/ */
@JvmStatic @JvmStatic
fun getFromSender(sender: CommandSender): ChromaGamerBase? { // TODO: Use Command2Sender @Deprecated("Use Command2Sender instead", ReplaceWith("getFromSender(Command2MCSender(sender, Channel.globalChat, sender))", "buttondevteam.lib.player.ChromaGamerBase.Companion.getFromSender", "buttondevteam.lib.chat.Command2MCSender", "buttondevteam.core.component.channel.Channel"))
fun getFromSender(sender: CommandSender): ChromaGamerBase {
return getFromSender(Command2MCSender(sender, Channel.globalChat, sender))
}
/**
* Get from the given sender. the object's type will depend on the sender's type.
* Throws an exception if the sender type is not supported.
*
* @param sender The sender to use
* @return A user as returned by a converter
*/
@JvmStatic
fun getFromSender(sender: Command2Sender): ChromaGamerBase {
for (converter in senderConverters) { for (converter in senderConverters) {
val ocg = converter.apply(sender) val ocg = converter.apply(sender)
if (ocg.isPresent) return ocg.get() if (ocg.isPresent) return ocg.get()
} }
return null throw RuntimeException("No converter found for sender type ${sender::class.java.simpleName}")
} }
fun saveUsers() { fun saveUsers() {