refactor: Abstract handle class and improve node handling

This commit is contained in:
Ben Woo 2023-03-28 16:56:30 +08:00
parent 9bbf42f993
commit 90a3388728
No known key found for this signature in database
GPG Key ID: FB2A3645536E12C8
25 changed files with 644 additions and 623 deletions

View File

@ -2,6 +2,7 @@ package com.onarandombox.MultiverseCore.api;
import com.onarandombox.MultiverseCore.configuration.node.NodeGroup;
import com.onarandombox.MultiverseCore.placeholders.MultiverseCorePlaceholders;
import io.vavr.control.Try;
import org.jvnet.hk2.annotations.Contract;
@Contract
@ -37,7 +38,7 @@ public interface MVConfig {
* @param name The name of the property.
* @return The value of the property.
*/
Object getProperty(String name);
Try<Object> getProperty(String name);
/**
* Sets a property in the config.
@ -46,7 +47,7 @@ public interface MVConfig {
* @param value The value of the property.
* @return True if the property was set successfully.
*/
boolean setProperty(String name, Object value);
Try<Boolean> setProperty(String name, Object value);
/**
* Sets world access permissions should be enforced.

View File

@ -14,6 +14,7 @@ import com.onarandombox.MultiverseCore.commandtools.MVCommandManager;
import com.onarandombox.MultiverseCore.commandtools.MultiverseCommand;
import com.onarandombox.MultiverseCore.commandtools.context.MVConfigValue;
import com.onarandombox.MultiverseCore.config.MVCoreConfig;
import io.vavr.control.Try;
import jakarta.inject.Inject;
import org.jetbrains.annotations.NotNull;
import org.jvnet.hk2.annotations.Service;
@ -55,20 +56,21 @@ public class ConfigCommand extends MultiverseCommand {
}
private void showConfigValue(BukkitCommandIssuer issuer, String name) {
Object currentValue = config.getProperty(name);
if (currentValue == null) {
issuer.sendMessage("No such config option: " + name);
return;
}
issuer.sendMessage(name + "is currently set to " + config.getProperty(name));
config.getProperty(name)
.onSuccess(value -> issuer.sendMessage(name + "is currently set to " + value))
.onFailure(throwable -> issuer.sendMessage("Unable to get " + name + ": " + throwable.getMessage()));
}
private void updateConfigValue(BukkitCommandIssuer issuer, String name, Object value) {
if (!config.setProperty(name, value)) {
issuer.sendMessage("Unable to set " + name + " to " + value);
return;
}
config.save();
issuer.sendMessage("Successfully set " + name + " to " + value);
config.setProperty(name, value)
.onSuccess(success -> {
if (success) {
config.save();
issuer.sendMessage("Successfully set " + name + " to " + value);
} else {
issuer.sendMessage("Unable to set " + name + " to " + value);
}
})
.onFailure(throwable -> issuer.sendMessage("Unable to set " + name + " to " + value + ": " + throwable.getMessage()));
}
}

View File

@ -15,14 +15,14 @@ import com.onarandombox.MultiverseCore.api.MVWorldManager;
import com.onarandombox.MultiverseCore.commandtools.context.GameRuleValue;
import com.onarandombox.MultiverseCore.commandtools.context.MVConfigValue;
import com.onarandombox.MultiverseCore.config.MVCoreConfig;
import com.onarandombox.MultiverseCore.configuration.node.Node;
import com.onarandombox.MultiverseCore.configuration.node.ValueNode;
import com.onarandombox.MultiverseCore.destination.DestinationsProvider;
import com.onarandombox.MultiverseCore.destination.ParsedDestination;
import com.onarandombox.MultiverseCore.display.filters.ContentFilter;
import com.onarandombox.MultiverseCore.display.filters.DefaultContentFilter;
import com.onarandombox.MultiverseCore.display.filters.RegexContentFilter;
import com.onarandombox.MultiverseCore.utils.PlayerFinder;
import io.github.townyadvanced.commentedconfiguration.setting.CommentedNode;
import io.github.townyadvanced.commentedconfiguration.setting.TypedValueNode;
import jakarta.inject.Inject;
import org.bukkit.GameRule;
import org.bukkit.entity.Player;
@ -123,7 +123,7 @@ public class MVCommandContexts extends PaperCommandContexts {
if (Strings.isNullOrEmpty(configName)) {
throw new InvalidCommandArgument("No config name specified.");
}
Optional<CommentedNode> node = config.getNodes().findNode(configName);
Optional<Node> node = config.getNodes().findNode(configName);
if (node.isEmpty()) {
throw new InvalidCommandArgument("The config " + configName + " is not valid.");
}
@ -133,12 +133,12 @@ public class MVCommandContexts extends PaperCommandContexts {
throw new InvalidCommandArgument("No config value specified.");
}
if (!(node.get() instanceof TypedValueNode)) {
if (!(node.get() instanceof ValueNode)) {
context.popFirstArg();
return new MVConfigValue(valueString);
}
ContextResolver<?, BukkitCommandExecutionContext> resolver = getResolver(((TypedValueNode<?>) node.get()).getType());
ContextResolver<?, BukkitCommandExecutionContext> resolver = getResolver(((ValueNode<?>) node.get()).getType());
if (resolver == null) {
context.popFirstArg();
return new MVConfigValue(valueString);

View File

@ -7,7 +7,7 @@ import java.nio.file.Path;
import com.dumptruckman.minecraft.util.Logging;
import com.onarandombox.MultiverseCore.MultiverseCore;
import com.onarandombox.MultiverseCore.api.MVConfig;
import com.onarandombox.MultiverseCore.configuration.ConfigHandle;
import com.onarandombox.MultiverseCore.configuration.handle.CommentedYamlConfigHandle;
import com.onarandombox.MultiverseCore.configuration.migration.BooleanMigratorAction;
import com.onarandombox.MultiverseCore.configuration.migration.ConfigMigrator;
import com.onarandombox.MultiverseCore.configuration.migration.IntegerMigratorAction;
@ -15,6 +15,7 @@ import com.onarandombox.MultiverseCore.configuration.migration.InvertBoolMigrato
import com.onarandombox.MultiverseCore.configuration.migration.MoveMigratorAction;
import com.onarandombox.MultiverseCore.configuration.migration.VersionMigrator;
import com.onarandombox.MultiverseCore.configuration.node.NodeGroup;
import io.vavr.control.Try;
import jakarta.inject.Inject;
import org.bukkit.plugin.PluginManager;
import org.jetbrains.annotations.NotNull;
@ -27,15 +28,14 @@ public class MVCoreConfig implements MVConfig {
private final Path configPath;
private final MVCoreConfigNodes configNodes;
private final ConfigHandle configHandle;
private final CommentedYamlConfigHandle configHandle;
@Inject
MVCoreConfig(@NotNull MultiverseCore core, @NotNull PluginManager pluginManager) {
this.configPath = Path.of(core.getDataFolder().getPath(), CONFIG_FILENAME);
this.configNodes = new MVCoreConfigNodes(pluginManager);
this.configHandle = ConfigHandle.builder(configPath)
this.configHandle = CommentedYamlConfigHandle.builder(configPath, configNodes.getNodes())
.logger(Logging.getLogger())
.nodes(configNodes.getNodes())
.migrator(ConfigMigrator.builder(configNodes.VERSION)
.addVersionMigrator(VersionMigrator.builder(5.0)
.addAction(MoveMigratorAction.of("multiverse-configuration.enforceaccess", "world.enforce-access"))
@ -110,12 +110,12 @@ public class MVCoreConfig implements MVConfig {
}
@Override
public Object getProperty(String name) {
public Try<Object> getProperty(String name) {
return configHandle.get(name);
}
@Override
public boolean setProperty(String name, Object value) {
public Try<Boolean> setProperty(String name, Object value) {
return configHandle.set(name, value);
}

View File

@ -1,14 +1,13 @@
package com.onarandombox.MultiverseCore.config;
import com.dumptruckman.minecraft.util.Logging;
import com.onarandombox.MultiverseCore.configuration.node.MVCommentedNode;
import com.onarandombox.MultiverseCore.configuration.node.MVValueNode;
import com.onarandombox.MultiverseCore.configuration.node.ConfigHeaderNode;
import com.onarandombox.MultiverseCore.configuration.node.ConfigNode;
import com.onarandombox.MultiverseCore.configuration.node.Node;
import com.onarandombox.MultiverseCore.configuration.node.NodeGroup;
import com.onarandombox.MultiverseCore.event.MVDebugModeEvent;
import io.github.townyadvanced.commentedconfiguration.setting.CommentedNode;
import jakarta.inject.Inject;
import org.bukkit.plugin.PluginManager;
import org.jvnet.hk2.annotations.Service;
class MVCoreConfigNodes {
@ -23,12 +22,12 @@ class MVCoreConfigNodes {
return nodes;
}
private <N extends CommentedNode> N node(N node) {
private <N extends Node> N node(N node) {
nodes.add(node);
return node;
}
private final MVCommentedNode HEADER = node(MVCommentedNode.builder("world") // TODO hacky way to get the header to the top of the file
private final ConfigHeaderNode HEADER = node(ConfigHeaderNode.builder("world") // TODO hacky way to get the header to the top of the file
.comment("####################################################################################################")
.comment("# #")
.comment("# █▀▄▀█ █░█ █░░ ▀█▀ █ █░█ █▀▀ █▀█ █▀ █▀▀   █▀▀ █▀█ █▀█ █▀▀ #")
@ -52,12 +51,12 @@ class MVCoreConfigNodes {
.comment("")
.build());
// private final MVCommentedNode WORLD_HEADER = node(MVCommentedNode.builder("world")
// private final ConfigHeaderNode WORLD_HEADER = node(ConfigHeaderNode.builder("world")
// .comment("")
// .comment("")
// .build());
public final MVValueNode<Boolean> ENFORCE_ACCESS = node(MVValueNode.builder("world.enforce-access", Boolean.class)
public final ConfigNode<Boolean> ENFORCE_ACCESS = node(ConfigNode.builder("world.enforce-access", Boolean.class)
.comment("This setting will prevent players from entering worlds they don't have access to.")
.comment("If this is set to false, players will be able to enter any world they want.")
.comment("If this is set to true, players will only be able to enter worlds they have")
@ -66,7 +65,7 @@ class MVCoreConfigNodes {
.name("enforce-access")
.build());
public final MVValueNode<Boolean> ENFORCE_GAMEMODE = node(MVValueNode.builder("world.enforce-gamemode", Boolean.class)
public final ConfigNode<Boolean> ENFORCE_GAMEMODE = node(ConfigNode.builder("world.enforce-gamemode", Boolean.class)
.comment("")
.comment("Sets whether Multiverse will should enforce gamemode on world change.")
.comment("If enabled, players will be forced into the gamemode of the world they are entering, unless they have")
@ -75,14 +74,14 @@ class MVCoreConfigNodes {
.name("enforce-gamemode")
.build());
public final MVValueNode<Boolean> AUTO_PURGE_ENTITIES = node(MVValueNode.builder("world.auto-purge-entities", Boolean.class)
public final ConfigNode<Boolean> AUTO_PURGE_ENTITIES = node(ConfigNode.builder("world.auto-purge-entities", Boolean.class)
.comment("")
.comment("Sets whether Multiverse will purge mobs and entities with be automatically.")
.defaultValue(false)
.name("auto-purge-entities")
.build());
public final MVValueNode<Boolean> TELEPORT_INTERCEPT = node(MVValueNode.builder("world.teleport-intercept", Boolean.class)
public final ConfigNode<Boolean> TELEPORT_INTERCEPT = node(ConfigNode.builder("world.teleport-intercept", Boolean.class)
.comment("")
.comment("If this is set to true, Multiverse will enforce access permissions for all teleportation,")
.comment("including teleportation from other plugins.")
@ -90,12 +89,12 @@ class MVCoreConfigNodes {
.name("teleport-intercept")
.build());
private final MVCommentedNode SPAWN_HEADER = node(MVCommentedNode.builder("spawn")
private final ConfigHeaderNode SPAWN_HEADER = node(ConfigHeaderNode.builder("spawn")
.comment("")
.comment("")
.build());
public final MVValueNode<Boolean> FIRST_SPAWN_OVERRIDE = node(MVValueNode.builder("spawn.first-spawn-override", Boolean.class)
public final ConfigNode<Boolean> FIRST_SPAWN_OVERRIDE = node(ConfigNode.builder("spawn.first-spawn-override", Boolean.class)
.comment("Sets whether Multiverse will override the first spawn location of a world.")
.comment("If enabled, Multiverse will set the first spawn location of a world to the spawn location of the world.")
.comment("If disabled, it will default to server.properties settings.")
@ -103,7 +102,7 @@ class MVCoreConfigNodes {
.name("first-spawn-override")
.build());
public final MVValueNode<String> FIRST_SPAWN_LOCATION = node(MVValueNode.builder("spawn.first-spawn-location", String.class)
public final ConfigNode<String> FIRST_SPAWN_LOCATION = node(ConfigNode.builder("spawn.first-spawn-location", String.class)
.comment("")
.comment("Sets the world that Multiverse will use as the location for players that first join the server.")
.comment("This only applies if first-spawn-override is set to true.")
@ -111,19 +110,19 @@ class MVCoreConfigNodes {
.name("first-spawn-location")
.build());
private final MVCommentedNode PORTAL_HEADER = node(MVCommentedNode.builder("portal")
private final ConfigHeaderNode PORTAL_HEADER = node(ConfigHeaderNode.builder("portal")
.comment("")
.comment("")
.build());
public final MVValueNode<Boolean> USE_CUSTOM_PORTAL_SEARCH = node(MVValueNode.builder("portal.use-custom-portal-search", Boolean.class)
public final ConfigNode<Boolean> USE_CUSTOM_PORTAL_SEARCH = node(ConfigNode.builder("portal.use-custom-portal-search", Boolean.class)
.comment("This config option defines whether or not Multiverse should interfere with's Bukkit's default portal search radius.")
.comment("Setting it to false would mean you want to simply let Bukkit decides the search radius itself.")
.defaultValue(false)
.name("use-custom-portal-search")
.build());
public final MVValueNode<Integer> CUSTOM_PORTAL_SEARCH_RADIUS = node(MVValueNode.builder("portal.custom-portal-search-radius", Integer.class)
public final ConfigNode<Integer> CUSTOM_PORTAL_SEARCH_RADIUS = node(ConfigNode.builder("portal.custom-portal-search-radius", Integer.class)
.comment("")
.comment("This config option defines the search radius Multiverse should use when searching for a portal.")
.comment("This only applies if use-custom-portal-search is set to true.")
@ -132,19 +131,19 @@ class MVCoreConfigNodes {
.validator(value -> value >= 0)
.build());
private final MVCommentedNode MESSAGING_HEADER = node(MVCommentedNode.builder("messaging")
private final ConfigHeaderNode MESSAGING_HEADER = node(ConfigHeaderNode.builder("messaging")
.comment("")
.comment("")
.build());
public final MVValueNode<Boolean> ENABLE_CHAT_PREFIX = node(MVValueNode.builder("messaging.enable-chat-prefix", Boolean.class)
public final ConfigNode<Boolean> ENABLE_CHAT_PREFIX = node(ConfigNode.builder("messaging.enable-chat-prefix", Boolean.class)
.comment("This config option defines whether or not Multiverse should prefix the chat with the world name.")
.comment("This only applies if use-custom-portal-search is set to true.")
.defaultValue(false)
.name("enable-chat-prefix")
.build());
public final MVValueNode<String> CHAT_PREFIX_FORMAT = node(MVValueNode.builder("messaging.chat-prefix-format", String.class)
public final ConfigNode<String> CHAT_PREFIX_FORMAT = node(ConfigNode.builder("messaging.chat-prefix-format", String.class)
.comment("")
.comment("This config option defines the format Multiverse should use when prefixing the chat with the world name.")
.comment("This only applies if enable-chat-prefix is set to true.")
@ -152,7 +151,7 @@ class MVCoreConfigNodes {
.name("chat-prefix-format")
.build());
public final MVValueNode<Boolean> REGISTER_PAPI_HOOK = node(MVValueNode.builder("messaging.register-papi-hook", Boolean.class)
public final ConfigNode<Boolean> REGISTER_PAPI_HOOK = node(ConfigNode.builder("messaging.register-papi-hook", Boolean.class)
.comment("")
.comment("This config option defines whether or not Multiverse should register the PlaceholderAPI hook.")
.comment("This only applies if PlaceholderAPI is installed.")
@ -160,12 +159,12 @@ class MVCoreConfigNodes {
.name("register-papi-hook")
.build());
private final MVCommentedNode MISC_HEADER = node(MVCommentedNode.builder("misc")
private final ConfigHeaderNode MISC_HEADER = node(ConfigHeaderNode.builder("misc")
.comment("")
.comment("")
.build());
public final MVValueNode<Integer> GLOBAL_DEBUG = node(MVValueNode.builder("misc.global-debug", Integer.class)
public final ConfigNode<Integer> GLOBAL_DEBUG = node(ConfigNode.builder("misc.global-debug", Integer.class)
.comment("This is our debug flag to help identify issues with Multiverse.")
.comment("If you are having issues with Multiverse, please set this to 3 and then post your log to pastebin.com")
.comment("Otherwise, there's no need to touch this. If not instructed by a wiki page or developer.")
@ -185,7 +184,7 @@ class MVCoreConfigNodes {
})
.build());
public final MVValueNode<Boolean> SILENT_START = node(MVValueNode.builder("misc.silent-start", Boolean.class)
public final ConfigNode<Boolean> SILENT_START = node(ConfigNode.builder("misc.silent-start", Boolean.class)
.comment("")
.comment("If true, the startup console messages will no longer show.")
.defaultValue(false)
@ -193,14 +192,14 @@ class MVCoreConfigNodes {
.onSetValue((oldValue, newValue) -> Logging.setShowingConfig(!newValue))
.build());
public final MVValueNode<Boolean> SHOW_DONATION_MESSAGE = node(MVValueNode.builder("misc.show-donation-message", Boolean.class)
public final ConfigNode<Boolean> SHOW_DONATION_MESSAGE = node(ConfigNode.builder("misc.show-donation-message", Boolean.class)
.comment("")
.comment("If you don't want to donate, you can set this to false and Multiverse will stop nagging you.")
.defaultValue(true)
.name("show-donation-message")
.build());
public final MVValueNode<Double> VERSION = node(MVValueNode.builder("version", Double.class)
public final ConfigNode<Double> VERSION = node(ConfigNode.builder("version", Double.class)
.comment("")
.comment("")
.comment("This just signifies the version number so we can see what version of config you have.")

View File

@ -1,360 +0,0 @@
package com.onarandombox.MultiverseCore.configuration;
import java.io.File;
import java.io.IOException;
import java.nio.file.Path;
import java.util.logging.Logger;
import com.dumptruckman.minecraft.util.Logging;
import com.onarandombox.MultiverseCore.configuration.migration.ConfigMigrator;
import com.onarandombox.MultiverseCore.configuration.node.EnhancedValueNode;
import com.onarandombox.MultiverseCore.configuration.node.NodeGroup;
import io.github.townyadvanced.commentedconfiguration.CommentedConfiguration;
import io.github.townyadvanced.commentedconfiguration.setting.CommentedNode;
import io.github.townyadvanced.commentedconfiguration.setting.TypedValueNode;
import io.github.townyadvanced.commentedconfiguration.setting.ValueNode;
import org.bukkit.plugin.Plugin;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
/**
* A class that makes use of CommentedConfiguration to provide a simple way to load and save with node objects.
*/
public class ConfigHandle {
/**
* A builder class for creating a ConfigHandle.
*
* @param configPath The path to the configuration file in string.
* @return A new Builder instance.
*/
public static Builder builder(String configPath) {
return new Builder(configPath);
}
/**
* A builder class for creating a ConfigHandle.
*
* @param configPath The path to the configuration file.
* @return A new Builder instance.
*/
public static Builder builder(Path configPath) {
return new Builder(configPath);
}
@NotNull
protected final Path configPath;
@Nullable
protected final Logger logger;
@NotNull
protected final NodeGroup nodes;
protected final ConfigMigrator migrator;
protected CommentedConfiguration config;
/**
* Creates a new MVSettings instance that makes use of CommentedConfiguration.
*
* @param configPath The path to the configuration file.
* @param logger The Logger to use for error messages.
* @param nodes All the node path and values for the configuration.
* @param migrator The migrator to use for migrating the configuration.
*/
protected ConfigHandle(@NotNull Path configPath, @Nullable Logger logger, @NotNull NodeGroup nodes, ConfigMigrator migrator) {
this.configPath = configPath;
this.nodes = nodes;
this.logger = logger;
this.migrator = migrator;
}
/**
* Loads the configuration.
*
* @return True if the configuration was loaded successfully, false otherwise.
*/
public boolean load() {
if (!createConfigFile()) {
return false;
}
this.config = new CommentedConfiguration(configPath, logger);
if (!config.load()) {
return false;
}
migrateConfig();
parseAllNodes();
return true;
}
/**
* Create a new config file if file does not exist
*
* @return True if file exist or created successfully, otherwise false.
*/
protected boolean createConfigFile() {
File configFile = configPath.toFile();
if (configFile.exists()) {
return true;
}
try {
if (!configFile.createNewFile()) {
return false;
}
Logging.info("Created new config file: %s", configFile.getName());
} catch (IOException e) {
return false;
}
return true;
}
/**
* Migration of the configuration based on {@link ConfigMigrator}.
*/
protected void migrateConfig() {
migrator.migrate(this);
}
/**
* Adds default node values to the configuration if they are not already present.
*/
protected void parseAllNodes() {
CommentedConfiguration oldConfig = config;
this.config = new CommentedConfiguration(configPath, logger);
for (CommentedNode node : nodes) {
if (node.getComments().length > 0) {
config.addComment(node.getPath(), node.getComments());
}
if (node instanceof TypedValueNode typedNode) {
if (!set(typedNode, oldConfig.getObject(node.getPath(), typedNode.getType(), typedNode.getDefaultValue()))) {
Logging.warning("Invalid value for node: %s, resetting to default...", node.getPath());
setDefault(typedNode);
}
} else if (node instanceof ValueNode valueNode) {
if (!set(valueNode, oldConfig.get(node.getPath(), valueNode.getDefaultValue()))) {
Logging.warning("Invalid value for node: %s, resetting to default...", node.getPath());
setDefault(valueNode);
}
}
}
}
/**
* Saves the configuration.
*/
public void save() {
config.save();
}
/**
* Checks if the configuration is loaded.
*
* @return True if the configuration is loaded, false otherwise.
*/
public boolean isLoaded() {
return config != null;
}
/**
* Gets the value of a node, if the node has a default value, it will be returned if the node is not found.
*
* @param node The node to get the value of.
* @return The value of the node.
*/
public Object get(@NotNull ValueNode node) {
return config.get(node.getPath(), node.getDefaultValue());
}
/**
* Get the value of the node by name.
*
* @param name The name of the node to get the value of.
* @return The value of the node.
*/
public Object get(@NotNull String name) {
return nodes.findNode(name)
.map(node -> (node instanceof ValueNode) ? get((ValueNode) node) : null)
.orElse(null);
}
/**
* Gets the value of a node, if the node has a default value, it will be returned if the node is not found.
*
* @param node The node to get the value of.
* @param type The type of the node value.
* @param <T> The type of the node value.
* @return The value of the node.
*/
public <T> T get(@NotNull ValueNode node, Class<T> type) {
return config.getObject(node.getPath(), type, (T) node.getDefaultValue());
}
/**
* Gets the value of a node, if the node has a default value, it will be returned if the node is not found.
*
* @param node The node to get the value of.
* @param <T> The type of the node value.
* @return The value of the node.
*/
public <T> T get(@NotNull TypedValueNode<T> node) {
return config.getObject(node.getPath(), node.getType(), node.getDefaultValue());
}
/**
* Set the value of the node by name.
*
* @param name The name of the node to set the value of.
* @param value The value to set.
*/
public boolean set(@NotNull String name, Object value) {
return nodes.findNode(name)
.map(node -> node instanceof ValueNode && set((ValueNode) node, value))
.orElse(false);
}
/**
* Sets the value of a node, if the validator is not null, it will be tested first.
*
* @param node The node to set the value of.
* @param value The value to set.
*/
public boolean set(@NotNull ValueNode node, Object value) {
if (node instanceof TypedValueNode typedValueNode) {
return set(typedValueNode, value);
}
config.set(node.getPath(), value);
return true;
}
/**
* Sets the value of a node, if the validator is not null, it will be tested first.
*
* @param node The node to set the value of.
* @param value The value to set.
* @param <T> The type of the node value.
*/
public <T> boolean set(@NotNull TypedValueNode<T> node, T value) {
if (node instanceof EnhancedValueNode<T> enhancedValueNode) {
return set(enhancedValueNode, value);
}
config.set(node.getPath(), value);
return true;
}
/**
* Sets the value of a node, if the validator is not null, it will be tested first.
*
* @param node The node to set the value of.
* @param value The value to set.
* @return True if the value was set, false otherwise.
* @param <T> The type of the node value.
*/
public <T> boolean set(@NotNull EnhancedValueNode<T> node, T value) {
if (!node.isValid(value)) {
return false;
}
T oldValue = get(node);
config.set(node.getPath(), value);
node.onSetValue(oldValue, get(node));
return true;
}
/**
* Sets the default value of a node.
*
* @param node The node to set the default value of.
*/
public void setDefault(@NotNull ValueNode node) {
config.set(node.getPath(), node.getDefaultValue());
}
/**
* Gets the inner configuration object.
*
* @return The configuration object.
*/
public @NotNull CommentedConfiguration getConfig() {
return config;
}
/**
* Gets the path of the configuration file.
*/
public static class Builder {
private final Path configPath;
private Logger logger;
private NodeGroup nodes;
private ConfigMigrator migrator;
/**
* Creates a new builder.
*
* @param configPath The path of the configuration file.
*/
public Builder(String configPath) {
this.configPath = Path.of(configPath);
}
/**
* Creates a new builder.
*
* @param configPath The path of the configuration file.
*/
public Builder(Path configPath) {
this.configPath = configPath;
}
/**
* Sets the logger to use.
*
* @param plugin The plugin to get the logger from.
* @return The builder.
*/
public Builder logger(@NotNull Plugin plugin) {
return logger(plugin.getLogger());
}
/**
* Sets the logger to use.
*
* @param logger The logger to use.
* @return The builder.
*/
public Builder logger(@Nullable Logger logger) {
this.logger = logger;
return this;
}
/**
* Sets the nodes to use.
*
* @param nodes The nodes to use.
* @return The builder.
*/
public Builder nodes(@Nullable NodeGroup nodes) {
this.nodes = nodes;
return this;
}
/**
* Sets the migrator to use.
*
* @param migrator The migrator to use.
* @return The builder.
*/
public Builder migrator(@Nullable ConfigMigrator migrator) {
this.migrator = migrator;
return this;
}
/**
* Builds the settings.
*
* @return The built settings.
*/
public ConfigHandle build() {
return new ConfigHandle(configPath, logger, nodes, migrator);
}
}
}

View File

@ -0,0 +1,68 @@
package com.onarandombox.MultiverseCore.configuration.handle;
import java.nio.file.Path;
import java.util.logging.Logger;
import com.dumptruckman.minecraft.util.Logging;
import com.onarandombox.MultiverseCore.configuration.migration.ConfigMigrator;
import com.onarandombox.MultiverseCore.configuration.node.NodeGroup;
import com.onarandombox.MultiverseCore.configuration.node.CommentedNode;
import com.onarandombox.MultiverseCore.configuration.node.ValueNode;
import io.github.townyadvanced.commentedconfiguration.CommentedConfiguration;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
public class CommentedYamlConfigHandle extends FileConfigHandle<CommentedConfiguration> {
public static @NotNull Builder builder(@NotNull Path configPath, @NotNull NodeGroup nodes) {
return new Builder(configPath, nodes);
}
protected CommentedYamlConfigHandle(@NotNull Path configPath, @Nullable Logger logger, @NotNull NodeGroup nodes, @Nullable ConfigMigrator migrator) {
super(configPath, logger, nodes, migrator);
}
@Override
protected boolean loadConfigObject() {
config = new CommentedConfiguration(configPath, logger);
return config.load();
}
@Override
protected void setUpNodes() {
CommentedConfiguration oldConfig = config;
this.config = new CommentedConfiguration(configPath, logger);
nodes.forEach(node -> {
if (node instanceof CommentedNode typedNode) {
if (typedNode.getComments().length > 0) {
config.addComment(typedNode.getPath(), typedNode.getComments());
}
}
if (node instanceof ValueNode valueNode) {
set(valueNode, oldConfig.getObject(valueNode.getPath(), valueNode.getType(), valueNode.getDefaultValue())).onFailure(e -> {
Logging.warning("Failed to set node " + valueNode.getPath() + " to " + valueNode.getDefaultValue());
setDefault(valueNode);
});
}
});
}
@Override
public boolean save() {
config.save();
return true;
}
public static class Builder extends FileConfigHandle.Builder<CommentedConfiguration, Builder> {
protected Builder(@NotNull Path configPath, @NotNull NodeGroup nodes) {
super(configPath, nodes);
}
@Override
public @NotNull CommentedYamlConfigHandle build() {
return new CommentedYamlConfigHandle(configPath, logger, nodes, migrator);
}
}
}

View File

@ -0,0 +1,208 @@
package com.onarandombox.MultiverseCore.configuration.handle;
import java.io.File;
import java.io.IOException;
import java.nio.file.Path;
import java.util.logging.Logger;
import com.dumptruckman.minecraft.util.Logging;
import com.onarandombox.MultiverseCore.configuration.migration.ConfigMigrator;
import com.onarandombox.MultiverseCore.configuration.node.NodeGroup;
import com.onarandombox.MultiverseCore.configuration.node.ValueNode;
import io.vavr.control.Try;
import org.bukkit.configuration.file.FileConfiguration;
import org.bukkit.plugin.Plugin;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
abstract class FileConfigHandle<C extends FileConfiguration> {
protected final @NotNull Path configPath;
protected final @NotNull File configFile;
protected final @Nullable Logger logger;
protected final @NotNull NodeGroup nodes;
protected final @Nullable ConfigMigrator migrator;
protected C config;
protected FileConfigHandle(@NotNull Path configPath, @Nullable Logger logger, @NotNull NodeGroup nodes, @Nullable ConfigMigrator migrator) {
this.configPath = configPath;
this.configFile = configPath.toFile();
this.logger = logger;
this.nodes = nodes;
this.migrator = migrator;
}
/**
* Loads the configuration.
*
* @return True if the configuration was loaded successfully, false otherwise.
*/
public boolean load() {
if (!createConfigFile()) {
Logging.severe("Failed to create config file: %s", configFile.getName());
return false;
}
if (!loadConfigObject()) {
Logging.severe("Failed to load config file: %s", configFile.getName());
return false;
}
migrateConfig();
setUpNodes();
return true;
}
/**
* Create a new config file if file does not exist
*
* @return True if file exist or created successfully, otherwise false.
*/
protected boolean createConfigFile() {
if (configFile.exists()) {
return true;
}
try {
if (!configFile.createNewFile()) {
return false;
}
Logging.info("Created new config file: %s", configFile.getName());
} catch (IOException e) {
return false;
}
return true;
}
protected abstract boolean loadConfigObject();
protected void migrateConfig() {
if (migrator != null) {
migrator.migrate(config);
}
}
protected abstract void setUpNodes();
/**
* Saves the configuration.
*/
public abstract boolean save();
/**
* Checks if the configuration is loaded.
*
* @return True if the configuration is loaded, false otherwise.
*/
public boolean isLoaded() {
return config != null;
}
/**
* Gets the configuration.
*
* @return The configuration.
*/
public C getConfig() {
return config;
}
public Try<Object> get(@Nullable String name) {
return nodes.findNode(name, ValueNode.class)
.map(node -> Try.of(() -> get(node)))
.orElse(Try.failure(new Exception("Node not found")));
}
/**
* Gets the value of a node, if the node has a default value, it will be returned if the node is not found.
*
* @param node The node to get the value of.
* @return The value of the node.
*/
public <T> T get(@NotNull ValueNode<T> node) {
return config.getObject(node.getPath(), node.getType(), node.getDefaultValue());
}
public Try<Boolean> set(@Nullable String name, Object value) {
return nodes.findNode(name, ValueNode.class)
.map(node -> (Try<Boolean>) set(node, value))
.orElse(Try.failure(new Exception("Node not found")));
}
/**
* Sets the value of a node, if the validator is not null, it will be tested first.
*
* @param node The node to set the value of.
* @param value The value to set.
* @return True if the value was set, false otherwise.
* @param <T> The type of the node value.
*/
public <T> Try<Boolean> set(@NotNull ValueNode<T> node, T value) {
if (!node.validate(value)) {
return Try.failure(new Exception("Validation failed"));
}
T oldValue = get(node);
config.set(node.getPath(), value);
node.onSetValue(oldValue, get(node));
return Try.success(true);
}
/**
* Sets the default value of a node.
*
* @param node The node to set the default value of.
*/
public void setDefault(@NotNull ValueNode node) {
config.set(node.getPath(), node.getDefaultValue());
}
public static abstract class Builder<C extends FileConfiguration, B extends Builder<C, B>> {
protected @NotNull Path configPath;
protected @Nullable Logger logger;
protected @NotNull NodeGroup nodes;
protected @Nullable ConfigMigrator migrator;
protected Builder(@NotNull Path configPath, @NotNull NodeGroup nodes) {
this.configPath = configPath;
this.nodes = nodes;
}
/**
* Sets the logger.
*
* @param logger The logger.
* @return The builder.
*/
public B logger(@Nullable Logger logger) {
this.logger = logger;
return self();
}
public B logger(Plugin plugin) {
this.logger = plugin.getLogger();
return self();
}
/**
* Sets the migrator.
*
* @param migrator The migrator.
* @return The builder.
*/
public B migrator(@Nullable ConfigMigrator migrator) {
this.migrator = migrator;
return self();
}
/**
* Builds the configuration handle.
*
* @return The configuration handle.
*/
public abstract @NotNull FileConfigHandle<C> build();
@SuppressWarnings("unchecked")
protected B self() {
return (B) this;
}
}
}

View File

@ -0,0 +1,68 @@
package com.onarandombox.MultiverseCore.configuration.handle;
import java.io.IOException;
import java.nio.file.Path;
import java.util.logging.Logger;
import com.onarandombox.MultiverseCore.configuration.migration.ConfigMigrator;
import com.onarandombox.MultiverseCore.configuration.node.NodeGroup;
import com.onarandombox.MultiverseCore.configuration.node.ValueNode;
import org.bukkit.configuration.InvalidConfigurationException;
import org.bukkit.configuration.file.YamlConfiguration;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
public class YamlConfigHandle extends FileConfigHandle<YamlConfiguration> {
public static @NotNull Builder<? extends Builder> builder(@NotNull Path configPath, @NotNull NodeGroup nodes) {
return new Builder<>(configPath, nodes);
}
protected YamlConfigHandle(@NotNull Path configPath, @Nullable Logger logger, @NotNull NodeGroup nodes, @Nullable ConfigMigrator migrator) {
super(configPath, logger, nodes, migrator);
}
@Override
protected boolean loadConfigObject() {
config = new YamlConfiguration();
try {
config.load(configFile);
} catch (IOException | InvalidConfigurationException e) {
return false;
}
return true;
}
@Override
protected void setUpNodes() {
YamlConfiguration oldConfig = config;
config = new YamlConfiguration();
nodes.forEach(node -> {
if (node instanceof ValueNode valueNode) {
set(valueNode, oldConfig.getObject(valueNode.getPath(), valueNode.getType(), valueNode.getDefaultValue()));
}
});
}
@Override
public boolean save() {
try {
config.save(configFile);
} catch (IOException e) {
return false;
}
return true;
}
public static class Builder<B extends Builder<B>> extends FileConfigHandle.Builder<YamlConfiguration, B> {
protected Builder(@NotNull Path configPath, @NotNull NodeGroup nodes) {
super(configPath, nodes);
}
@Override
public @NotNull YamlConfigHandle build() {
return new YamlConfigHandle(configPath, logger, nodes, migrator);
}
}
}

View File

@ -2,7 +2,7 @@ package com.onarandombox.MultiverseCore.configuration.migration;
import co.aikar.commands.ACFUtil;
import com.dumptruckman.minecraft.util.Logging;
import com.onarandombox.MultiverseCore.configuration.ConfigHandle;
import org.bukkit.configuration.file.FileConfiguration;
/**
* Single migrator action that converts a string value to a boolean.
@ -20,9 +20,9 @@ public class BooleanMigratorAction implements MigratorAction {
}
@Override
public void migrate(ConfigHandle settings) {
settings.getConfig().set(path, ACFUtil.isTruthy(settings.getConfig().getString(path, "")));
Logging.info("Converted %s to boolean %s", path, settings.getConfig().getBoolean(path));
public void migrate(FileConfiguration config) {
config.set(path, ACFUtil.isTruthy(config.getString(path, "")));
Logging.info("Converted %s to boolean %s", path, config.getBoolean(path));
}
}

View File

@ -4,8 +4,9 @@ import java.util.ArrayList;
import java.util.List;
import com.dumptruckman.minecraft.util.Logging;
import com.onarandombox.MultiverseCore.configuration.ConfigHandle;
import com.onarandombox.MultiverseCore.configuration.node.ValueNode;
import io.github.townyadvanced.commentedconfiguration.setting.TypedValueNode;
import org.bukkit.configuration.file.FileConfiguration;
/**
* Helper class for migrating configs to the latest config version.
@ -19,14 +20,14 @@ public class ConfigMigrator {
* Default value should be the current latest version number.
* @return The builder instance.
*/
public static Builder builder(TypedValueNode<Double> versionNode) {
public static Builder builder(ValueNode<Double> versionNode) {
return new Builder(versionNode);
}
private final TypedValueNode<Double> versionNode;
private final ValueNode<Double> versionNode;
private final List<VersionMigrator> versionMigrators;
protected ConfigMigrator(TypedValueNode<Double> versionNode, List<VersionMigrator> versionMigrators) {
protected ConfigMigrator(ValueNode<Double> versionNode, List<VersionMigrator> versionMigrators) {
this.versionNode = versionNode;
this.versionMigrators = versionMigrators;
}
@ -34,25 +35,25 @@ public class ConfigMigrator {
/**
* Migrates the config to the latest version if necessary.
*
* @param settings The target settings instance to migrate.
* @param config The target settings instance to migrate.
*/
public void migrate(ConfigHandle settings) {
double versionNumber = settings.getConfig().getDouble(versionNode.getPath());
public void migrate(FileConfiguration config) {
double versionNumber = config.getDouble(versionNode.getPath());
for (VersionMigrator versionMigrator : versionMigrators) {
if (versionNumber < versionMigrator.getVersion()) {
Logging.info("Migrating config from version %s to %s...", versionNumber, versionMigrator.getVersion());
versionMigrator.migrate(settings);
versionMigrator.migrate(config);
}
}
// Set the version number to the latest version number
settings.setDefault(versionNode);
config.set(versionNode.getPath(), versionNode.getDefaultValue());
}
/**
* A builder for a ConfigMigrator.
*/
public static class Builder {
private final TypedValueNode<Double> versionNode;
private final ValueNode<Double> versionNode;
private final List<VersionMigrator> versionMigrators;
/**
@ -61,7 +62,7 @@ public class ConfigMigrator {
* @param versionNode The node that stores the version number of the config.
* Default value should be the current latest version number.
*/
public Builder(TypedValueNode<Double> versionNode) {
public Builder(ValueNode<Double> versionNode) {
this.versionNode = versionNode;
this.versionMigrators = new ArrayList<>();
}

View File

@ -2,8 +2,7 @@ package com.onarandombox.MultiverseCore.configuration.migration;
import co.aikar.commands.ACFUtil;
import com.dumptruckman.minecraft.util.Logging;
import com.onarandombox.MultiverseCore.configuration.ConfigHandle;
import org.bukkit.util.NumberConversions;
import org.bukkit.configuration.file.FileConfiguration;
/**
* Single migrator action that converts a string value to an integer.
@ -21,8 +20,8 @@ public class IntegerMigratorAction implements MigratorAction {
}
@Override
public void migrate(ConfigHandle settings) {
settings.getConfig().set(path, ACFUtil.parseInt(settings.getConfig().getString(path)));
Logging.info("Converted %s to integer %s", path, settings.getConfig().getInt(path));
public void migrate(FileConfiguration config) {
config.set(path, ACFUtil.parseInt(config.getString(path)));
Logging.info("Converted %s to integer %s", path, config.getInt(path));
}
}

View File

@ -1,7 +1,7 @@
package com.onarandombox.MultiverseCore.configuration.migration;
import com.dumptruckman.minecraft.util.Logging;
import com.onarandombox.MultiverseCore.configuration.ConfigHandle;
import org.bukkit.configuration.file.FileConfiguration;
/**
* Single migrator action that inverts a boolean value for a given path.
@ -28,9 +28,9 @@ public class InvertBoolMigratorAction implements MigratorAction {
* {@inheritDoc}
*/
@Override
public void migrate(ConfigHandle settings) {
boolean boolValue = !settings.getConfig().getBoolean(path);
settings.getConfig().set(path, boolValue);
public void migrate(FileConfiguration config) {
boolean boolValue = !config.getBoolean(path);
config.set(path, boolValue);
Logging.info("Inverted %s to boolean %s", path, boolValue);
}
}

View File

@ -1,6 +1,6 @@
package com.onarandombox.MultiverseCore.configuration.migration;
import com.onarandombox.MultiverseCore.configuration.ConfigHandle;
import org.bukkit.configuration.file.FileConfiguration;
/**
* A migrator action is a single action that is performed when migrating a config.
@ -10,7 +10,7 @@ public interface MigratorAction {
/**
* Performs the migration action.
*
* @param settings The target settings instance to migrate.
* @param config The target settings instance to migrate.
*/
void migrate(ConfigHandle settings);
void migrate(FileConfiguration config);
}

View File

@ -3,7 +3,7 @@ package com.onarandombox.MultiverseCore.configuration.migration;
import java.util.Optional;
import com.dumptruckman.minecraft.util.Logging;
import com.onarandombox.MultiverseCore.configuration.ConfigHandle;
import org.bukkit.configuration.file.FileConfiguration;
/**
* Single migrator action that moves a value from one path to another.
@ -33,11 +33,11 @@ public class MoveMigratorAction implements MigratorAction {
* {@inheritDoc}
*/
@Override
public void migrate(ConfigHandle settings) {
Optional.ofNullable(settings.getConfig().get(fromPath))
public void migrate(FileConfiguration config) {
Optional.ofNullable(config.get(fromPath))
.ifPresent(value -> {
settings.getConfig().set(toPath, value);
settings.getConfig().set(fromPath, null);
config.set(toPath, value);
config.set(fromPath, null);
Logging.config("Moved path %s to %s", fromPath, toPath);
});
}

View File

@ -3,7 +3,7 @@ package com.onarandombox.MultiverseCore.configuration.migration;
import java.util.ArrayList;
import java.util.List;
import com.onarandombox.MultiverseCore.configuration.ConfigHandle;
import org.bukkit.configuration.file.FileConfiguration;
/**
* A version migrator is a collection of migrator actions that are performed when migrating a config to a specific version.
@ -31,10 +31,10 @@ public class VersionMigrator {
/**
* Performs all the migrator actions.
*
* @param settings The target settings instance to migrate.
* @param config The target settings instance to migrate.
*/
public void migrate(ConfigHandle settings) {
actions.forEach(action -> action.migrate(settings));
public void migrate(FileConfiguration config) {
actions.forEach(action -> action.migrate(config));
}
/**

View File

@ -0,0 +1,13 @@
package com.onarandombox.MultiverseCore.configuration.node;
import org.jetbrains.annotations.NotNull;
public interface CommentedNode extends Node {
/**
* Gets the comment of the node.
*
* @return The comment of the node.
*/
@NotNull String[] getComments();
}

View File

@ -0,0 +1,83 @@
package com.onarandombox.MultiverseCore.configuration.node;
import java.util.ArrayList;
import java.util.Collections;
import com.google.common.base.Strings;
import org.jetbrains.annotations.NotNull;
public class ConfigHeaderNode implements CommentedNode {
/**
* Creates a new builder for a {@link ConfigHeaderNode}.
*
* @param path The path of the node.
* @return The new builder.
*/
public static Builder<? extends Builder> builder(String path) {
return new Builder<>(path);
}
private final String path;
private final String[] comments;
protected ConfigHeaderNode(String path, String[] comments) {
this.path = path;
this.comments = comments;
}
/**
* {@inheritDoc}
*/
@Override
public @NotNull String getPath() {
return path;
}
/**
* {@inheritDoc}
*/
@Override
public String[] getComments() {
return comments;
}
public static class Builder<B extends Builder<B>> {
protected final String path;
protected final ArrayList<String> comments;
public Builder(String path) {
this.path = path;
this.comments = new ArrayList<>();
}
/**
* Adds a comment line to the node.
*
* @param comment The comment to add.
* @return This builder.
*/
public B comment(String comment) {
if (!Strings.isNullOrEmpty(comment) && !comment.startsWith("#")) {
comment = "# " + comment;
}
comments.add(comment);
return (B) this;
}
public B comment(String... comments) {
Collections.addAll(this.comments, comments);
return (B) this;
}
public B comment(Iterable<String> comments) {
comments.forEach(this.comments::add);
return (B) this;
}
public ConfigHeaderNode build() {
return new ConfigHeaderNode(path, comments.toArray(new String[0]));
}
}
}

View File

@ -7,39 +7,43 @@ import java.util.function.Function;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
/**
* Implementation of {@link EnhancedValueNode}.
* @param <T> The type of the value.
*/
public class MVValueNode<T> extends MVCommentedNode implements EnhancedValueNode<T> {
public class ConfigNode<T> extends ConfigHeaderNode implements ValueNode<T> {
/**
* Creates a new builder for a {@link MVValueNode}.
* Creates a new builder for a {@link ConfigNode}.
*
* @param path The path of the node.
* @param type The type of the value.
* @return The new builder.
* @param <T> The type of the value.
*/
public static <T> Builder<T, ? extends Builder> builder(String path, Class<T> type) {
return new Builder<>(path, type);
public static <T> ConfigNode.Builder<T, ? extends ConfigNode.Builder> builder(String path, Class<T> type) {
return new ConfigNode.Builder<>(path, type);
}
protected final String name;
protected final Class<T> type;
protected final T defaultValue;
protected final String name;
protected final Function<T, Boolean> validator;
protected final BiConsumer<T, T> onSetValue;
protected MVValueNode(String path, String[] comments, Class<T> type, T defaultValue, String name, Function<T, Boolean> validator, BiConsumer<T, T> onSetValue) {
protected ConfigNode(String path, String[] comments, String name, Class<T> type, T defaultValue, Function<T, Boolean> validator, BiConsumer<T, T> onSetValue) {
super(path, comments);
this.name = name;
this.type = type;
this.defaultValue = defaultValue;
this.name = name;
this.validator = validator;
this.onSetValue = onSetValue;
}
/**
* {@inheritDoc}
*/
@Override
public Optional<String> getName() {
return Optional.ofNullable(name);
}
/**
* {@inheritDoc}
*/
@ -60,15 +64,7 @@ public class MVValueNode<T> extends MVCommentedNode implements EnhancedValueNode
* {@inheritDoc}
*/
@Override
public Optional<String> getName() {
return Optional.ofNullable(name);
}
/**
* {@inheritDoc}
*/
@Override
public boolean isValid(T value) {
public boolean validate(T value) {
if (validator != null) {
return validator.apply(value);
}
@ -86,16 +82,16 @@ public class MVValueNode<T> extends MVCommentedNode implements EnhancedValueNode
}
/**
* Builder for {@link MVValueNode}.
* Builder for {@link ConfigNode}.
*
* @param <T> The type of the value.
* @param <B> The type of the builder.
*/
public static class Builder<T, B extends Builder<T, B>> extends MVCommentedNode.Builder<B> {
public static class Builder<T, B extends ConfigNode.Builder<T, B>> extends ConfigHeaderNode.Builder<B> {
protected String name;
protected final Class<T> type;
protected T defaultValue;
protected String name;
protected Function<T, Boolean> validator;
protected BiConsumer<T, T> onSetValue;
@ -107,8 +103,8 @@ public class MVValueNode<T> extends MVCommentedNode implements EnhancedValueNode
*/
protected Builder(@NotNull String path, @NotNull Class<T> type) {
super(path);
this.type = type;
this.name = path;
this.type = type;
}
/**
@ -153,8 +149,8 @@ public class MVValueNode<T> extends MVCommentedNode implements EnhancedValueNode
* {@inheritDoc}
*/
@Override
public MVValueNode<T> build() {
return new MVValueNode<>(path, comments.toArray(new String[0]), type, defaultValue, name, validator, onSetValue);
public ConfigNode<T> build() {
return new ConfigNode<>(path, comments.toArray(new String[0]), name, type, defaultValue, validator, onSetValue);
}
}
}

View File

@ -1,29 +0,0 @@
package com.onarandombox.MultiverseCore.configuration.node;
import java.util.Optional;
import io.github.townyadvanced.commentedconfiguration.setting.TypedValueNode;
/**
* A {@link TypedValueNode} that has a name, validation, and action to be performed when the value is set.
*
* @param <T> The type of the node's value.
*/
public interface EnhancedValueNode<T> extends TypedValueNode<T> {
/**
* Gets the name of this node. Used for identifying the node from user input.
*
* @return The name of this node.
*/
Optional<String> getName();
boolean isValid(T value);
/**
* Called when the value of this node is set.
*
* @param oldValue The old value.
* @param newValue The new value.
*/
void onSetValue(T oldValue, T newValue);
}

View File

@ -1,91 +0,0 @@
package com.onarandombox.MultiverseCore.configuration.node;
import java.util.ArrayList;
import java.util.List;
import io.github.townyadvanced.commentedconfiguration.setting.CommentedNode;
import org.jetbrains.annotations.NotNull;
/**
* Implementation of {@link CommentedNode} that allows for comments to be added to the node.
*/
public class MVCommentedNode implements CommentedNode {
/**
* Creates a new builder for a {@link MVCommentedNode}.
*
* @param path The path of the node.
* @return The new builder.
*/
public static Builder<Builder> builder(String path) {
return new Builder<>(path);
}
protected final String path;
protected final String[] comments;
protected MVCommentedNode(String path, String[] comments) {
this.path = path;
this.comments = comments;
}
/**
* {@inheritDoc}
*/
@Override
public @NotNull String getPath() {
return path;
}
/**
* {@inheritDoc}
*/
@Override
public @NotNull String[] getComments() {
return comments;
}
/**
* Builder for {@link MVCommentedNode}.
*
* @param <B> The type of the builder.
*/
public static class Builder<B extends Builder> {
protected final String path;
protected final List<String> comments;
/**
* Creates a new builder for a {@link MVCommentedNode}.
*
* @param path The path of the node.
*/
protected Builder(String path) {
this.path = path;
this.comments = new ArrayList<>();
}
/**
* Adds a comment line to the node.
*
* @param comment The comment to add.
* @return This builder.
*/
public B comment(@NotNull String comment) {
if (!comment.isEmpty() && !comment.trim().startsWith("#")) {
// Automatically add a comment prefix if the comment doesn't start with one.
comment = "# " + comment;
}
this.comments.add(comment);
return (B) this;
}
/**
* Builds the node.
*
* @return The built node.
*/
public MVCommentedNode build() {
return new MVCommentedNode(path, comments.toArray(new String[0]));
}
}
}

View File

@ -0,0 +1,13 @@
package com.onarandombox.MultiverseCore.configuration.node;
import org.jetbrains.annotations.NotNull;
public interface Node {
/**
* Gets the YAML path of the node.
*
* @return The YAML path of the node.
*/
@NotNull String getPath();
}

View File

@ -13,30 +13,30 @@ import org.jetbrains.annotations.NotNull;
/**
* A collection of {@link CommentedNode}s, with mappings to nodes by name.
*/
public class NodeGroup implements Collection<CommentedNode> {
private final Collection<CommentedNode> nodes;
private final Map<String, CommentedNode> nodesMap;
public class NodeGroup implements Collection<Node> {
private final Collection<Node> nodes;
private final Map<String, Node> nodesMap;
public NodeGroup() {
this.nodes = new ArrayList<>();
this.nodesMap = new HashMap<>();
}
public NodeGroup(Collection<CommentedNode> nodes) {
public NodeGroup(Collection<Node> nodes) {
this.nodes = nodes;
this.nodesMap = new HashMap<>(nodes.size());
nodes.forEach(this::addNodeIndex);
}
private void addNodeIndex(CommentedNode node) {
if (node instanceof EnhancedValueNode) {
((EnhancedValueNode<?>) node).getName().ifPresent(name -> nodesMap.put(name, node));
private void addNodeIndex(Node node) {
if (node instanceof ValueNode) {
((ValueNode<?>) node).getName().ifPresent(name -> nodesMap.put(name, node));
}
}
private void removeNodeIndex(CommentedNode node) {
if (node instanceof EnhancedValueNode) {
((EnhancedValueNode<?>) node).getName().ifPresent(nodesMap::remove);
private void removeNodeIndex(Node node) {
if (node instanceof ValueNode) {
((ValueNode<?>) node).getName().ifPresent(nodesMap::remove);
}
}
@ -49,14 +49,19 @@ public class NodeGroup implements Collection<CommentedNode> {
return nodesMap.keySet();
}
public Optional<Node> findNode(String name) {
return Optional.ofNullable(nodesMap.get(name));
}
/**
* Gets the node with the given name.
*
* @param name The name of the node to get.
* @return The node with the given name, or {@link Optional#empty()} if no node with the given name exists.
*/
public Optional<CommentedNode> findNode(String name) {
return Optional.ofNullable(nodesMap.get(name));
public <T extends Node> Optional<T> findNode(String name, Class<T> type) {
return Optional.ofNullable(nodesMap.get(name))
.map(node -> type.isAssignableFrom(node.getClass()) ? (T) node : null);
}
@Override
@ -76,7 +81,7 @@ public class NodeGroup implements Collection<CommentedNode> {
@NotNull
@Override
public Iterator<CommentedNode> iterator() {
public Iterator<Node> iterator() {
return nodes.iterator();
}
@ -91,9 +96,9 @@ public class NodeGroup implements Collection<CommentedNode> {
}
@Override
public boolean add(CommentedNode commentedNode) {
if (nodes.add(commentedNode)) {
addNodeIndex(commentedNode);
public boolean add(Node node) {
if (nodes.add(node)) {
addNodeIndex(node);
return true;
}
return false;
@ -102,7 +107,7 @@ public class NodeGroup implements Collection<CommentedNode> {
@Override
public boolean remove(Object o) {
if (nodes.remove(o) && o instanceof CommentedNode) {
removeNodeIndex((CommentedNode) o);
removeNodeIndex((Node) o);
return true;
}
return false;
@ -114,7 +119,7 @@ public class NodeGroup implements Collection<CommentedNode> {
}
@Override
public boolean addAll(@NotNull Collection<? extends CommentedNode> collection) {
public boolean addAll(@NotNull Collection<? extends Node> collection) {
return nodes.addAll(collection);
}

View File

@ -0,0 +1,45 @@
package com.onarandombox.MultiverseCore.configuration.node;
import java.util.Optional;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
public interface ValueNode<T> extends Node {
/**
* Gets the name of this node. Used for identifying the node from user input.
*
* @return The name of this node.
*/
Optional<String> getName();
/**
* Gets the class type {@link T} of the node value.
*
* @return The class type of the node value.
*/
@NotNull Class<T> getType();
/**
* Gets the default value with type {@link T} of the node.
*
* @return The default value of the node.
*/
@Nullable T getDefaultValue();
/**
* Validates the value of this node.
*
* @param value The value to validate.
* @return True if the value is valid, false otherwise.
*/
boolean validate(T value);
/**
* Called when the value of this node is set.
*
* @param oldValue The old value.
* @param newValue The new value.
*/
void onSetValue(T oldValue, T newValue);
}

View File

@ -59,14 +59,14 @@ class ConfigTest : TestWithMockBukkit() {
@Test
fun `Getting existing config property with getProperty returns expected value`() {
assertEquals(false, config.getProperty("enforce-access"))
assertEquals("world", config.getProperty("first-spawn-location"))
assertEquals(false, config.getProperty("enforce-access").get())
assertEquals("world", config.getProperty("first-spawn-location").get())
}
@Test
fun `Getting non-existing config property with getProperty returns null`() {
assertNull(config.getProperty("invalid-property"))
assertNull(config.getProperty("version"))
assertTrue(config.getProperty("invalid-property").isFailure)
assertTrue(config.getProperty("version").isFailure)
}
@Test
@ -77,19 +77,19 @@ class ConfigTest : TestWithMockBukkit() {
@Test
fun `Updating an existing config property with setProperty reflects the changes in getProperty`() {
assertTrue(config.setProperty("enforce-access", true))
assertEquals(true, config.getProperty("enforce-access"))
assertTrue(config.setProperty("enforce-access", true).get())
assertEquals(true, config.getProperty("enforce-access").get())
assertTrue(config.setProperty("first-spawn-location", "world2"))
assertEquals("world2", config.getProperty("first-spawn-location"))
assertTrue(config.setProperty("first-spawn-location", "world2").get())
assertEquals("world2", config.getProperty("first-spawn-location").get())
assertTrue(config.setProperty("global-debug", 1))
assertEquals(1, config.getProperty("global-debug"))
assertTrue(config.setProperty("global-debug", 1).get())
assertEquals(1, config.getProperty("global-debug").get())
}
@Test
fun `Updating a non-existing property with setProperty returns false`() {
assertFalse(config.setProperty("invalid-property", false))
assertFalse(config.setProperty("version", 1.1))
assertTrue(config.setProperty("invalid-property", false).isFailure)
assertTrue(config.setProperty("version", 1.1).isFailure)
}
}