Convert config stuff and fix issues
This commit is contained in:
parent
65be3b2df9
commit
482df40992
5 changed files with 576 additions and 518 deletions
|
@ -34,6 +34,7 @@
|
|||
<executions>
|
||||
<execution>
|
||||
<id>compile</id>
|
||||
<phase>process-sources</phase>
|
||||
<goals>
|
||||
<goal>compile</goal>
|
||||
</goals>
|
||||
|
|
|
@ -6,8 +6,6 @@ import buttondevteam.lib.TBMCCoreAPI
|
|||
import buttondevteam.lib.architecture.Component.Companion.updateConfig
|
||||
import buttondevteam.lib.chat.Command2MC
|
||||
import buttondevteam.lib.chat.ICommand2MC
|
||||
import lombok.AccessLevel
|
||||
import lombok.Getter
|
||||
import org.bukkit.configuration.InvalidConfigurationException
|
||||
import org.bukkit.configuration.file.FileConfiguration
|
||||
import org.bukkit.configuration.file.YamlConfiguration
|
||||
|
@ -16,22 +14,19 @@ import java.io.File
|
|||
import java.io.IOException
|
||||
import java.util.*
|
||||
import java.util.function.Consumer
|
||||
import java.util.function.Function
|
||||
|
||||
@HasConfig(global = true)
|
||||
abstract class ButtonPlugin : JavaPlugin() {
|
||||
protected val iConfig = IHaveConfig { saveConfig() }
|
||||
private var yaml: CommentedConfiguration? = null
|
||||
|
||||
@Getter(AccessLevel.PROTECTED)
|
||||
private val data //TODO
|
||||
protected val data //TODO
|
||||
: IHaveConfig? = null
|
||||
|
||||
/**
|
||||
* Used to unregister components in the right order - and to reload configs
|
||||
*/
|
||||
@Getter
|
||||
private val componentStack = Stack<Component<*>>()
|
||||
val componentStack = Stack<Component<*>>()
|
||||
protected abstract fun pluginEnable()
|
||||
|
||||
/**
|
||||
|
@ -111,9 +106,12 @@ abstract class ButtonPlugin : JavaPlugin() {
|
|||
this.yaml = yaml
|
||||
val res = getTextResource("configHelp.yml") ?: return true
|
||||
val yc = YamlConfiguration.loadConfiguration(res)
|
||||
for ((key, value) in yc.getValues(true)) if (value is String) yaml.addComment(key.replace(".generalDescriptionInsteadOfAConfig", ""),
|
||||
*Arrays.stream<String>(value.split("\n".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray())
|
||||
.map<String> { str: String -> "# " + str.trim { it <= ' ' } }.toArray<String> { _Dummy_.__Array__() })
|
||||
for ((key, value) in yc.getValues(true)) if (value is String) yaml.addComment(key.replace(
|
||||
".generalDescriptionInsteadOfAConfig",
|
||||
""
|
||||
),
|
||||
*value.split("\n").map { str -> "# " + str.trim { it <= ' ' } }.toTypedArray()
|
||||
)
|
||||
return true
|
||||
}
|
||||
|
||||
|
@ -148,7 +146,7 @@ abstract class ButtonPlugin : JavaPlugin() {
|
|||
val command2MC = Command2MC()
|
||||
fun configGenAllowed(obj: Any): Boolean {
|
||||
return !Optional.ofNullable(obj.javaClass.getAnnotation(ConfigOpts::class.java))
|
||||
.map(Function<ConfigOpts, Boolean> { obj: ConfigOpts -> obj.disableConfigGen() }).orElse(false)
|
||||
.map { it.disableConfigGen }.orElse(false)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -11,38 +11,34 @@ import org.bukkit.event.Listener
|
|||
import org.bukkit.plugin.java.JavaPlugin
|
||||
import java.util.*
|
||||
import java.util.function.Consumer
|
||||
import java.util.function.Function
|
||||
import java.util.stream.Collectors
|
||||
|
||||
/**
|
||||
* Configuration is based on class name
|
||||
*/
|
||||
@HasConfig(global = false) //Used for obtaining javadoc
|
||||
|
||||
abstract class Component<TP : JavaPlugin?> {
|
||||
@Getter
|
||||
private var enabled = false
|
||||
var isEnabled = false
|
||||
|
||||
@Getter
|
||||
private var plugin: TP = null
|
||||
var plugin: TP? = null
|
||||
|
||||
@Getter
|
||||
private val config = IHaveConfig(null)
|
||||
val config = IHaveConfig(null)
|
||||
|
||||
@Getter
|
||||
private val data //TODO
|
||||
: IHaveConfig? = null
|
||||
|
||||
@JvmField
|
||||
val shouldBeEnabled = config.getData("enabled",
|
||||
Optional.ofNullable(javaClass.getAnnotation(ComponentMetadata::class.java)).map(Function<ComponentMetadata, Boolean> { obj: ComponentMetadata -> obj.enabledByDefault() }).orElse(true))
|
||||
val shouldBeEnabled: ConfigData<Boolean> = config.getData("enabled",
|
||||
Optional.ofNullable(javaClass.getAnnotation(ComponentMetadata::class.java)).map { it.enabledByDefault }
|
||||
.orElse(true))
|
||||
|
||||
fun log(message: String) {
|
||||
plugin!!.logger.info("[" + className + "] " + message)
|
||||
plugin!!.logger.info("[$className] $message")
|
||||
}
|
||||
|
||||
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) {
|
||||
if (plugin is ButtonPlugin) command.registerToPlugin(plugin as ButtonPlugin)
|
||||
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
|
||||
*/
|
||||
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)
|
||||
if (cs == null) cs = c.createSection(key)
|
||||
val res = cs!!.getValues(false).entries.stream().filter { (_, value): Map.Entry<String?, Any?> -> value is ConfigurationSection }
|
||||
.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?> ->
|
||||
val conf = IHaveConfig { getPlugin().saveConfig() }
|
||||
conf.reset(value as ConfigurationSection?)
|
||||
conf
|
||||
}))
|
||||
if (res.size == 0) {
|
||||
val res = cs!!.getValues(false).entries.stream()
|
||||
.filter { (_, value): Map.Entry<String?, Any?> -> value is ConfigurationSection }
|
||||
.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
|
||||
})
|
||||
)
|
||||
if (res.isEmpty()) {
|
||||
for ((key1, value) in defaultProvider) {
|
||||
val conf = IHaveConfig { getPlugin().saveConfig() }
|
||||
val conf = IHaveConfig { plugin!!.saveConfig() }
|
||||
conf.reset(cs.createSection(key1))
|
||||
value.accept(conf)
|
||||
res[key1] = conf
|
||||
|
@ -128,7 +129,7 @@ abstract class Component<TP : JavaPlugin?> {
|
|||
}
|
||||
|
||||
private val className: String
|
||||
private get() = javaClass.simpleName
|
||||
get() = javaClass.simpleName
|
||||
|
||||
companion object {
|
||||
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)
|
||||
*/
|
||||
@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)
|
||||
}
|
||||
|
||||
|
@ -156,29 +157,37 @@ abstract class Component<TP : JavaPlugin?> {
|
|||
* @return Whether the component is unregistered successfully (it also got disabled)
|
||||
*/
|
||||
@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)
|
||||
}
|
||||
|
||||
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 {
|
||||
val metaAnn = component.javaClass.getAnnotation(ComponentMetadata::class.java)
|
||||
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
|
||||
if (!components.containsKey(dep)) {
|
||||
plugin!!.logger.warning("Failed to " + (if (register) "" else "un") + "register component " + component.className + " as a required dependency is missing/disabled: " + dep.simpleName)
|
||||
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)
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
if (register) {
|
||||
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
|
||||
}
|
||||
component.plugin = plugin
|
||||
component.config.saveAction = Runnable { plugin!!.saveConfig() }
|
||||
component.plugin = plugin // TODO: Perhaps construct a new object with these initialized
|
||||
component.config.saveAction = Runnable { plugin.saveConfig() }
|
||||
updateConfig(plugin, component)
|
||||
component.register(plugin)
|
||||
components[component.javaClass] = component
|
||||
|
@ -188,7 +197,11 @@ abstract class Component<TP : JavaPlugin?> {
|
|||
setComponentEnabled(component, true)
|
||||
true
|
||||
} catch (e: Exception) {
|
||||
TBMCCoreAPI.SendException("Failed to enable component " + component.className + "!", e, component)
|
||||
TBMCCoreAPI.SendException(
|
||||
"Failed to enable component " + component.className + "!",
|
||||
e,
|
||||
component
|
||||
)
|
||||
true
|
||||
} catch (e: NoClassDefFoundError) {
|
||||
TBMCCoreAPI.SendException("Failed to enable component " + component.className + "!", e, component)
|
||||
|
@ -197,14 +210,22 @@ abstract class Component<TP : JavaPlugin?> {
|
|||
}
|
||||
} else {
|
||||
if (!components.containsKey(component.javaClass)) return true //Already unregistered
|
||||
if (component.enabled) {
|
||||
if (component.isEnabled) {
|
||||
try {
|
||||
setComponentEnabled(component, false)
|
||||
} 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
|
||||
} 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
|
||||
}
|
||||
}
|
||||
|
@ -228,10 +249,10 @@ abstract class Component<TP : JavaPlugin?> {
|
|||
@Throws(UnregisteredComponentException::class)
|
||||
fun setComponentEnabled(component: Component<*>, enabled: Boolean) {
|
||||
if (!components.containsKey(component.javaClass)) throw UnregisteredComponentException(component)
|
||||
if (component.enabled == enabled) return //Don't do anything
|
||||
if (enabled.also { component.enabled = it }) {
|
||||
if (component.isEnabled == enabled) return //Don't do anything
|
||||
if (enabled.also { component.isEnabled = it }) {
|
||||
try {
|
||||
updateConfig(component.getPlugin(), component)
|
||||
updateConfig(component.plugin!!, component)
|
||||
component.enable()
|
||||
if (ButtonPlugin.configGenAllowed(component)) {
|
||||
IHaveConfig.pregenConfig(component, null)
|
||||
|
@ -253,7 +274,7 @@ abstract class Component<TP : JavaPlugin?> {
|
|||
}
|
||||
} else {
|
||||
component.disable()
|
||||
ButtonPlugin.getCommand2MC().unregisterCommands(component)
|
||||
ButtonPlugin.command2MC.unregisterCommands(component)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,252 +1,260 @@
|
|||
package buttondevteam.lib.architecture;
|
||||
package buttondevteam.lib.architecture
|
||||
|
||||
import buttondevteam.core.MainPlugin;
|
||||
import buttondevteam.lib.ChromaUtils;
|
||||
import lombok.*;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.configuration.Configuration;
|
||||
import org.bukkit.configuration.file.YamlConfiguration;
|
||||
import org.bukkit.scheduler.BukkitTask;
|
||||
|
||||
import java.lang.reflect.Array;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.function.BiFunction;
|
||||
import java.util.function.Function;
|
||||
import buttondevteam.core.MainPlugin
|
||||
import buttondevteam.lib.ChromaUtils
|
||||
import buttondevteam.lib.architecture.IHaveConfig.getConfig
|
||||
import lombok.*
|
||||
import org.bukkit.Bukkit
|
||||
import org.bukkit.configuration.Configuration
|
||||
import org.bukkit.scheduler.BukkitTask
|
||||
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 {@link Component#getConfig()} or {@link ButtonPlugin#getIConfig()} then {@link IHaveConfig#getData(String, Object)} to get an instance.
|
||||
* Use the getter/setter constructor if [T] isn't a primitive type or String.<br></br>
|
||||
* Use [Component.getConfig] or [ButtonPlugin.getIConfig] then [IHaveConfig.getData] to get an instance.
|
||||
*/
|
||||
public class ConfigData<T> {
|
||||
private static final HashMap<Configuration, SaveTask> saveTasks = new HashMap<>();
|
||||
/**
|
||||
* May be null for testing
|
||||
*/
|
||||
private IHaveConfig config;
|
||||
@Getter
|
||||
@Setter(AccessLevel.PACKAGE)
|
||||
private String path;
|
||||
protected final T def;
|
||||
private final Object primitiveDef;
|
||||
/**
|
||||
* The parameter is of a primitive type as returned by {@link YamlConfiguration#get(String)}
|
||||
*/
|
||||
private final Function<Object, T> getter;
|
||||
/**
|
||||
* The result should be a primitive type or string that can be retrieved correctly later
|
||||
*/
|
||||
private final Function<T, Object> setter;
|
||||
open class ConfigData<T> internal constructor(
|
||||
config: IHaveConfig?,
|
||||
path: String?,
|
||||
def: T,
|
||||
primitiveDef: Any?,
|
||||
getter: Function<Any?, T>?,
|
||||
setter: Function<T, Any?>?
|
||||
) {
|
||||
/**
|
||||
* May be null for testing
|
||||
*/
|
||||
private val config: IHaveConfig?
|
||||
|
||||
/**
|
||||
* The config value should not change outside this instance
|
||||
*/
|
||||
private T value;
|
||||
@Getter
|
||||
@Setter(AccessLevel.PACKAGE)
|
||||
private val path: String?
|
||||
protected val def: T?
|
||||
private val primitiveDef: Any?
|
||||
|
||||
ConfigData(IHaveConfig config, String path, T def, Object primitiveDef, Function<Object, T> getter, Function<T, Object> setter) {
|
||||
if (def == null) {
|
||||
if (primitiveDef == null)
|
||||
throw new IllegalArgumentException("Either def or primitiveDef must be set.");
|
||||
if (getter == null)
|
||||
throw new IllegalArgumentException("A getter and setter must be present when using primitiveDef.");
|
||||
def = getter.apply(primitiveDef);
|
||||
} else if (primitiveDef == null)
|
||||
if (setter == null)
|
||||
primitiveDef = def;
|
||||
else
|
||||
primitiveDef = setter.apply(def);
|
||||
if ((getter == null) != (setter == null))
|
||||
throw new IllegalArgumentException("Both setters and getters must be present (or none if def is primitive).");
|
||||
this.config = config;
|
||||
this.path = path;
|
||||
this.def = def;
|
||||
this.primitiveDef = primitiveDef;
|
||||
this.getter = getter;
|
||||
this.setter = setter;
|
||||
get(); //Generate config automatically
|
||||
}
|
||||
/**
|
||||
* The parameter is of a primitive type as returned by [YamlConfiguration.get]
|
||||
*/
|
||||
private val getter: Function<Any?, T>?
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "ConfigData{" + "path='" + path + '\'' + ", value=" + value + '}';
|
||||
}
|
||||
/**
|
||||
* The result should be a primitive type or string that can be retrieved correctly later
|
||||
*/
|
||||
private val setter: Function<T, Any?>?
|
||||
|
||||
void reset() {
|
||||
value = null;
|
||||
}
|
||||
/**
|
||||
* The config value should not change outside this instance
|
||||
*/
|
||||
private var value: T? = null
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public T get() {
|
||||
if (value != null) return value; //Speed things up
|
||||
var config = this.config.getConfig();
|
||||
Object val;
|
||||
if (config == null || !config.isSet(path)) { //Call set() if config == null
|
||||
val = primitiveDef;
|
||||
if ((def == null || this instanceof 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
|
||||
else
|
||||
set(def); //Save default value - def is always set
|
||||
} else
|
||||
val = config.get(path); //config==null: testing
|
||||
if (val == null) //If it's set to null explicitly
|
||||
val = primitiveDef;
|
||||
BiFunction<Object, Object, Object> convert = (_val, _def) -> {
|
||||
if (_def instanceof Number) //If we expect a number
|
||||
if (_val instanceof Number)
|
||||
_val = ChromaUtils.convertNumber((Number) _val,
|
||||
(Class<? extends Number>) _def.getClass());
|
||||
else
|
||||
_val = _def; //If we didn't get a number, return default (which is a number)
|
||||
else if (_val instanceof List && _def != null && _def.getClass().isArray())
|
||||
_val = ((List<T>) _val).toArray((T[]) Array.newInstance(_def.getClass().getComponentType(), 0));
|
||||
return _val;
|
||||
};
|
||||
if (getter != null) {
|
||||
val = convert.apply(val, primitiveDef);
|
||||
T hmm = getter.apply(val);
|
||||
if (hmm == null) hmm = def; //Set if the getter returned null
|
||||
return hmm;
|
||||
}
|
||||
val = convert.apply(val, def);
|
||||
return value = (T) val; //Always cache, if not cached yet
|
||||
}
|
||||
init {
|
||||
var def: T? = def
|
||||
var primitiveDef = primitiveDef
|
||||
if (def == null) {
|
||||
requireNotNull(primitiveDef) { "Either def or primitiveDef must be set." }
|
||||
requireNotNull(getter) { "A getter and setter must be present when using primitiveDef." }
|
||||
def = getter.apply(primitiveDef)
|
||||
} else if (primitiveDef == null) primitiveDef = if (setter == null) def else setter.apply(def)
|
||||
require(getter == null == (setter == null)) { "Both setters and getters must be present (or none if def is primitive)." }
|
||||
this.config = config
|
||||
this.path = path
|
||||
this.def = def
|
||||
this.primitiveDef = primitiveDef
|
||||
this.getter = getter
|
||||
this.setter = setter
|
||||
get() //Generate config automatically
|
||||
}
|
||||
|
||||
public void set(T value) {
|
||||
if (this instanceof ReadOnlyConfigData)
|
||||
return; //Safety for Discord channel/role data
|
||||
Object val;
|
||||
if (setter != null && value != null)
|
||||
val = setter.apply(value);
|
||||
else val = value;
|
||||
if (config.getConfig() != null)
|
||||
setInternal(val);
|
||||
this.value = value;
|
||||
}
|
||||
override fun toString(): String {
|
||||
return "ConfigData{path='$path', value=$value}"
|
||||
}
|
||||
|
||||
private void setInternal(Object val) {
|
||||
config.getConfig().set(path, val);
|
||||
signalChange(config);
|
||||
}
|
||||
fun reset() {
|
||||
value = null
|
||||
}
|
||||
|
||||
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));
|
||||
}
|
||||
}
|
||||
}
|
||||
fun get(): T? {
|
||||
if (value != null) return value //Speed things up
|
||||
val config = config!!.getConfig<Any>()
|
||||
var `val`: Any?
|
||||
if (config == null || !config.isSet(path)) { //Call set() if config == null
|
||||
`val` = primitiveDef
|
||||
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
|
||||
else set(def) //Save default value - def is always set
|
||||
} else `val` = config.get(path) //config==null: testing
|
||||
if (`val` == null) //If it's set to null explicitly
|
||||
`val` = primitiveDef
|
||||
val convert = BiFunction { _val: Any?, _def: Any? ->
|
||||
if (_def is Number) //If we expect a number
|
||||
_val = if (_val is Number) ChromaUtils.convertNumber(
|
||||
_val as Number?,
|
||||
_def.javaClass as Class<out Number?>
|
||||
) else _def //If we didn't get a number, return default (which is a number)
|
||||
else if (_val is List<*> && _def != null && _def.javaClass.isArray) _val = (_val as List<T>).toArray<T>(
|
||||
java.lang.reflect.Array.newInstance(
|
||||
_def.javaClass.componentType,
|
||||
0
|
||||
) as Array<T>
|
||||
)
|
||||
_val
|
||||
}
|
||||
if (getter != null) {
|
||||
`val` = convert.apply(`val`, primitiveDef)
|
||||
var hmm: T? = getter.apply(`val`)
|
||||
if (hmm == null) hmm = def //Set if the getter returned null
|
||||
return hmm
|
||||
}
|
||||
`val` = convert.apply(`val`, def)
|
||||
return `val` as T?. also {
|
||||
value = it //Always cache, if not cached yet
|
||||
}
|
||||
}
|
||||
|
||||
@AllArgsConstructor
|
||||
private static class SaveTask {
|
||||
BukkitTask task;
|
||||
Runnable saveAction;
|
||||
}
|
||||
fun set(value: T?) {
|
||||
if (this is ReadOnlyConfigData<*>) return //Safety for Discord channel/role data
|
||||
val `val`: Any?
|
||||
`val` = if (setter != null && value != null) setter.apply(value) else value
|
||||
if (config!!.getConfig<Any>() != null) setInternal(`val`)
|
||||
this.value = value
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
private fun setInternal(`val`: Any?) {
|
||||
config!!.getConfig<Any>().set(path, `val`)
|
||||
signalChange(config)
|
||||
}
|
||||
|
||||
public static <T> ConfigData.ConfigDataBuilder<T> builder(IHaveConfig config, String path) {
|
||||
return new ConfigDataBuilder<T>(config, path);
|
||||
}
|
||||
@AllArgsConstructor
|
||||
private class SaveTask {
|
||||
var task: BukkitTask? = null
|
||||
var saveAction: Runnable? = null
|
||||
}
|
||||
|
||||
@RequiredArgsConstructor(access = AccessLevel.PACKAGE)
|
||||
public static class ConfigDataBuilder<T> {
|
||||
private final IHaveConfig config;
|
||||
private final String path;
|
||||
private T def;
|
||||
private Object primitiveDef;
|
||||
private Function<Object, T> getter;
|
||||
private Function<T, Object> setter;
|
||||
@RequiredArgsConstructor(access = AccessLevel.PACKAGE)
|
||||
class ConfigDataBuilder<T> {
|
||||
private val config: IHaveConfig? = null
|
||||
private val path: String? = null
|
||||
private var def: T? = null
|
||||
private var primitiveDef: Any? = null
|
||||
private var getter: Function<Any?, T?>? = null
|
||||
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.
|
||||
* <br/>
|
||||
* To set the value as it is stored, use {@link #primitiveDef(Object)}.
|
||||
*
|
||||
* @param def The default value
|
||||
* @return This builder
|
||||
*/
|
||||
public ConfigDataBuilder<T> def(T def) {
|
||||
this.def = def;
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* The default value to use, as used in code. If not a primitive type, use the [.getter] and [.setter] methods.
|
||||
* <br></br>
|
||||
* To set the value as it is stored, use [.primitiveDef].
|
||||
*
|
||||
* @param def The default value
|
||||
* @return This builder
|
||||
*/
|
||||
fun def(def: T): ConfigDataBuilder<T> {
|
||||
this.def = def
|
||||
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.
|
||||
* <br/>
|
||||
* To set the value as used in the code, use {@link #def(Object)}.
|
||||
*
|
||||
* @param primitiveDef The default value
|
||||
* @return This builder
|
||||
*/
|
||||
public ConfigDataBuilder<T> primitiveDef(Object primitiveDef) {
|
||||
this.primitiveDef = primitiveDef;
|
||||
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.
|
||||
* <br></br>
|
||||
* To set the value as used in the code, use [.def].
|
||||
*
|
||||
* @param primitiveDef The default value
|
||||
* @return This builder
|
||||
*/
|
||||
fun primitiveDef(primitiveDef: Any?): ConfigDataBuilder<T> {
|
||||
this.primitiveDef = primitiveDef
|
||||
return this
|
||||
}
|
||||
|
||||
/**
|
||||
* A function to use to obtain the runtime object from the yaml representation (usually string).
|
||||
* The {@link #setter(Function)} must also be set.
|
||||
*
|
||||
* @param getter A function that receives the primitive type and returns the runtime type
|
||||
* @return This builder
|
||||
*/
|
||||
public ConfigDataBuilder<T> getter(Function<Object, T> getter) {
|
||||
this.getter = getter;
|
||||
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<Any?, T>?): ConfigDataBuilder<T> {
|
||||
this.getter = getter
|
||||
return this
|
||||
}
|
||||
|
||||
/**
|
||||
* A function to use to obtain the yaml representation (usually string) from the runtime object.
|
||||
* The {@link #getter(Function)} must also be set.
|
||||
*
|
||||
* @param setter A function that receives the runtime type and returns the primitive type
|
||||
* @return This builder
|
||||
*/
|
||||
public ConfigDataBuilder<T> setter(Function<T, Object> setter) {
|
||||
this.setter = setter;
|
||||
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<T, Any?>?): ConfigDataBuilder<T> {
|
||||
this.setter = setter
|
||||
return this
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds a modifiable config representation. Use if you want to change the value <i>in code</i>.
|
||||
*
|
||||
* @return A ConfigData instance.
|
||||
*/
|
||||
public ConfigData<T> build() {
|
||||
ConfigData<T> config = new ConfigData<>(this.config, path, def, primitiveDef, getter, setter);
|
||||
this.config.onConfigBuild(config);
|
||||
return config;
|
||||
}
|
||||
/**
|
||||
* Builds a modifiable config representation. Use if you want to change the value *in code*.
|
||||
*
|
||||
* @return A ConfigData instance.
|
||||
*/
|
||||
fun build(): ConfigData<T?> {
|
||||
val config = ConfigData(config, path, def, primitiveDef, getter, setter)
|
||||
this.config!!.onConfigBuild(config)
|
||||
return config
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds a read-only config representation. Use if you only want the value to be changed <i>in the config</i>.
|
||||
*
|
||||
* @return A ReadOnlyConfigData instance.
|
||||
*/
|
||||
public ReadOnlyConfigData<T> buildReadOnly() {
|
||||
ReadOnlyConfigData<T> config = new ReadOnlyConfigData<>(this.config, path, def, primitiveDef, getter, setter);
|
||||
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<T?> {
|
||||
val config = ReadOnlyConfigData(config, path, def, primitiveDef, getter, setter)
|
||||
this.config!!.onConfigBuild(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)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,256 +1,286 @@
|
|||
package buttondevteam.lib.architecture;
|
||||
package buttondevteam.lib.architecture
|
||||
|
||||
import buttondevteam.core.MainPlugin;
|
||||
import buttondevteam.lib.TBMCCoreAPI;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import lombok.val;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.configuration.ConfigurationSection;
|
||||
import org.bukkit.plugin.java.JavaPlugin;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.util.*;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.stream.Collectors;
|
||||
import buttondevteam.core.MainPlugin
|
||||
import buttondevteam.lib.TBMCCoreAPI
|
||||
import buttondevteam.lib.architecture.ConfigData.ConfigDataBuilder
|
||||
import lombok.Getter
|
||||
import org.bukkit.Bukkit
|
||||
import org.bukkit.configuration.ConfigurationSection
|
||||
import org.bukkit.plugin.java.JavaPlugin
|
||||
import java.lang.reflect.InvocationTargetException
|
||||
import java.util.*
|
||||
import java.util.function.Function
|
||||
import java.util.function.Predicate
|
||||
import java.util.function.Supplier
|
||||
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.
|
||||
*/
|
||||
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;
|
||||
class IHaveConfig(var saveAction: Runnable?) { // TODO: Make non-nullable after adding component builder
|
||||
private val datamap = HashMap<String, ConfigData<*>>()
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
public IHaveConfig(Runnable saveAction) {
|
||||
this.saveAction = saveAction;
|
||||
}
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @param path The dot-separated path relative to this config instance
|
||||
* @param <T> The runtime type of the config value
|
||||
* @return A ConfigData builder to set how to obtain the value
|
||||
*/
|
||||
public <T> ConfigData.ConfigDataBuilder<T> getConfig(String path) {
|
||||
return ConfigData.builder(this, path);
|
||||
}
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @param path The dot-separated path relative to this config instance
|
||||
* @param <T> The runtime type of the config value
|
||||
* @return A ConfigData builder to set how to obtain the value
|
||||
</T> */
|
||||
fun <T> getConfig(path: String?): ConfigDataBuilder<T> {
|
||||
return ConfigData.builder(this, path)
|
||||
}
|
||||
|
||||
void onConfigBuild(ConfigData<?> config) {
|
||||
datamap.put(config.getPath(), config);
|
||||
}
|
||||
fun onConfigBuild(config: ConfigData<*>) {
|
||||
datamap[config.path] = config
|
||||
}
|
||||
|
||||
/**
|
||||
* This method overload should only be used with primitives or String.
|
||||
*
|
||||
* @param path The path in config to use
|
||||
* @param def The value to use by default
|
||||
* @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
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T> ConfigData<T> getData(String path, T def) {
|
||||
ConfigData<?> data = datamap.get(path);
|
||||
if (data == null) datamap.put(path, data = new ConfigData<>(this, path, def, def, null, null));
|
||||
return (ConfigData<T>) data;
|
||||
}
|
||||
/**
|
||||
* This method overload should only be used with primitives or String.
|
||||
*
|
||||
* @param path The path in config to use
|
||||
* @param def The value to use by default
|
||||
* @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
|
||||
</T> */
|
||||
fun <T> getData(path: String, def: T): ConfigData<T> {
|
||||
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<T>
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 <T> The type of this variable (can be any class)
|
||||
* @return The data object that can be used to get or set the value
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T> ConfigData<T> getData(String path, T def, Function<Object, T> getter, Function<T, Object> setter) {
|
||||
ConfigData<?> data = datamap.get(path);
|
||||
if (data == null)
|
||||
datamap.put(path, data = new ConfigData<>(this, path, def, setter.apply(def), getter, setter));
|
||||
return (ConfigData<T>) data;
|
||||
}
|
||||
/**
|
||||
* 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 <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> */
|
||||
fun <T> getData(path: String, def: T, getter: Function<Any?, T>?, setter: Function<T, Any?>): ConfigData<T> {
|
||||
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<T>
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 <b>primitive</b> 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 <T> The type of this variable (can be any class)
|
||||
* @return The data object that can be used to get or set the value
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T> ConfigData<T> getDataPrimDef(String path, Object primitiveDef, Function<Object, T> getter, Function<T, Object> setter) {
|
||||
ConfigData<?> data = datamap.get(path);
|
||||
if (data == null)
|
||||
datamap.put(path, data = new ConfigData<>(this, path, getter.apply(primitiveDef), primitiveDef, getter, setter));
|
||||
return (ConfigData<T>) data;
|
||||
}
|
||||
/**
|
||||
* 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 <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> */
|
||||
fun <T> getDataPrimDef(
|
||||
path: String,
|
||||
primitiveDef: Any?,
|
||||
getter: Function<Any?, T>,
|
||||
setter: Function<T, Any?>?
|
||||
): 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.
|
||||
*
|
||||
* @param path The path in config to use
|
||||
* @param primitiveDef The <b>primitive</b> 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 <T> The type of this variable (can be any class)
|
||||
* @return The data object that can be used to get or set the value
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T> ReadOnlyConfigData<T> getReadOnlyDataPrimDef(String path, Object primitiveDef, Function<Object, T> getter, Function<T, Object> setter) {
|
||||
ConfigData<?> data = datamap.get(path);
|
||||
if (data == null)
|
||||
datamap.put(path, data = new ReadOnlyConfigData<>(this, path, getter.apply(primitiveDef), primitiveDef, getter, setter));
|
||||
return (ReadOnlyConfigData<T>) data;
|
||||
}
|
||||
/**
|
||||
* 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 <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> */
|
||||
fun <T> getReadOnlyDataPrimDef(
|
||||
path: String,
|
||||
primitiveDef: Any?,
|
||||
getter: Function<Any?, T>,
|
||||
setter: Function<T, Any?>?
|
||||
): 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>
|
||||
}
|
||||
|
||||
/**
|
||||
* This method overload should only be used with primitves or String.
|
||||
*
|
||||
* @param path The path in config to use
|
||||
* @param def The value to use by default
|
||||
* @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
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T> ConfigData<T> getData(String path, Supplier<T> def) {
|
||||
ConfigData<?> data = datamap.get(path);
|
||||
if (data == null) {
|
||||
val defval = def.get();
|
||||
datamap.put(path, data = new ConfigData<>(this, path, defval, defval, null, null));
|
||||
}
|
||||
return (ConfigData<T>) data;
|
||||
}
|
||||
/**
|
||||
* This method overload should only be used with primitves or String.
|
||||
*
|
||||
* @param path The path in config to use
|
||||
* @param def The value to use by default
|
||||
* @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
|
||||
</T> */
|
||||
fun <T> getData(path: String, def: Supplier<T>): ConfigData<T> {
|
||||
var data = datamap[path]
|
||||
if (data == null) {
|
||||
val defval = def.get()
|
||||
datamap[path] = ConfigData(this, path, defval, defval, null, null).also { data = it }
|
||||
}
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
return data as ConfigData<T>
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 <T> The type of this variable (can be any class)
|
||||
* @return The data object that can be used to get or set the value
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T> ConfigData<T> getData(String path, Supplier<T> def, Function<Object, T> getter, Function<T, Object> setter) {
|
||||
ConfigData<?> data = datamap.get(path);
|
||||
if (data == null) {
|
||||
val defval = def.get();
|
||||
datamap.put(path, data = new ConfigData<>(this, path, defval, setter.apply(defval), getter, setter));
|
||||
}
|
||||
return (ConfigData<T>) data;
|
||||
}
|
||||
/**
|
||||
* 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 <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> */
|
||||
fun <T> getData(
|
||||
path: String,
|
||||
def: Supplier<T>,
|
||||
getter: Function<Any?, T>?,
|
||||
setter: Function<T, Any?>
|
||||
): ConfigData<T> {
|
||||
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<T>
|
||||
}
|
||||
|
||||
/**
|
||||
* This method overload should only be used with primitves or String.
|
||||
*
|
||||
* @param path The path in config to use
|
||||
* @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
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T> ListConfigData<T> getListData(String path) {
|
||||
ConfigData<?> data = datamap.get(path);
|
||||
if (data == null)
|
||||
datamap.put(path, data = new ListConfigData<>(this, path, new ListConfigData.List<T>()));
|
||||
return (ListConfigData<T>) data;
|
||||
}
|
||||
/**
|
||||
* This method overload should only be used with primitves or String.
|
||||
*
|
||||
* @param path The path in config to use
|
||||
* @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
|
||||
</T> */
|
||||
fun <T> getListData(path: String): ListConfigData<T> {
|
||||
var data = datamap[path]
|
||||
if (data == null) datamap[path] = ListConfigData(this, path, ListConfigData.List<T>()).also { data = it }
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
return data as ListConfigData<T>
|
||||
}
|
||||
|
||||
/**
|
||||
* Schedules a save operation. Use after changing the ConfigurationSection directly.
|
||||
*/
|
||||
public void signalChange() {
|
||||
ConfigData.signalChange(this);
|
||||
}
|
||||
/**
|
||||
* Schedules a save operation. Use after changing the ConfigurationSection directly.
|
||||
*/
|
||||
fun signalChange() {
|
||||
ConfigData.signalChange(this)
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears all caches and loads everything from yaml.
|
||||
*/
|
||||
public void reset(ConfigurationSection config) {
|
||||
this.config = config;
|
||||
datamap.forEach((path, data) -> data.reset());
|
||||
}
|
||||
/**
|
||||
* Clears all caches and loads everything from yaml.
|
||||
*/
|
||||
fun reset(config: ConfigurationSection?) {
|
||||
this.config = config
|
||||
datamap.forEach { (path: String?, data: ConfigData<*>) -> data.reset() }
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates the config YAML.
|
||||
*
|
||||
* @param obj The object which has config methods
|
||||
* @param configMap The result from {@link Component#getConfigMap(String, Map)}. May be null.
|
||||
*/
|
||||
public static void pregenConfig(Object obj, @Nullable Map<String, IHaveConfig> configMap) {
|
||||
val ms = obj.getClass().getDeclaredMethods();
|
||||
for (val m : ms) {
|
||||
if (!m.getReturnType().getName().equals(ConfigData.class.getName())) continue;
|
||||
final String mName;
|
||||
{
|
||||
var name = m.getName();
|
||||
var ind = name.lastIndexOf('$');
|
||||
if (ind == -1) mName = name;
|
||||
else mName = name.substring(ind + 1);
|
||||
}
|
||||
try {
|
||||
m.setAccessible(true);
|
||||
List<ConfigData<?>> configList;
|
||||
if (m.getParameterCount() == 0) {
|
||||
configList = Collections.singletonList((ConfigData<?>) m.invoke(obj));
|
||||
} else if (m.getParameterCount() == 1 && m.getParameterTypes()[0] == IHaveConfig.class) {
|
||||
if (configMap == null) continue; //Hope it will get called with the param later
|
||||
configList = configMap.entrySet().stream().map(kv ->
|
||||
{
|
||||
try {
|
||||
return (ConfigData<?>) m.invoke(obj, kv.getValue());
|
||||
} catch (IllegalAccessException | InvocationTargetException e) {
|
||||
String msg = "Failed to pregenerate " + mName + " for " + obj + " using config " + kv.getKey() + "!";
|
||||
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);
|
||||
return null;
|
||||
}
|
||||
}).filter(Objects::nonNull).collect(Collectors.toList());
|
||||
} else {
|
||||
if (TBMCCoreAPI.IsTestServer())
|
||||
MainPlugin.Instance.getLogger().warning("Method " + mName + " returns a config but its parameters are unknown: " + Arrays.toString(m.getParameterTypes()));
|
||||
continue;
|
||||
}
|
||||
for (val c : configList) {
|
||||
if (c.getPath().length() == 0)
|
||||
c.setPath(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
|
||||
}
|
||||
} 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
companion object {
|
||||
/**
|
||||
* Generates the config YAML.
|
||||
*
|
||||
* @param obj The object which has config methods
|
||||
* @param configMap The result from [Component.getConfigMap]. May be null.
|
||||
*/
|
||||
fun pregenConfig(obj: Any, configMap: Map<String, IHaveConfig?>?) {
|
||||
val ms = obj.javaClass.declaredMethods
|
||||
for (m in ms) {
|
||||
if (m.returnType.name != ConfigData::class.java.name) continue
|
||||
val mName: String
|
||||
run {
|
||||
val name = m.name
|
||||
val ind = name.lastIndexOf('$')
|
||||
mName = if (ind == -1) name else name.substring(ind + 1)
|
||||
}
|
||||
try {
|
||||
m.isAccessible = true
|
||||
var configList: List<ConfigData<*>>
|
||||
configList = if (m.parameterCount == 0) {
|
||||
listOf(m.invoke(obj) as ConfigData<*>)
|
||||
} else if (m.parameterCount == 1 && m.parameterTypes[0] == IHaveConfig::class.java) {
|
||||
if (configMap == null) continue //Hope it will get called with the param later
|
||||
configMap.entries.stream().map { (key, value): Map.Entry<String, IHaveConfig?> ->
|
||||
try {
|
||||
return@map m.invoke(obj, value) as ConfigData<*>
|
||||
} catch (e: IllegalAccessException) {
|
||||
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
|
||||
} 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 {
|
||||
if (TBMCCoreAPI.IsTestServer()) MainPlugin.Instance.logger.warning(
|
||||
"Method " + mName + " returns a config but its parameters are unknown: " + Arrays.toString(
|
||||
m.parameterTypes
|
||||
)
|
||||
)
|
||||
continue
|
||||
}
|
||||
for (c in configList) {
|
||||
if (c.path.length == 0) c.setPath(mName) else if (c.path != mName) MainPlugin.Instance.logger.warning(
|
||||
"Config name does not match: " + c.path + " instead of " + mName
|
||||
)
|
||||
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) }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue