Convert config stuff and fix issues

This commit is contained in:
Norbi Peti 2023-02-23 00:54:29 +01:00
parent 65be3b2df9
commit 482df40992
5 changed files with 576 additions and 518 deletions

View file

@ -34,6 +34,7 @@
<executions> <executions>
<execution> <execution>
<id>compile</id> <id>compile</id>
<phase>process-sources</phase>
<goals> <goals>
<goal>compile</goal> <goal>compile</goal>
</goals> </goals>

View file

@ -6,8 +6,6 @@ import buttondevteam.lib.TBMCCoreAPI
import buttondevteam.lib.architecture.Component.Companion.updateConfig import buttondevteam.lib.architecture.Component.Companion.updateConfig
import buttondevteam.lib.chat.Command2MC import buttondevteam.lib.chat.Command2MC
import buttondevteam.lib.chat.ICommand2MC import buttondevteam.lib.chat.ICommand2MC
import lombok.AccessLevel
import lombok.Getter
import org.bukkit.configuration.InvalidConfigurationException import org.bukkit.configuration.InvalidConfigurationException
import org.bukkit.configuration.file.FileConfiguration import org.bukkit.configuration.file.FileConfiguration
import org.bukkit.configuration.file.YamlConfiguration import org.bukkit.configuration.file.YamlConfiguration
@ -16,22 +14,19 @@ import java.io.File
import java.io.IOException import java.io.IOException
import java.util.* import java.util.*
import java.util.function.Consumer import java.util.function.Consumer
import java.util.function.Function
@HasConfig(global = true) @HasConfig(global = true)
abstract class ButtonPlugin : JavaPlugin() { abstract class ButtonPlugin : JavaPlugin() {
protected val iConfig = IHaveConfig { saveConfig() } protected val iConfig = IHaveConfig { saveConfig() }
private var yaml: CommentedConfiguration? = null private var yaml: CommentedConfiguration? = null
@Getter(AccessLevel.PROTECTED) protected val data //TODO
private val data //TODO
: IHaveConfig? = null : IHaveConfig? = null
/** /**
* Used to unregister components in the right order - and to reload configs * Used to unregister components in the right order - and to reload configs
*/ */
@Getter val componentStack = Stack<Component<*>>()
private val componentStack = Stack<Component<*>>()
protected abstract fun pluginEnable() protected abstract fun pluginEnable()
/** /**
@ -111,9 +106,12 @@ abstract class ButtonPlugin : JavaPlugin() {
this.yaml = yaml this.yaml = yaml
val res = getTextResource("configHelp.yml") ?: return true val res = getTextResource("configHelp.yml") ?: return true
val yc = YamlConfiguration.loadConfiguration(res) val yc = YamlConfiguration.loadConfiguration(res)
for ((key, value) in yc.getValues(true)) if (value is String) yaml.addComment(key.replace(".generalDescriptionInsteadOfAConfig", ""), for ((key, value) in yc.getValues(true)) if (value is String) yaml.addComment(key.replace(
*Arrays.stream<String>(value.split("\n".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()) ".generalDescriptionInsteadOfAConfig",
.map<String> { str: String -> "# " + str.trim { it <= ' ' } }.toArray<String> { _Dummy_.__Array__() }) ""
),
*value.split("\n").map { str -> "# " + str.trim { it <= ' ' } }.toTypedArray()
)
return true return true
} }
@ -148,7 +146,7 @@ abstract class ButtonPlugin : JavaPlugin() {
val command2MC = Command2MC() val command2MC = Command2MC()
fun configGenAllowed(obj: Any): Boolean { fun configGenAllowed(obj: Any): Boolean {
return !Optional.ofNullable(obj.javaClass.getAnnotation(ConfigOpts::class.java)) return !Optional.ofNullable(obj.javaClass.getAnnotation(ConfigOpts::class.java))
.map(Function<ConfigOpts, Boolean> { obj: ConfigOpts -> obj.disableConfigGen() }).orElse(false) .map { it.disableConfigGen }.orElse(false)
} }
} }
} }

View file

@ -11,38 +11,34 @@ import org.bukkit.event.Listener
import org.bukkit.plugin.java.JavaPlugin import org.bukkit.plugin.java.JavaPlugin
import java.util.* import java.util.*
import java.util.function.Consumer import java.util.function.Consumer
import java.util.function.Function
import java.util.stream.Collectors import java.util.stream.Collectors
/** /**
* Configuration is based on class name * Configuration is based on class name
*/ */
@HasConfig(global = false) //Used for obtaining javadoc @HasConfig(global = false) //Used for obtaining javadoc
abstract class Component<TP : JavaPlugin?> { abstract class Component<TP : JavaPlugin?> {
@Getter var isEnabled = false
private var enabled = false
@Getter var plugin: TP? = null
private var plugin: TP = null
@Getter val config = IHaveConfig(null)
private val config = IHaveConfig(null)
@Getter @Getter
private val data //TODO private val data //TODO
: IHaveConfig? = null : IHaveConfig? = null
@JvmField @JvmField
val shouldBeEnabled = config.getData("enabled", val shouldBeEnabled: ConfigData<Boolean> = config.getData("enabled",
Optional.ofNullable(javaClass.getAnnotation(ComponentMetadata::class.java)).map(Function<ComponentMetadata, Boolean> { obj: ComponentMetadata -> obj.enabledByDefault() }).orElse(true)) Optional.ofNullable(javaClass.getAnnotation(ComponentMetadata::class.java)).map { it.enabledByDefault }
.orElse(true))
fun log(message: String) { fun log(message: String) {
plugin!!.logger.info("[" + className + "] " + message) plugin!!.logger.info("[$className] $message")
} }
fun logWarn(message: String) { fun logWarn(message: String) {
plugin!!.logger.warning("[" + className + "] " + message) plugin!!.logger.warning("[$className] $message")
} }
/** /**
@ -85,7 +81,7 @@ abstract class Component<TP : JavaPlugin?> {
fun registerCommand(command: ICommand2MC) { fun registerCommand(command: ICommand2MC) {
if (plugin is ButtonPlugin) command.registerToPlugin(plugin as ButtonPlugin) if (plugin is ButtonPlugin) command.registerToPlugin(plugin as ButtonPlugin)
command.registerToComponent(this) command.registerToComponent(this)
ButtonPlugin.getCommand2MC().registerCommand(command) ButtonPlugin.command2MC.registerCommand(command)
} }
/** /**
@ -107,18 +103,23 @@ abstract class Component<TP : JavaPlugin?> {
* @return A map containing configs * @return A map containing configs
*/ */
fun getConfigMap(key: String?, defaultProvider: Map<String, Consumer<IHaveConfig?>>): Map<String, IHaveConfig> { fun getConfigMap(key: String?, defaultProvider: Map<String, Consumer<IHaveConfig?>>): Map<String, IHaveConfig> {
val c: ConfigurationSection = getConfig().getConfig() val c: ConfigurationSection = config.config
var cs = c.getConfigurationSection(key) var cs = c.getConfigurationSection(key)
if (cs == null) cs = c.createSection(key) if (cs == null) cs = c.createSection(key)
val res = cs!!.getValues(false).entries.stream().filter { (_, value): Map.Entry<String?, Any?> -> value is ConfigurationSection } val res = cs!!.getValues(false).entries.stream()
.collect(Collectors.toMap<Map.Entry<String?, Any?>, String, IHaveConfig>(Function<Map.Entry<String?, Any?>, String> { (key1, value) -> java.util.Map.Entry.key }, Function<Map.Entry<String?, Any?>, IHaveConfig> { (_, value): Map.Entry<String?, Any?> -> .filter { (_, value): Map.Entry<String?, Any?> -> value is ConfigurationSection }
val conf = IHaveConfig { getPlugin().saveConfig() } .collect(
Collectors.toMap<Map.Entry<String?, Any?>, String, IHaveConfig>(
{ it.key },
{ (_, value): Map.Entry<String?, Any?> ->
val conf = IHaveConfig { plugin!!.saveConfig() }
conf.reset(value as ConfigurationSection?) conf.reset(value as ConfigurationSection?)
conf conf
})) })
if (res.size == 0) { )
if (res.isEmpty()) {
for ((key1, value) in defaultProvider) { for ((key1, value) in defaultProvider) {
val conf = IHaveConfig { getPlugin().saveConfig() } val conf = IHaveConfig { plugin!!.saveConfig() }
conf.reset(cs.createSection(key1)) conf.reset(cs.createSection(key1))
value.accept(conf) value.accept(conf)
res[key1] = conf res[key1] = conf
@ -128,7 +129,7 @@ abstract class Component<TP : JavaPlugin?> {
} }
private val className: String private val className: String
private get() = javaClass.simpleName get() = javaClass.simpleName
companion object { companion object {
private val components = HashMap<Class<out Component<*>>, Component<out JavaPlugin>>() private val components = HashMap<Class<out Component<*>>, Component<out JavaPlugin>>()
@ -143,7 +144,7 @@ abstract class Component<TP : JavaPlugin?> {
* @return Whether the component is registered successfully (it may have failed to enable) * @return Whether the component is registered successfully (it may have failed to enable)
*/ */
@JvmStatic @JvmStatic
fun <T : JavaPlugin?> registerComponent(plugin: T, component: Component<T>): Boolean { fun <T : JavaPlugin> registerComponent(plugin: T, component: Component<T>): Boolean {
return registerUnregisterComponent(plugin, component, true) return registerUnregisterComponent(plugin, component, true)
} }
@ -156,29 +157,37 @@ abstract class Component<TP : JavaPlugin?> {
* @return Whether the component is unregistered successfully (it also got disabled) * @return Whether the component is unregistered successfully (it also got disabled)
*/ */
@JvmStatic @JvmStatic
fun <T : JavaPlugin?> unregisterComponent(plugin: T, component: Component<T>): Boolean { fun <T : JavaPlugin> unregisterComponent(plugin: T, component: Component<T>): Boolean {
return registerUnregisterComponent(plugin, component, false) return registerUnregisterComponent(plugin, component, false)
} }
fun <T : JavaPlugin?> registerUnregisterComponent(plugin: T, component: Component<T>, register: Boolean): Boolean { fun <T : JavaPlugin> registerUnregisterComponent(
plugin: T,
component: Component<T>,
register: Boolean
): Boolean {
return try { return try {
val metaAnn = component.javaClass.getAnnotation(ComponentMetadata::class.java) val metaAnn = component.javaClass.getAnnotation(ComponentMetadata::class.java)
if (metaAnn != null) { if (metaAnn != null) {
val dependencies: Array<Class<out Component<*>>> = metaAnn.depends() val dependencies = metaAnn.depends
for (dep in dependencies) { //TODO: Support dependencies at enable/disable as well for (dep in dependencies) { //TODO: Support dependencies at enable/disable as well
if (!components.containsKey(dep)) { if (!components.containsKey(dep.java)) {
plugin!!.logger.warning("Failed to " + (if (register) "" else "un") + "register component " + component.className + " as a required dependency is missing/disabled: " + dep.simpleName) plugin.logger.warning("Failed to " + (if (register) "" else "un") + "register component " + component.className + " as a required dependency is missing/disabled: " + dep.simpleName)
return false return false
} }
} }
} }
if (register) { if (register) {
if (components.containsKey(component.javaClass)) { if (components.containsKey(component.javaClass)) {
TBMCCoreAPI.SendException("Failed to register component " + component.className, IllegalArgumentException("The component is already registered!"), plugin) TBMCCoreAPI.SendException(
"Failed to register component " + component.className,
IllegalArgumentException("The component is already registered!"),
plugin
)
return false return false
} }
component.plugin = plugin component.plugin = plugin // TODO: Perhaps construct a new object with these initialized
component.config.saveAction = Runnable { plugin!!.saveConfig() } component.config.saveAction = Runnable { plugin.saveConfig() }
updateConfig(plugin, component) updateConfig(plugin, component)
component.register(plugin) component.register(plugin)
components[component.javaClass] = component components[component.javaClass] = component
@ -188,7 +197,11 @@ abstract class Component<TP : JavaPlugin?> {
setComponentEnabled(component, true) setComponentEnabled(component, true)
true true
} catch (e: Exception) { } catch (e: Exception) {
TBMCCoreAPI.SendException("Failed to enable component " + component.className + "!", e, component) TBMCCoreAPI.SendException(
"Failed to enable component " + component.className + "!",
e,
component
)
true true
} catch (e: NoClassDefFoundError) { } catch (e: NoClassDefFoundError) {
TBMCCoreAPI.SendException("Failed to enable component " + component.className + "!", e, component) TBMCCoreAPI.SendException("Failed to enable component " + component.className + "!", e, component)
@ -197,14 +210,22 @@ abstract class Component<TP : JavaPlugin?> {
} }
} else { } else {
if (!components.containsKey(component.javaClass)) return true //Already unregistered if (!components.containsKey(component.javaClass)) return true //Already unregistered
if (component.enabled) { if (component.isEnabled) {
try { try {
setComponentEnabled(component, false) setComponentEnabled(component, false)
} catch (e: Exception) { } catch (e: Exception) {
TBMCCoreAPI.SendException("Failed to disable component " + component.className + "!", e, component) TBMCCoreAPI.SendException(
"Failed to disable component " + component.className + "!",
e,
component
)
return false //If failed to disable, won't unregister either return false //If failed to disable, won't unregister either
} catch (e: NoClassDefFoundError) { } catch (e: NoClassDefFoundError) {
TBMCCoreAPI.SendException("Failed to disable component " + component.className + "!", e, component) TBMCCoreAPI.SendException(
"Failed to disable component " + component.className + "!",
e,
component
)
return false return false
} }
} }
@ -228,10 +249,10 @@ abstract class Component<TP : JavaPlugin?> {
@Throws(UnregisteredComponentException::class) @Throws(UnregisteredComponentException::class)
fun setComponentEnabled(component: Component<*>, enabled: Boolean) { fun setComponentEnabled(component: Component<*>, enabled: Boolean) {
if (!components.containsKey(component.javaClass)) throw UnregisteredComponentException(component) if (!components.containsKey(component.javaClass)) throw UnregisteredComponentException(component)
if (component.enabled == enabled) return //Don't do anything if (component.isEnabled == enabled) return //Don't do anything
if (enabled.also { component.enabled = it }) { if (enabled.also { component.isEnabled = it }) {
try { try {
updateConfig(component.getPlugin(), component) updateConfig(component.plugin!!, component)
component.enable() component.enable()
if (ButtonPlugin.configGenAllowed(component)) { if (ButtonPlugin.configGenAllowed(component)) {
IHaveConfig.pregenConfig(component, null) IHaveConfig.pregenConfig(component, null)
@ -253,7 +274,7 @@ abstract class Component<TP : JavaPlugin?> {
} }
} else { } else {
component.disable() component.disable()
ButtonPlugin.getCommand2MC().unregisterCommands(component) ButtonPlugin.command2MC.unregisterCommands(component)
} }
} }

View file

@ -1,252 +1,260 @@
package buttondevteam.lib.architecture; package buttondevteam.lib.architecture
import buttondevteam.core.MainPlugin; import buttondevteam.core.MainPlugin
import buttondevteam.lib.ChromaUtils; import buttondevteam.lib.ChromaUtils
import lombok.*; import buttondevteam.lib.architecture.IHaveConfig.getConfig
import org.bukkit.Bukkit; import lombok.*
import org.bukkit.configuration.Configuration; import org.bukkit.Bukkit
import org.bukkit.configuration.file.YamlConfiguration; import org.bukkit.configuration.Configuration
import org.bukkit.scheduler.BukkitTask; import org.bukkit.scheduler.BukkitTask
import java.util.function.BiFunction
import java.lang.reflect.Array; import java.util.function.Function
import java.util.HashMap;
import java.util.List;
import java.util.function.BiFunction;
import java.util.function.Function;
/** /**
* Use the getter/setter constructor if {@link T} isn't a primitive type or String.<br> * Use the getter/setter constructor if [T] isn't a primitive type or String.<br></br>
* Use {@link Component#getConfig()} or {@link ButtonPlugin#getIConfig()} then {@link IHaveConfig#getData(String, Object)} to get an instance. * Use [Component.getConfig] or [ButtonPlugin.getIConfig] then [IHaveConfig.getData] to get an instance.
*/ */
public class ConfigData<T> { open class ConfigData<T> internal constructor(
private static final HashMap<Configuration, SaveTask> saveTasks = new HashMap<>(); config: IHaveConfig?,
path: String?,
def: T,
primitiveDef: Any?,
getter: Function<Any?, T>?,
setter: Function<T, Any?>?
) {
/** /**
* May be null for testing * May be null for testing
*/ */
private IHaveConfig config; private val config: IHaveConfig?
@Getter @Getter
@Setter(AccessLevel.PACKAGE) @Setter(AccessLevel.PACKAGE)
private String path; private val path: String?
protected final T def; protected val def: T?
private final Object primitiveDef; private val primitiveDef: Any?
/** /**
* The parameter is of a primitive type as returned by {@link YamlConfiguration#get(String)} * The parameter is of a primitive type as returned by [YamlConfiguration.get]
*/ */
private final Function<Object, T> getter; private val getter: Function<Any?, T>?
/** /**
* The result should be a primitive type or string that can be retrieved correctly later * The result should be a primitive type or string that can be retrieved correctly later
*/ */
private final Function<T, Object> setter; private val setter: Function<T, Any?>?
/** /**
* The config value should not change outside this instance * The config value should not change outside this instance
*/ */
private T value; private var value: T? = null
ConfigData(IHaveConfig config, String path, T def, Object primitiveDef, Function<Object, T> getter, Function<T, Object> setter) { init {
var def: T? = def
var primitiveDef = primitiveDef
if (def == null) { if (def == null) {
if (primitiveDef == null) requireNotNull(primitiveDef) { "Either def or primitiveDef must be set." }
throw new IllegalArgumentException("Either def or primitiveDef must be set."); requireNotNull(getter) { "A getter and setter must be present when using primitiveDef." }
if (getter == null) def = getter.apply(primitiveDef)
throw new IllegalArgumentException("A getter and setter must be present when using primitiveDef."); } else if (primitiveDef == null) primitiveDef = if (setter == null) def else setter.apply(def)
def = getter.apply(primitiveDef); require(getter == null == (setter == null)) { "Both setters and getters must be present (or none if def is primitive)." }
} else if (primitiveDef == null) this.config = config
if (setter == null) this.path = path
primitiveDef = def; this.def = def
else this.primitiveDef = primitiveDef
primitiveDef = setter.apply(def); this.getter = getter
if ((getter == null) != (setter == null)) this.setter = setter
throw new IllegalArgumentException("Both setters and getters must be present (or none if def is primitive)."); get() //Generate config automatically
this.config = config;
this.path = path;
this.def = def;
this.primitiveDef = primitiveDef;
this.getter = getter;
this.setter = setter;
get(); //Generate config automatically
} }
@Override override fun toString(): String {
public String toString() { return "ConfigData{path='$path', value=$value}"
return "ConfigData{" + "path='" + path + '\'' + ", value=" + value + '}';
} }
void reset() { fun reset() {
value = null; value = null
} }
@SuppressWarnings("unchecked") fun get(): T? {
public T get() { if (value != null) return value //Speed things up
if (value != null) return value; //Speed things up val config = config!!.getConfig<Any>()
var config = this.config.getConfig(); var `val`: Any?
Object val;
if (config == null || !config.isSet(path)) { //Call set() if config == null if (config == null || !config.isSet(path)) { //Call set() if config == null
val = primitiveDef; `val` = primitiveDef
if ((def == null || this instanceof ReadOnlyConfigData) && config != null) //In Discord's case def may be null if ((def == null || this is ReadOnlyConfigData<*>) && config != null) //In Discord's case def may be null
setInternal(primitiveDef); //If read-only then we still need to save the default value so it can be set setInternal(primitiveDef) //If read-only then we still need to save the default value so it can be set
else else set(def) //Save default value - def is always set
set(def); //Save default value - def is always set } else `val` = config.get(path) //config==null: testing
} else if (`val` == null) //If it's set to null explicitly
val = config.get(path); //config==null: testing `val` = primitiveDef
if (val == null) //If it's set to null explicitly val convert = BiFunction { _val: Any?, _def: Any? ->
val = primitiveDef; if (_def is Number) //If we expect a number
BiFunction<Object, Object, Object> convert = (_val, _def) -> { _val = if (_val is Number) ChromaUtils.convertNumber(
if (_def instanceof Number) //If we expect a number _val as Number?,
if (_val instanceof Number) _def.javaClass as Class<out Number?>
_val = ChromaUtils.convertNumber((Number) _val, ) else _def //If we didn't get a number, return default (which is a number)
(Class<? extends Number>) _def.getClass()); else if (_val is List<*> && _def != null && _def.javaClass.isArray) _val = (_val as List<T>).toArray<T>(
else java.lang.reflect.Array.newInstance(
_val = _def; //If we didn't get a number, return default (which is a number) _def.javaClass.componentType,
else if (_val instanceof List && _def != null && _def.getClass().isArray()) 0
_val = ((List<T>) _val).toArray((T[]) Array.newInstance(_def.getClass().getComponentType(), 0)); ) as Array<T>
return _val; )
}; _val
}
if (getter != null) { if (getter != null) {
val = convert.apply(val, primitiveDef); `val` = convert.apply(`val`, primitiveDef)
T hmm = getter.apply(val); var hmm: T? = getter.apply(`val`)
if (hmm == null) hmm = def; //Set if the getter returned null if (hmm == null) hmm = def //Set if the getter returned null
return hmm; return hmm
}
`val` = convert.apply(`val`, def)
return `val` as T?. also {
value = it //Always cache, if not cached yet
} }
val = convert.apply(val, def);
return value = (T) val; //Always cache, if not cached yet
} }
public void set(T value) { fun set(value: T?) {
if (this instanceof ReadOnlyConfigData) if (this is ReadOnlyConfigData<*>) return //Safety for Discord channel/role data
return; //Safety for Discord channel/role data val `val`: Any?
Object val; `val` = if (setter != null && value != null) setter.apply(value) else value
if (setter != null && value != null) if (config!!.getConfig<Any>() != null) setInternal(`val`)
val = setter.apply(value); this.value = value
else val = value;
if (config.getConfig() != null)
setInternal(val);
this.value = value;
} }
private void setInternal(Object val) { private fun setInternal(`val`: Any?) {
config.getConfig().set(path, val); config!!.getConfig<Any>().set(path, `val`)
signalChange(config); signalChange(config)
}
static void signalChange(IHaveConfig config) {
var cc = config.getConfig();
var sa = config.getSaveAction();
if (!saveTasks.containsKey(cc.getRoot())) {
synchronized (saveTasks) {
saveTasks.put(cc.getRoot(), new SaveTask(Bukkit.getScheduler().runTaskLaterAsynchronously(MainPlugin.Instance, () -> {
synchronized (saveTasks) {
saveTasks.remove(cc.getRoot());
sa.run();
}
}, 100), sa));
}
}
} }
@AllArgsConstructor @AllArgsConstructor
private static class SaveTask { private class SaveTask {
BukkitTask task; var task: BukkitTask? = null
Runnable saveAction; var saveAction: Runnable? = null
}
public static boolean saveNow(Configuration config) {
synchronized (saveTasks) {
SaveTask st = saveTasks.get(config);
if (st != null) {
st.task.cancel();
saveTasks.remove(config);
st.saveAction.run();
return true;
}
}
return false;
}
public static <T> ConfigData.ConfigDataBuilder<T> builder(IHaveConfig config, String path) {
return new ConfigDataBuilder<T>(config, path);
} }
@RequiredArgsConstructor(access = AccessLevel.PACKAGE) @RequiredArgsConstructor(access = AccessLevel.PACKAGE)
public static class ConfigDataBuilder<T> { class ConfigDataBuilder<T> {
private final IHaveConfig config; private val config: IHaveConfig? = null
private final String path; private val path: String? = null
private T def; private var def: T? = null
private Object primitiveDef; private var primitiveDef: Any? = null
private Function<Object, T> getter; private var getter: Function<Any?, T?>? = null
private Function<T, Object> setter; private var setter: Function<T?, Any?>? = null
/** /**
* The default value to use, as used in code. If not a primitive type, use the {@link #getter(Function)} and {@link #setter(Function)} methods. * The default value to use, as used in code. If not a primitive type, use the [.getter] and [.setter] methods.
* <br/> * <br></br>
* To set the value as it is stored, use {@link #primitiveDef(Object)}. * To set the value as it is stored, use [.primitiveDef].
* *
* @param def The default value * @param def The default value
* @return This builder * @return This builder
*/ */
public ConfigDataBuilder<T> def(T def) { fun def(def: T): ConfigDataBuilder<T> {
this.def = def; this.def = def
return this; return this
} }
/** /**
* The default value to use, as stored in yaml. Must be a primitive type. Make sure to use the {@link #getter(Function)} and {@link #setter(Function)} methods. * The default value to use, as stored in yaml. Must be a primitive type. Make sure to use the [.getter] and [.setter] methods.
* <br/> * <br></br>
* To set the value as used in the code, use {@link #def(Object)}. * To set the value as used in the code, use [.def].
* *
* @param primitiveDef The default value * @param primitiveDef The default value
* @return This builder * @return This builder
*/ */
public ConfigDataBuilder<T> primitiveDef(Object primitiveDef) { fun primitiveDef(primitiveDef: Any?): ConfigDataBuilder<T> {
this.primitiveDef = primitiveDef; this.primitiveDef = primitiveDef
return this; return this
} }
/** /**
* A function to use to obtain the runtime object from the yaml representation (usually string). * A function to use to obtain the runtime object from the yaml representation (usually string).
* The {@link #setter(Function)} must also be set. * The [.setter] must also be set.
* *
* @param getter A function that receives the primitive type and returns the runtime type * @param getter A function that receives the primitive type and returns the runtime type
* @return This builder * @return This builder
*/ */
public ConfigDataBuilder<T> getter(Function<Object, T> getter) { fun getter(getter: Function<Any?, T>?): ConfigDataBuilder<T> {
this.getter = getter; this.getter = getter
return this; return this
} }
/** /**
* A function to use to obtain the yaml representation (usually string) from the runtime object. * A function to use to obtain the yaml representation (usually string) from the runtime object.
* The {@link #getter(Function)} must also be set. * The [.getter] must also be set.
* *
* @param setter A function that receives the runtime type and returns the primitive type * @param setter A function that receives the runtime type and returns the primitive type
* @return This builder * @return This builder
*/ */
public ConfigDataBuilder<T> setter(Function<T, Object> setter) { fun setter(setter: Function<T, Any?>?): ConfigDataBuilder<T> {
this.setter = setter; this.setter = setter
return this; return this
} }
/** /**
* Builds a modifiable config representation. Use if you want to change the value <i>in code</i>. * Builds a modifiable config representation. Use if you want to change the value *in code*.
* *
* @return A ConfigData instance. * @return A ConfigData instance.
*/ */
public ConfigData<T> build() { fun build(): ConfigData<T?> {
ConfigData<T> config = new ConfigData<>(this.config, path, def, primitiveDef, getter, setter); val config = ConfigData(config, path, def, primitiveDef, getter, setter)
this.config.onConfigBuild(config); this.config!!.onConfigBuild(config)
return config; return config
} }
/** /**
* Builds a read-only config representation. Use if you only want the value to be changed <i>in the config</i>. * Builds a read-only config representation. Use if you only want the value to be changed *in the config*.
* *
* @return A ReadOnlyConfigData instance. * @return A ReadOnlyConfigData instance.
*/ */
public ReadOnlyConfigData<T> buildReadOnly() { fun buildReadOnly(): ReadOnlyConfigData<T?> {
ReadOnlyConfigData<T> config = new ReadOnlyConfigData<>(this.config, path, def, primitiveDef, getter, setter); val config = ReadOnlyConfigData(config, path, def, primitiveDef, getter, setter)
this.config.onConfigBuild(config); this.config!!.onConfigBuild(config)
return config; return config
} }
public String toString() {return "ConfigData.ConfigDataBuilder(config=" + this.config + ", path=" + this.path + ", def=" + this.def + ", primitiveDef=" + this.primitiveDef + ", getter=" + this.getter + ", setter=" + this.setter + ")";} override fun toString(): String {
return "ConfigData.ConfigDataBuilder(config=" + config + ", path=" + path + ", def=" + def + ", primitiveDef=" + primitiveDef + ", getter=" + getter + ", setter=" + setter + ")"
}
}
companion object {
private val saveTasks = HashMap<Configuration, SaveTask>()
fun signalChange(config: IHaveConfig?) {
val cc = config!!.getConfig<Any>()
val sa = config.saveAction
if (!saveTasks.containsKey(cc.getRoot())) {
synchronized(saveTasks) {
saveTasks.put(
cc.getRoot(),
SaveTask(Bukkit.getScheduler().runTaskLaterAsynchronously(MainPlugin.Instance, {
synchronized(
saveTasks
) {
saveTasks.remove(cc.getRoot())
sa!!.run()
}
}, 100), sa)
)
}
}
}
@JvmStatic
fun saveNow(config: Configuration): Boolean {
synchronized(saveTasks) {
val st = saveTasks[config]
if (st != null) {
st.task!!.cancel()
saveTasks.remove(config)
st.saveAction!!.run()
return true
}
}
return false
}
fun <T> builder(config: IHaveConfig?, path: String?): ConfigDataBuilder<T> {
return ConfigDataBuilder(config, path)
}
} }
} }

View file

@ -1,43 +1,33 @@
package buttondevteam.lib.architecture; package buttondevteam.lib.architecture
import buttondevteam.core.MainPlugin; import buttondevteam.core.MainPlugin
import buttondevteam.lib.TBMCCoreAPI; import buttondevteam.lib.TBMCCoreAPI
import lombok.Getter; import buttondevteam.lib.architecture.ConfigData.ConfigDataBuilder
import lombok.Setter; import lombok.Getter
import lombok.val; import org.bukkit.Bukkit
import org.bukkit.Bukkit; import org.bukkit.configuration.ConfigurationSection
import org.bukkit.configuration.ConfigurationSection; import org.bukkit.plugin.java.JavaPlugin
import org.bukkit.plugin.java.JavaPlugin; import java.lang.reflect.InvocationTargetException
import java.util.*
import javax.annotation.Nullable; import java.util.function.Function
import java.lang.reflect.InvocationTargetException; import java.util.function.Predicate
import java.util.*; import java.util.function.Supplier
import java.util.function.Function; import java.util.stream.Collectors
import java.util.function.Supplier;
import java.util.stream.Collectors;
/** /**
* A config system * A config system
*/
public final class IHaveConfig {
private final HashMap<String, ConfigData<?>> datamap = new HashMap<>();
/**
* Returns the Bukkit ConfigurationSection. Use {@link #signalChange()} after changing it.
*/
@Getter
private ConfigurationSection config;
@Getter
@Setter
private Runnable saveAction;
/**
* May be used in testing. * 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. * @param saveAction What to do to save the config to disk. Don't use get methods until it's non-null.
*/ */
public IHaveConfig(Runnable saveAction) { class IHaveConfig(var saveAction: Runnable?) { // TODO: Make non-nullable after adding component builder
this.saveAction = saveAction; private val datamap = HashMap<String, ConfigData<*>>()
}
/**
* Returns the Bukkit ConfigurationSection. Use [.signalChange] after changing it.
*/
@Getter
private var config: ConfigurationSection? = null
/** /**
* 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. * 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.
@ -45,13 +35,13 @@ public final class IHaveConfig {
* @param path The dot-separated path relative to this config instance * @param path The dot-separated path relative to this config instance
* @param <T> The runtime type of the config value * @param <T> The runtime type of the config value
* @return A ConfigData builder to set how to obtain the value * @return A ConfigData builder to set how to obtain the value
*/ </T> */
public <T> ConfigData.ConfigDataBuilder<T> getConfig(String path) { fun <T> getConfig(path: String?): ConfigDataBuilder<T> {
return ConfigData.builder(this, path); return ConfigData.builder(this, path)
} }
void onConfigBuild(ConfigData<?> config) { fun onConfigBuild(config: ConfigData<*>) {
datamap.put(config.getPath(), config); datamap[config.path] = config
} }
/** /**
@ -61,12 +51,12 @@ public final class IHaveConfig {
* @param def The value to use by default * @param def The value to use by default
* @param <T> The type of this variable (only use primitives or String) * @param <T> The type of this variable (only use primitives or String)
* @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> */
@SuppressWarnings("unchecked") fun <T> getData(path: String, def: T): ConfigData<T> {
public <T> ConfigData<T> getData(String path, T def) { var data = datamap[path]
ConfigData<?> data = datamap.get(path); if (data == null) datamap[path] = ConfigData(this, path, def, def, null, null).also { data = it }
if (data == null) datamap.put(path, data = new ConfigData<>(this, path, def, def, null, null)); @Suppress("UNCHECKED_CAST")
return (ConfigData<T>) data; return data as ConfigData<T>
} }
/** /**
@ -78,49 +68,59 @@ public final class IHaveConfig {
* @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 <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> */
@SuppressWarnings("unchecked") fun <T> getData(path: String, def: T, getter: Function<Any?, T>?, setter: Function<T, Any?>): ConfigData<T> {
public <T> ConfigData<T> getData(String path, T def, Function<Object, T> getter, Function<T, Object> setter) { var data = datamap[path]
ConfigData<?> data = datamap.get(path); if (data == null) datamap[path] =
if (data == null) ConfigData(this, path, def, setter.apply(def), getter, setter).also { data = it }
datamap.put(path, data = new ConfigData<>(this, path, def, setter.apply(def), getter, setter)); @Suppress("UNCHECKED_CAST")
return (ConfigData<T>) data; return data as ConfigData<T>
} }
/** /**
* This method overload may be used with any class. The given default value will be run through the getter. * 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 path The path in config to use
* @param primitiveDef The <b>primitive</b> value to use by default * @param primitiveDef The **primitive** 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 <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> */
@SuppressWarnings("unchecked") fun <T> getDataPrimDef(
public <T> ConfigData<T> getDataPrimDef(String path, Object primitiveDef, Function<Object, T> getter, Function<T, Object> setter) { path: String,
ConfigData<?> data = datamap.get(path); primitiveDef: Any?,
if (data == null) getter: Function<Any?, T>,
datamap.put(path, data = new ConfigData<>(this, path, getter.apply(primitiveDef), primitiveDef, getter, setter)); setter: Function<T, Any?>?
return (ConfigData<T>) data; ): ConfigData<T> {
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<T>
} }
/** /**
* This method overload may be used with any class. The given default value will be run through the getter. * 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 path The path in config to use
* @param primitiveDef The <b>primitive</b> value to use by default * @param primitiveDef The **primitive** 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 <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> */
@SuppressWarnings("unchecked") fun <T> getReadOnlyDataPrimDef(
public <T> ReadOnlyConfigData<T> getReadOnlyDataPrimDef(String path, Object primitiveDef, Function<Object, T> getter, Function<T, Object> setter) { path: String,
ConfigData<?> data = datamap.get(path); primitiveDef: Any?,
if (data == null) getter: Function<Any?, T>,
datamap.put(path, data = new ReadOnlyConfigData<>(this, path, getter.apply(primitiveDef), primitiveDef, getter, setter)); setter: Function<T, Any?>?
return (ReadOnlyConfigData<T>) data; ): ReadOnlyConfigData<T> {
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<T>
} }
/** /**
@ -130,15 +130,15 @@ public final class IHaveConfig {
* @param def The value to use by default * @param def The value to use by default
* @param <T> The type of this variable (only use primitives or String) * @param <T> The type of this variable (only use primitives or String)
* @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> */
@SuppressWarnings("unchecked") fun <T> getData(path: String, def: Supplier<T>): ConfigData<T> {
public <T> ConfigData<T> getData(String path, Supplier<T> def) { var data = datamap[path]
ConfigData<?> data = datamap.get(path);
if (data == null) { if (data == null) {
val defval = def.get(); val defval = def.get()
datamap.put(path, data = new ConfigData<>(this, path, defval, defval, null, null)); datamap[path] = ConfigData(this, path, defval, defval, null, null).also { data = it }
} }
return (ConfigData<T>) data; @Suppress("UNCHECKED_CAST")
return data as ConfigData<T>
} }
/** /**
@ -150,15 +150,20 @@ public final class IHaveConfig {
* @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 <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> */
@SuppressWarnings("unchecked") fun <T> getData(
public <T> ConfigData<T> getData(String path, Supplier<T> def, Function<Object, T> getter, Function<T, Object> setter) { path: String,
ConfigData<?> data = datamap.get(path); def: Supplier<T>,
getter: Function<Any?, T>?,
setter: Function<T, Any?>
): ConfigData<T> {
var data = datamap[path]
if (data == null) { if (data == null) {
val defval = def.get(); val defval = def.get()
datamap.put(path, data = new ConfigData<>(this, path, defval, setter.apply(defval), getter, setter)); datamap[path] = ConfigData(this, path, defval, setter.apply(defval), getter, setter).also { data = it }
} }
return (ConfigData<T>) data; @Suppress("UNCHECKED_CAST")
return data as ConfigData<T>
} }
/** /**
@ -167,89 +172,114 @@ public final class IHaveConfig {
* @param path The path in config to use * @param path The path in config to use
* @param <T> The type of this variable (only use primitives or String) * @param <T> The type of this variable (only use primitives or String)
* @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> */
@SuppressWarnings("unchecked") fun <T> getListData(path: String): ListConfigData<T> {
public <T> ListConfigData<T> getListData(String path) { var data = datamap[path]
ConfigData<?> data = datamap.get(path); if (data == null) datamap[path] = ListConfigData(this, path, ListConfigData.List<T>()).also { data = it }
if (data == null) @Suppress("UNCHECKED_CAST")
datamap.put(path, data = new ListConfigData<>(this, path, new ListConfigData.List<T>())); return data as ListConfigData<T>
return (ListConfigData<T>) data;
} }
/** /**
* Schedules a save operation. Use after changing the ConfigurationSection directly. * Schedules a save operation. Use after changing the ConfigurationSection directly.
*/ */
public void signalChange() { fun signalChange() {
ConfigData.signalChange(this); ConfigData.signalChange(this)
} }
/** /**
* Clears all caches and loads everything from yaml. * Clears all caches and loads everything from yaml.
*/ */
public void reset(ConfigurationSection config) { fun reset(config: ConfigurationSection?) {
this.config = config; this.config = config
datamap.forEach((path, data) -> data.reset()); datamap.forEach { (path: String?, data: ConfigData<*>) -> data.reset() }
} }
companion object {
/** /**
* Generates the config YAML. * Generates the config YAML.
* *
* @param obj The object which has config methods * @param obj The object which has config methods
* @param configMap The result from {@link Component#getConfigMap(String, Map)}. May be null. * @param configMap The result from [Component.getConfigMap]. May be null.
*/ */
public static void pregenConfig(Object obj, @Nullable Map<String, IHaveConfig> configMap) { fun pregenConfig(obj: Any, configMap: Map<String, IHaveConfig?>?) {
val ms = obj.getClass().getDeclaredMethods(); val ms = obj.javaClass.declaredMethods
for (val m : ms) { for (m in ms) {
if (!m.getReturnType().getName().equals(ConfigData.class.getName())) continue; if (m.returnType.name != ConfigData::class.java.name) continue
final String mName; val mName: String
{ run {
var name = m.getName(); val name = m.name
var ind = name.lastIndexOf('$'); val ind = name.lastIndexOf('$')
if (ind == -1) mName = name; mName = if (ind == -1) name else name.substring(ind + 1)
else mName = name.substring(ind + 1);
} }
try { try {
m.setAccessible(true); m.isAccessible = true
List<ConfigData<?>> configList; var configList: List<ConfigData<*>>
if (m.getParameterCount() == 0) { configList = if (m.parameterCount == 0) {
configList = Collections.singletonList((ConfigData<?>) m.invoke(obj)); listOf(m.invoke(obj) as ConfigData<*>)
} else if (m.getParameterCount() == 1 && m.getParameterTypes()[0] == IHaveConfig.class) { } else if (m.parameterCount == 1 && m.parameterTypes[0] == IHaveConfig::class.java) {
if (configMap == null) continue; //Hope it will get called with the param later if (configMap == null) continue //Hope it will get called with the param later
configList = configMap.entrySet().stream().map(kv -> configMap.entries.stream().map { (key, value): Map.Entry<String, IHaveConfig?> ->
{
try { try {
return (ConfigData<?>) m.invoke(obj, kv.getValue()); return@map m.invoke(obj, value) as ConfigData<*>
} catch (IllegalAccessException | InvocationTargetException e) { } catch (e: IllegalAccessException) {
String msg = "Failed to pregenerate " + mName + " for " + obj + " using config " + kv.getKey() + "!"; val msg = "Failed to pregenerate $mName for $obj using config $key!"
if (obj instanceof Component<?>) if (obj is Component<*>) TBMCCoreAPI.SendException(
TBMCCoreAPI.SendException(msg, e, (Component<?>) obj); msg,
else if (obj instanceof JavaPlugin) e,
TBMCCoreAPI.SendException(msg, e, (JavaPlugin) obj); obj
else ) else if (obj is JavaPlugin) TBMCCoreAPI.SendException(
TBMCCoreAPI.SendException(msg, e, false, Bukkit.getLogger()::warning); msg,
return null; e,
obj
) else TBMCCoreAPI.SendException(msg, e, false) { msg: String? ->
Bukkit.getLogger().warning(msg)
} }
}).filter(Objects::nonNull).collect(Collectors.toList()); return@map null
} catch (e: InvocationTargetException) {
val msg = "Failed to pregenerate $mName for $obj using config $key!"
if (obj is Component<*>) TBMCCoreAPI.SendException(
msg,
e,
obj
) else if (obj is JavaPlugin) TBMCCoreAPI.SendException(
msg,
e,
obj
) else TBMCCoreAPI.SendException(msg, e, false) { msg: String? ->
Bukkit.getLogger().warning(msg)
}
return@map null
}
}
.filter(Predicate<ConfigData<Any?>> { obj: ConfigData<Any?>? -> Objects.nonNull(obj) })
.collect(Collectors.toList())
} else { } else {
if (TBMCCoreAPI.IsTestServer()) if (TBMCCoreAPI.IsTestServer()) MainPlugin.Instance.logger.warning(
MainPlugin.Instance.getLogger().warning("Method " + mName + " returns a config but its parameters are unknown: " + Arrays.toString(m.getParameterTypes())); "Method " + mName + " returns a config but its parameters are unknown: " + Arrays.toString(
continue; m.parameterTypes
)
)
continue
} }
for (val c : configList) { for (c in configList) {
if (c.getPath().length() == 0) if (c.path.length == 0) c.setPath(mName) else if (c.path != mName) MainPlugin.Instance.logger.warning(
c.setPath(mName); "Config name does not match: " + c.path + " instead of " + mName
else if (!c.getPath().equals(mName)) )
MainPlugin.Instance.getLogger().warning("Config name does not match: " + c.getPath() + " instead of " + mName); c.get() //Saves the default value if needed - also checks validity
c.get(); //Saves the default value if needed - also checks validity }
} catch (e: Exception) {
val msg = "Failed to pregenerate $mName for $obj!"
if (obj is Component<*>) TBMCCoreAPI.SendException(
msg,
e,
obj
) else if (obj is JavaPlugin) TBMCCoreAPI.SendException(msg, e, obj) else TBMCCoreAPI.SendException(
msg,
e,
false
) { msg: String? -> Bukkit.getLogger().warning(msg) }
} }
} catch (Exception e) {
String msg = "Failed to pregenerate " + mName + " for " + obj + "!";
if (obj instanceof Component<?>)
TBMCCoreAPI.SendException(msg, e, (Component<?>) obj);
else if (obj instanceof JavaPlugin)
TBMCCoreAPI.SendException(msg, e, (JavaPlugin) obj);
else
TBMCCoreAPI.SendException(msg, e, false, Bukkit.getLogger()::warning);
} }
} }
} }