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
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.Event
import org.bukkit.event.HandlerList
@ -12,7 +12,7 @@ import org.bukkit.event.HandlerList
*
* @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 {
private var cancelled = false
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.
* @param config May be null for testing
* @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
* @param path The path to the config value
* @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?,
override val path: String,
primitiveDef: Any?,
private val getter: Function<Any?, T>,
private val setter: Function<T, Any?>,
private val primitiveDef: Any,
private val getter: Function<Any, T>,
private val setter: Function<T, Any>,
private val readOnly: Boolean
) : IConfigData<T> {
private val pdef: Any?
/**
* The config value should not change outside this instance
@ -34,8 +37,6 @@ class ConfigData<T> internal constructor(
private var value: T? = null
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
}
@ -47,28 +48,25 @@ class ConfigData<T> internal constructor(
val cachedValue = value
if (cachedValue != null) return cachedValue //Speed things up
val config = config?.config
var `val`: Any?
if (config == null || !config.isSet(path)) {
`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 }
val freshValue = config?.get(path) ?: primitiveDef.also { setInternal(it) }
return getter.apply(convertPrimitiveType(freshValue)).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
val `val` = value?.let { setter.apply(it) }
val `val` = setter.apply(value)
setInternal(`val`)
this.value = value
}
@ -86,6 +84,10 @@ class ConfigData<T> internal constructor(
companion object {
private val saveTasks = HashMap<Configuration, SaveTask>()
/**
* Signals that the config has changed and should be saved
*/
fun signalChange(config: IHaveConfig) {
val cc = config.config
val sa = config.saveAction
@ -99,7 +101,7 @@ class ConfigData<T> internal constructor(
return
}
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) {
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
fun saveNow(config: Configuration): Boolean {
synchronized(saveTasks) {

View file

@ -2,7 +2,6 @@ package buttondevteam.lib.architecture
import buttondevteam.lib.architecture.config.IConfigData
import org.bukkit.configuration.ConfigurationSection
import java.util.*
import java.util.function.Function
/**
@ -26,12 +25,12 @@ class IHaveConfig(
* 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 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)
* @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 readOnly If true, changing the value will have no effect
* @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
</T> */
@Suppress("UNCHECKED_CAST")
@ -39,11 +38,12 @@ class IHaveConfig(
fun <T> getData(
path: String,
def: T,
getter: Function<Any?, T>? = null,
setter: Function<T, Any?>? = null,
getter: Function<Any, T>? = null,
setter: Function<T, Any>? = null,
readOnly: Boolean = false
): 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
fun <T> getData(
path: String,
getter: Function<Any?, T>,
setter: Function<T, Any?>,
primitiveDef: Any?,
getter: Function<Any, T>,
setter: Function<T, Any>,
primitiveDef: Any,
readOnly: Boolean = false
): ConfigData<T> {
val data =

View file

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

View file

@ -1,8 +1,18 @@
package buttondevteam.lib.architecture.config
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
}

View file

@ -8,7 +8,7 @@ class ChatMessage internal constructor(
/**
* The sender which sends the message.
*/
val sender: CommandSender,
val sender: Command2Sender,
/**
* 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.Companion.saveNow
import buttondevteam.lib.architecture.IHaveConfig
import buttondevteam.lib.chat.Command2MCSender
import buttondevteam.lib.chat.Command2Sender
import org.bukkit.Bukkit
import org.bukkit.command.CommandSender
import org.bukkit.configuration.file.YamlConfiguration
@ -168,7 +170,7 @@ abstract class ChromaGamerBase {
companion object {
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
@ -303,22 +305,41 @@ abstract class ChromaGamerBase {
*/
@JvmStatic
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
* @return A user as returned by a converter or null if none can supply it
* @return A user as returned by a converter
*/
@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) {
val ocg = converter.apply(sender)
if (ocg.isPresent) return ocg.get()
}
return null
throw RuntimeException("No converter found for sender type ${sender::class.java.simpleName}")
}
fun saveUsers() {