diff --git a/src/main/java/com/onarandombox/MultiverseCore/api/MVConfig.java b/src/main/java/com/onarandombox/MultiverseCore/api/MVConfig.java index 090fc296..55d11a78 100644 --- a/src/main/java/com/onarandombox/MultiverseCore/api/MVConfig.java +++ b/src/main/java/com/onarandombox/MultiverseCore/api/MVConfig.java @@ -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 @@ -34,19 +35,21 @@ public interface MVConfig { /** * Gets a property from the config. * - * @param name The name of the property. - * @return The value of the property. + * @param name The name of the property. + * @return A {@link Try} with the value of the property, otherwise a {@link Try.Failure} if there is no property by + * that name. */ - Object getProperty(String name); + Try getProperty(String name); /** * Sets a property in the config. * * @param name The name of the property. * @param value The value of the property. - * @return True if the property was set successfully. + * @return An empty {@link Try} if the property was set successfully, otherwise a {@link Try.Failure} with the + * exception explaining why the property could not be set. */ - boolean setProperty(String name, Object value); + Try setProperty(String name, Object value); /** * Sets world access permissions should be enforced. diff --git a/src/main/java/com/onarandombox/MultiverseCore/commands/ConfigCommand.java b/src/main/java/com/onarandombox/MultiverseCore/commands/ConfigCommand.java index 484a3228..60c14b5b 100644 --- a/src/main/java/com/onarandombox/MultiverseCore/commands/ConfigCommand.java +++ b/src/main/java/com/onarandombox/MultiverseCore/commands/ConfigCommand.java @@ -1,6 +1,5 @@ package com.onarandombox.MultiverseCore.commands; -import co.aikar.commands.BukkitCommandIssuer; import co.aikar.commands.annotation.CommandAlias; import co.aikar.commands.annotation.CommandCompletion; import co.aikar.commands.annotation.CommandPermission; @@ -9,11 +8,12 @@ import co.aikar.commands.annotation.Optional; import co.aikar.commands.annotation.Single; import co.aikar.commands.annotation.Subcommand; import co.aikar.commands.annotation.Syntax; -import com.onarandombox.MultiverseCore.api.MVConfig; +import com.onarandombox.MultiverseCore.commandtools.MVCommandIssuer; 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 com.onarandombox.MultiverseCore.exceptions.MultiverseException; import jakarta.inject.Inject; import org.jetbrains.annotations.NotNull; import org.jvnet.hk2.annotations.Service; @@ -35,7 +35,7 @@ public class ConfigCommand extends MultiverseCommand { @CommandCompletion("@mvconfigs") @Syntax(" [new-value]") @Description("") //TODO - public void onConfigCommand(BukkitCommandIssuer issuer, + public void onConfigCommand(MVCommandIssuer issuer, @Syntax("") @Description("") //TODO @@ -54,21 +54,26 @@ public class ConfigCommand extends MultiverseCommand { updateConfigValue(issuer, name, value.getValue()); } - 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)); + private void showConfigValue(MVCommandIssuer issuer, String name) { + config.getProperty(name) + .onSuccess(value -> issuer.sendMessage(name + "is currently set to " + value)) + .onFailure(e -> issuer.sendMessage("Unable to get " + name + ": " + e.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); + private void updateConfigValue(MVCommandIssuer issuer, String name, Object value) { + config.setProperty(name, value) + .onSuccess(ignore -> { + config.save(); + issuer.sendMessage("Successfully set " + name + " to " + value); + }) + .onFailure(e -> { + issuer.sendMessage("Unable to set " + name + " to " + value + "."); + if (e instanceof MultiverseException) { + var message = ((MultiverseException) e).getMVMessage(); + if (message != null) { + issuer.sendError(message); + } + } + }); } } diff --git a/src/main/java/com/onarandombox/MultiverseCore/commandtools/MVCommandContexts.java b/src/main/java/com/onarandombox/MultiverseCore/commandtools/MVCommandContexts.java index 3b1ea6b9..1f490ba8 100644 --- a/src/main/java/com/onarandombox/MultiverseCore/commandtools/MVCommandContexts.java +++ b/src/main/java/com/onarandombox/MultiverseCore/commandtools/MVCommandContexts.java @@ -15,14 +15,15 @@ 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 io.vavr.control.Option; import jakarta.inject.Inject; import org.bukkit.GameRule; import org.bukkit.entity.Player; @@ -123,7 +124,7 @@ public class MVCommandContexts extends PaperCommandContexts { if (Strings.isNullOrEmpty(configName)) { throw new InvalidCommandArgument("No config name specified."); } - Optional node = config.getNodes().findNode(configName); + Option node = config.getNodes().findNode(configName); if (node.isEmpty()) { throw new InvalidCommandArgument("The config " + configName + " is not valid."); } @@ -133,12 +134,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 resolver = getResolver(((TypedValueNode) node.get()).getType()); + ContextResolver resolver = getResolver(((ValueNode) node.get()).getType()); if (resolver == null) { context.popFirstArg(); return new MVConfigValue(valueString); diff --git a/src/main/java/com/onarandombox/MultiverseCore/config/MVCoreConfig.java b/src/main/java/com/onarandombox/MultiverseCore/config/MVCoreConfig.java index 27613587..8afb8a18 100644 --- a/src/main/java/com/onarandombox/MultiverseCore/config/MVCoreConfig.java +++ b/src/main/java/com/onarandombox/MultiverseCore/config/MVCoreConfig.java @@ -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,13 +28,13 @@ 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) .logger(Logging.getLogger()) .nodes(configNodes.getNodes()) .migrator(ConfigMigrator.builder(configNodes.VERSION) @@ -110,12 +111,12 @@ public class MVCoreConfig implements MVConfig { } @Override - public Object getProperty(String name) { + public Try getProperty(String name) { return configHandle.get(name); } @Override - public boolean setProperty(String name, Object value) { + public Try setProperty(String name, Object value) { return configHandle.set(name, value); } diff --git a/src/main/java/com/onarandombox/MultiverseCore/config/MVCoreConfigNodes.java b/src/main/java/com/onarandombox/MultiverseCore/config/MVCoreConfigNodes.java index ae027b4e..4aa05878 100644 --- a/src/main/java/com/onarandombox/MultiverseCore/config/MVCoreConfigNodes.java +++ b/src/main/java/com/onarandombox/MultiverseCore/config/MVCoreConfigNodes.java @@ -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 node(N node) { + private 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 ENFORCE_ACCESS = node(MVValueNode.builder("world.enforce-access", Boolean.class) + public final ConfigNode 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 ENFORCE_GAMEMODE = node(MVValueNode.builder("world.enforce-gamemode", Boolean.class) + public final ConfigNode 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 AUTO_PURGE_ENTITIES = node(MVValueNode.builder("world.auto-purge-entities", Boolean.class) + public final ConfigNode 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 TELEPORT_INTERCEPT = node(MVValueNode.builder("world.teleport-intercept", Boolean.class) + public final ConfigNode 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 FIRST_SPAWN_OVERRIDE = node(MVValueNode.builder("spawn.first-spawn-override", Boolean.class) + public final ConfigNode 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 FIRST_SPAWN_LOCATION = node(MVValueNode.builder("spawn.first-spawn-location", String.class) + public final ConfigNode 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 USE_CUSTOM_PORTAL_SEARCH = node(MVValueNode.builder("portal.use-custom-portal-search", Boolean.class) + public final ConfigNode 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 CUSTOM_PORTAL_SEARCH_RADIUS = node(MVValueNode.builder("portal.custom-portal-search-radius", Integer.class) + public final ConfigNode 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 ENABLE_CHAT_PREFIX = node(MVValueNode.builder("messaging.enable-chat-prefix", Boolean.class) + public final ConfigNode 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 CHAT_PREFIX_FORMAT = node(MVValueNode.builder("messaging.chat-prefix-format", String.class) + public final ConfigNode 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 REGISTER_PAPI_HOOK = node(MVValueNode.builder("messaging.register-papi-hook", Boolean.class) + public final ConfigNode 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 GLOBAL_DEBUG = node(MVValueNode.builder("misc.global-debug", Integer.class) + public final ConfigNode 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 SILENT_START = node(MVValueNode.builder("misc.silent-start", Boolean.class) + public final ConfigNode 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 SHOW_DONATION_MESSAGE = node(MVValueNode.builder("misc.show-donation-message", Boolean.class) + public final ConfigNode 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 VERSION = node(MVValueNode.builder("version", Double.class) + public final ConfigNode 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.") diff --git a/src/main/java/com/onarandombox/MultiverseCore/configuration/ConfigHandle.java b/src/main/java/com/onarandombox/MultiverseCore/configuration/ConfigHandle.java deleted file mode 100644 index 4aabb2d7..00000000 --- a/src/main/java/com/onarandombox/MultiverseCore/configuration/ConfigHandle.java +++ /dev/null @@ -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 The type of the node value. - * @return The value of the node. - */ - public T get(@NotNull ValueNode node, Class 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 The type of the node value. - * @return The value of the node. - */ - public T get(@NotNull TypedValueNode 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 The type of the node value. - */ - public boolean set(@NotNull TypedValueNode node, T value) { - if (node instanceof EnhancedValueNode 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 The type of the node value. - */ - public boolean set(@NotNull EnhancedValueNode 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); - } - } -} diff --git a/src/main/java/com/onarandombox/MultiverseCore/configuration/handle/CommentedYamlConfigHandle.java b/src/main/java/com/onarandombox/MultiverseCore/configuration/handle/CommentedYamlConfigHandle.java new file mode 100644 index 00000000..f43a2a86 --- /dev/null +++ b/src/main/java/com/onarandombox/MultiverseCore/configuration/handle/CommentedYamlConfigHandle.java @@ -0,0 +1,93 @@ +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; + +/** + * Configuration handle for commented YAML files. + */ +public class CommentedYamlConfigHandle extends FileConfigHandle { + + /** + * Creates a new builder for a {@link CommentedYamlConfigHandle}. + * + * @param configPath The path to the config file. + * @return The builder. + */ + public static @NotNull Builder builder(@NotNull Path configPath) { + return new Builder(configPath); + } + + protected CommentedYamlConfigHandle(@NotNull Path configPath, @Nullable Logger logger, @Nullable NodeGroup nodes, @Nullable ConfigMigrator migrator) { + super(configPath, logger, nodes, migrator); + } + + /** + * {@inheritDoc} + */ + @Override + protected boolean loadConfigObject() { + config = new CommentedConfiguration(configPath, logger); + return config.load(); + } + + /** + * {@inheritDoc} + */ + @Override + protected void setUpNodes() { + if (nodes == null || nodes.isEmpty()) { + return; + } + + 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); + }); + } + }); + } + + /** + * {@inheritDoc} + */ + @Override + public boolean save() { + config.save(); + return true; + } + + public static class Builder extends FileConfigHandle.Builder { + + protected Builder(@NotNull Path configPath) { + super(configPath); + } + + /** + * {@inheritDoc} + */ + @Override + public @NotNull CommentedYamlConfigHandle build() { + return new CommentedYamlConfigHandle(configPath, logger, nodes, migrator); + } + } +} diff --git a/src/main/java/com/onarandombox/MultiverseCore/configuration/handle/FileConfigHandle.java b/src/main/java/com/onarandombox/MultiverseCore/configuration/handle/FileConfigHandle.java new file mode 100644 index 00000000..f5742fb5 --- /dev/null +++ b/src/main/java/com/onarandombox/MultiverseCore/configuration/handle/FileConfigHandle.java @@ -0,0 +1,259 @@ +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.ConfigNodeNotFoundException; +import com.onarandombox.MultiverseCore.configuration.node.NodeGroup; +import com.onarandombox.MultiverseCore.configuration.node.ValueNode; +import com.onarandombox.MultiverseCore.exceptions.MultiverseException; +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; + +/** + * Generic configuration handle for file based configurations. + * @param The configuration type. + */ +abstract class FileConfigHandle { + + protected final @NotNull Path configPath; + protected final @NotNull File configFile; + protected final @Nullable Logger logger; + protected final @Nullable NodeGroup nodes; + protected final @Nullable ConfigMigrator migrator; + + protected C config; + + protected FileConfigHandle(@NotNull Path configPath, @Nullable Logger logger, @Nullable 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; + } + + /** + * Loads the configuration object. + * + * @return True if the configuration was loaded successfully, false otherwise. + */ + protected abstract boolean loadConfigObject(); + + /** + * Migrates the configuration. + */ + protected void migrateConfig() { + if (migrator != null) { + migrator.migrate(config); + } + } + + /** + * Sets up the nodes. + */ + 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; + } + + /** + * Gets the value of a node, if the node has a default value, it will be returned if the node is not found. + * @param name The name of the node. + * @return The value of the node. + */ + public Try get(@Nullable String name) { + return nodes.findNode(name, ValueNode.class) + .toTry(() -> new ConfigNodeNotFoundException(name)) + .map(node -> get((ValueNode) node)); + } + + /** + * 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 get(@NotNull ValueNode node) { + return config.getObject(node.getPath(), node.getType(), node.getDefaultValue()); + } + + /** + * Sets the value of a node, if the validator is not null, it will be tested first. + * + * @param name The name of the node. + * @param value The value to set. + * @return True if the value was set, false otherwise. + */ + public Try set(@Nullable String name, Object value) { + return nodes.findNode(name, ValueNode.class) + .toTry(() -> new ConfigNodeNotFoundException(name)) + .flatMap(node -> set(node, value)); + } + + /** + * 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 The type of the node value. + */ + public Try set(@NotNull ValueNode node, T value) { + if (!node.validate(value)) { + return Try.failure(new MultiverseException("Validation failed", null)); // TODO replace validation + } + T oldValue = get(node); + config.set(node.getPath(), value); + node.onSetValue(oldValue, get(node)); + return Try.success(null); + } + + /** + * 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()); + } + + /** + * Builder for {@link FileConfigHandle}. + * + * @param The configuration type. + * @param The builder type. + */ + public static abstract class Builder> { + + protected @NotNull Path configPath; + protected @Nullable Logger logger; + protected @Nullable NodeGroup nodes; + protected @Nullable ConfigMigrator migrator; + + protected Builder(@NotNull Path configPath) { + this.configPath = configPath; + } + + /** + * Sets the logger. + * + * @param logger The logger. + * @return The builder. + */ + public B logger(@Nullable Logger logger) { + this.logger = logger; + return self(); + } + + /** + * Sets the logger. + * + * @param plugin The plugin to get the logger from. + * @return The builder. + */ + public B logger(Plugin plugin) { + this.logger = plugin.getLogger(); + return self(); + } + + /** + * Sets the nodes. + * + * @param nodes The nodes. + * @return The builder. + */ + public B nodes(@Nullable NodeGroup nodes) { + this.nodes = nodes; + 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 build(); + + @SuppressWarnings("unchecked") + protected B self() { + return (B) this; + } + } +} diff --git a/src/main/java/com/onarandombox/MultiverseCore/configuration/handle/YamlConfigHandle.java b/src/main/java/com/onarandombox/MultiverseCore/configuration/handle/YamlConfigHandle.java new file mode 100644 index 00000000..cfc385b8 --- /dev/null +++ b/src/main/java/com/onarandombox/MultiverseCore/configuration/handle/YamlConfigHandle.java @@ -0,0 +1,97 @@ +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; + +/** + * Configuration handle for YAML files. + */ +public class YamlConfigHandle extends FileConfigHandle { + + /** + * Creates a new builder for {@link YamlConfigHandle}. + * + * @param configPath The path to the config file. + * @return The builder. + */ + public static @NotNull Builder builder(@NotNull Path configPath) { + return new Builder<>(configPath); + } + + protected YamlConfigHandle(@NotNull Path configPath, @Nullable Logger logger, @Nullable NodeGroup nodes, @Nullable ConfigMigrator migrator) { + super(configPath, logger, nodes, migrator); + } + + /** + * {@inheritDoc} + */ + @Override + protected boolean loadConfigObject() { + config = new YamlConfiguration(); + try { + config.load(configFile); + } catch (IOException | InvalidConfigurationException e) { + return false; + } + return true; + } + + /** + * {@inheritDoc} + */ + @Override + protected void setUpNodes() { + if (nodes == null || nodes.isEmpty()) { + return; + } + + YamlConfiguration oldConfig = config; + config = new YamlConfiguration(); + nodes.forEach(node -> { + if (node instanceof ValueNode valueNode) { + set(valueNode, oldConfig.getObject(valueNode.getPath(), valueNode.getType(), valueNode.getDefaultValue())); + } + }); + } + + /** + * {@inheritDoc} + */ + @Override + public boolean save() { + try { + config.save(configFile); + } catch (IOException e) { + return false; + } + return true; + } + + /** + * Builder for {@link YamlConfigHandle}. + * @param The type of the builder. + */ + public static class Builder> extends FileConfigHandle.Builder { + + protected Builder(@NotNull Path configPath) { + super(configPath); + } + + /** + * {@inheritDoc} + */ + @Override + public @NotNull YamlConfigHandle build() { + return new YamlConfigHandle(configPath, logger, nodes, migrator); + } + } +} diff --git a/src/main/java/com/onarandombox/MultiverseCore/configuration/migration/BooleanMigratorAction.java b/src/main/java/com/onarandombox/MultiverseCore/configuration/migration/BooleanMigratorAction.java index b4546215..f89eeb47 100644 --- a/src/main/java/com/onarandombox/MultiverseCore/configuration/migration/BooleanMigratorAction.java +++ b/src/main/java/com/onarandombox/MultiverseCore/configuration/migration/BooleanMigratorAction.java @@ -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)); } } diff --git a/src/main/java/com/onarandombox/MultiverseCore/configuration/migration/ConfigMigrator.java b/src/main/java/com/onarandombox/MultiverseCore/configuration/migration/ConfigMigrator.java index 6d7faac3..09381979 100644 --- a/src/main/java/com/onarandombox/MultiverseCore/configuration/migration/ConfigMigrator.java +++ b/src/main/java/com/onarandombox/MultiverseCore/configuration/migration/ConfigMigrator.java @@ -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 versionNode) { + public static Builder builder(ValueNode versionNode) { return new Builder(versionNode); } - private final TypedValueNode versionNode; + private final ValueNode versionNode; private final List versionMigrators; - protected ConfigMigrator(TypedValueNode versionNode, List versionMigrators) { + protected ConfigMigrator(ValueNode versionNode, List 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 versionNode; + private final ValueNode versionNode; private final List 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 versionNode) { + public Builder(ValueNode versionNode) { this.versionNode = versionNode; this.versionMigrators = new ArrayList<>(); } diff --git a/src/main/java/com/onarandombox/MultiverseCore/configuration/migration/IntegerMigratorAction.java b/src/main/java/com/onarandombox/MultiverseCore/configuration/migration/IntegerMigratorAction.java index 964187c1..2c2eef07 100644 --- a/src/main/java/com/onarandombox/MultiverseCore/configuration/migration/IntegerMigratorAction.java +++ b/src/main/java/com/onarandombox/MultiverseCore/configuration/migration/IntegerMigratorAction.java @@ -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)); } } diff --git a/src/main/java/com/onarandombox/MultiverseCore/configuration/migration/InvertBoolMigratorAction.java b/src/main/java/com/onarandombox/MultiverseCore/configuration/migration/InvertBoolMigratorAction.java index e5709242..72366d7d 100644 --- a/src/main/java/com/onarandombox/MultiverseCore/configuration/migration/InvertBoolMigratorAction.java +++ b/src/main/java/com/onarandombox/MultiverseCore/configuration/migration/InvertBoolMigratorAction.java @@ -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); } } diff --git a/src/main/java/com/onarandombox/MultiverseCore/configuration/migration/MigratorAction.java b/src/main/java/com/onarandombox/MultiverseCore/configuration/migration/MigratorAction.java index 1f3fdbe8..3aaed4e4 100644 --- a/src/main/java/com/onarandombox/MultiverseCore/configuration/migration/MigratorAction.java +++ b/src/main/java/com/onarandombox/MultiverseCore/configuration/migration/MigratorAction.java @@ -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); } diff --git a/src/main/java/com/onarandombox/MultiverseCore/configuration/migration/MoveMigratorAction.java b/src/main/java/com/onarandombox/MultiverseCore/configuration/migration/MoveMigratorAction.java index 662d5685..498c440b 100644 --- a/src/main/java/com/onarandombox/MultiverseCore/configuration/migration/MoveMigratorAction.java +++ b/src/main/java/com/onarandombox/MultiverseCore/configuration/migration/MoveMigratorAction.java @@ -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); }); } diff --git a/src/main/java/com/onarandombox/MultiverseCore/configuration/migration/VersionMigrator.java b/src/main/java/com/onarandombox/MultiverseCore/configuration/migration/VersionMigrator.java index e0aacbfd..0bb072a0 100644 --- a/src/main/java/com/onarandombox/MultiverseCore/configuration/migration/VersionMigrator.java +++ b/src/main/java/com/onarandombox/MultiverseCore/configuration/migration/VersionMigrator.java @@ -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)); } /** diff --git a/src/main/java/com/onarandombox/MultiverseCore/configuration/node/CommentedNode.java b/src/main/java/com/onarandombox/MultiverseCore/configuration/node/CommentedNode.java new file mode 100644 index 00000000..80a085ed --- /dev/null +++ b/src/main/java/com/onarandombox/MultiverseCore/configuration/node/CommentedNode.java @@ -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(); +} diff --git a/src/main/java/com/onarandombox/MultiverseCore/configuration/node/ConfigHeaderNode.java b/src/main/java/com/onarandombox/MultiverseCore/configuration/node/ConfigHeaderNode.java new file mode 100644 index 00000000..00b330ab --- /dev/null +++ b/src/main/java/com/onarandombox/MultiverseCore/configuration/node/ConfigHeaderNode.java @@ -0,0 +1,81 @@ +package com.onarandombox.MultiverseCore.configuration.node; + +import java.util.ArrayList; +import java.util.List; + +import com.google.common.base.Strings; +import org.jetbrains.annotations.NotNull; + +/** + * A node that represents a header without any value. + */ +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 @NotNull Builder> builder(String path) { + return new Builder<>(path); + } + + private final @NotNull String path; + private final @NotNull String[] comments; + + protected ConfigHeaderNode(@NotNull String path, @NotNull String[] comments) { + this.path = path; + this.comments = comments; + } + + /** + * {@inheritDoc} + */ + @Override + public @NotNull String getPath() { + return path; + } + + /** + * {@inheritDoc} + */ + @Override + public @NotNull String[] getComments() { + return comments; + } + + public static class Builder> { + + protected final @NotNull String path; + protected final @NotNull List comments; + + public Builder(@NotNull 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 @NotNull B comment(@NotNull String comment) { + if (!Strings.isNullOrEmpty(comment) && !comment.startsWith("#")) { + comment = "# " + comment; + } + comments.add(comment); + return (B) this; + } + + /** + * Builds the node. + * + * @return The built node. + */ + public @NotNull ConfigHeaderNode build() { + return new ConfigHeaderNode(path, comments.toArray(new String[0])); + } + } +} diff --git a/src/main/java/com/onarandombox/MultiverseCore/configuration/node/MVValueNode.java b/src/main/java/com/onarandombox/MultiverseCore/configuration/node/ConfigNode.java similarity index 56% rename from src/main/java/com/onarandombox/MultiverseCore/configuration/node/MVValueNode.java rename to src/main/java/com/onarandombox/MultiverseCore/configuration/node/ConfigNode.java index c8b9d9df..42d3f12f 100644 --- a/src/main/java/com/onarandombox/MultiverseCore/configuration/node/MVValueNode.java +++ b/src/main/java/com/onarandombox/MultiverseCore/configuration/node/ConfigNode.java @@ -1,45 +1,64 @@ package com.onarandombox.MultiverseCore.configuration.node; -import java.util.Optional; import java.util.function.BiConsumer; import java.util.function.Function; +import io.vavr.control.Option; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; /** - * Implementation of {@link EnhancedValueNode}. - * @param The type of the value. + * A node that contains a value. + * @param The type of the value. */ -public class MVValueNode extends MVCommentedNode implements EnhancedValueNode { +public class ConfigNode extends ConfigHeaderNode implements ValueNode { /** - * 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 The type of the value. */ - public static Builder builder(String path, Class type) { - return new Builder<>(path, type); + public static @NotNull ConfigNode.Builder> builder( + @NotNull String path, + @NotNull Class type + ) { + return new ConfigNode.Builder<>(path, type); } - protected final Class type; - protected final T defaultValue; - protected final String name; - protected final Function validator; - protected final BiConsumer onSetValue; + protected final @Nullable String name; + protected final @NotNull Class type; + protected final @Nullable T defaultValue; + protected final @Nullable Function validator; + protected final @Nullable BiConsumer onSetValue; - protected MVValueNode(String path, String[] comments, Class type, T defaultValue, String name, Function validator, BiConsumer onSetValue) { + protected ConfigNode( + @NotNull String path, + @NotNull String[] comments, + @Nullable String name, + @NotNull Class type, + @Nullable T defaultValue, + @Nullable Function validator, + @Nullable BiConsumer 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 @NotNull Option getName() { + return Option.of(name); + } + /** * {@inheritDoc} */ @@ -60,15 +79,7 @@ public class MVValueNode extends MVCommentedNode implements EnhancedValueNode * {@inheritDoc} */ @Override - public Optional getName() { - return Optional.ofNullable(name); - } - - /** - * {@inheritDoc} - */ - @Override - public boolean isValid(T value) { + public boolean validate(@Nullable T value) { if (validator != null) { return validator.apply(value); } @@ -79,25 +90,25 @@ public class MVValueNode extends MVCommentedNode implements EnhancedValueNode * {@inheritDoc} */ @Override - public void onSetValue(T oldValue, T newValue) { + public void onSetValue(@Nullable T oldValue, @Nullable T newValue) { if (onSetValue != null) { onSetValue.accept(oldValue, newValue); } } /** - * Builder for {@link MVValueNode}. + * Builder for {@link ConfigNode}. * * @param The type of the value. * @param The type of the builder. */ - public static class Builder> extends MVCommentedNode.Builder { + public static class Builder> extends ConfigHeaderNode.Builder { - protected final Class type; - protected T defaultValue; - protected String name; - protected Function validator; - protected BiConsumer onSetValue; + protected @Nullable String name; + protected @NotNull final Class type; + protected @Nullable T defaultValue; + protected @Nullable Function validator; + protected @Nullable BiConsumer onSetValue; /** * Creates a new builder. @@ -107,8 +118,8 @@ public class MVValueNode extends MVCommentedNode implements EnhancedValueNode */ protected Builder(@NotNull String path, @NotNull Class type) { super(path); - this.type = type; this.name = path; + this.type = type; } /** @@ -117,7 +128,7 @@ public class MVValueNode extends MVCommentedNode implements EnhancedValueNode * @param defaultValue The default value. * @return This builder. */ - public B defaultValue(@NotNull T defaultValue) { + public @NotNull B defaultValue(@NotNull T defaultValue) { this.defaultValue = defaultValue; return (B) this; } @@ -128,12 +139,12 @@ public class MVValueNode extends MVCommentedNode implements EnhancedValueNode * @param name The name of this node. * @return This builder. */ - public B name(@Nullable String name) { + public @NotNull B name(@Nullable String name) { this.name = name; return (B) this; } - public B validator(@Nullable Function validator) { + public @NotNull B validator(@NotNull Function validator) { this.validator = validator; return (B) this; } @@ -144,7 +155,7 @@ public class MVValueNode extends MVCommentedNode implements EnhancedValueNode * @param onSetValue The action to be performed. * @return This builder. */ - public B onSetValue(@Nullable BiConsumer onSetValue) { + public @NotNull B onSetValue(@NotNull BiConsumer onSetValue) { this.onSetValue = onSetValue; return (B) this; } @@ -153,8 +164,8 @@ public class MVValueNode extends MVCommentedNode implements EnhancedValueNode * {@inheritDoc} */ @Override - public MVValueNode build() { - return new MVValueNode<>(path, comments.toArray(new String[0]), type, defaultValue, name, validator, onSetValue); + public @NotNull ConfigNode build() { + return new ConfigNode<>(path, comments.toArray(new String[0]), name, type, defaultValue, validator, onSetValue); } } } diff --git a/src/main/java/com/onarandombox/MultiverseCore/configuration/node/ConfigNodeNotFoundException.java b/src/main/java/com/onarandombox/MultiverseCore/configuration/node/ConfigNodeNotFoundException.java new file mode 100644 index 00000000..3917a17e --- /dev/null +++ b/src/main/java/com/onarandombox/MultiverseCore/configuration/node/ConfigNodeNotFoundException.java @@ -0,0 +1,14 @@ +package com.onarandombox.MultiverseCore.configuration.node; + +import com.onarandombox.MultiverseCore.exceptions.MultiverseException; +import org.jetbrains.annotations.Nullable; + +import static com.onarandombox.MultiverseCore.utils.MVCorei18n.CONFIG_NODE_NOTFOUND; +import static com.onarandombox.MultiverseCore.utils.message.MessageReplacement.replace; + +public class ConfigNodeNotFoundException extends MultiverseException { + + public ConfigNodeNotFoundException(@Nullable String nodeName) { + super(CONFIG_NODE_NOTFOUND.bundle("Config node not found: {node}", replace("{node}").with(nodeName)), null); + } +} diff --git a/src/main/java/com/onarandombox/MultiverseCore/configuration/node/EnhancedValueNode.java b/src/main/java/com/onarandombox/MultiverseCore/configuration/node/EnhancedValueNode.java deleted file mode 100644 index c29ecf17..00000000 --- a/src/main/java/com/onarandombox/MultiverseCore/configuration/node/EnhancedValueNode.java +++ /dev/null @@ -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 The type of the node's value. - */ -public interface EnhancedValueNode extends TypedValueNode { - /** - * Gets the name of this node. Used for identifying the node from user input. - * - * @return The name of this node. - */ - Optional 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); -} diff --git a/src/main/java/com/onarandombox/MultiverseCore/configuration/node/MVCommentedNode.java b/src/main/java/com/onarandombox/MultiverseCore/configuration/node/MVCommentedNode.java deleted file mode 100644 index eaf2637c..00000000 --- a/src/main/java/com/onarandombox/MultiverseCore/configuration/node/MVCommentedNode.java +++ /dev/null @@ -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(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 The type of the builder. - */ - public static class Builder { - protected final String path; - protected final List 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])); - } - } -} diff --git a/src/main/java/com/onarandombox/MultiverseCore/configuration/node/Node.java b/src/main/java/com/onarandombox/MultiverseCore/configuration/node/Node.java new file mode 100644 index 00000000..790b2de4 --- /dev/null +++ b/src/main/java/com/onarandombox/MultiverseCore/configuration/node/Node.java @@ -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(); +} diff --git a/src/main/java/com/onarandombox/MultiverseCore/configuration/node/NodeGroup.java b/src/main/java/com/onarandombox/MultiverseCore/configuration/node/NodeGroup.java index 810d5c2b..ffac0065 100644 --- a/src/main/java/com/onarandombox/MultiverseCore/configuration/node/NodeGroup.java +++ b/src/main/java/com/onarandombox/MultiverseCore/configuration/node/NodeGroup.java @@ -5,38 +5,38 @@ import java.util.Collection; import java.util.HashMap; import java.util.Iterator; import java.util.Map; -import java.util.Optional; import io.github.townyadvanced.commentedconfiguration.setting.CommentedNode; +import io.vavr.control.Option; import org.jetbrains.annotations.NotNull; /** * A collection of {@link CommentedNode}s, with mappings to nodes by name. */ -public class NodeGroup implements Collection { - private final Collection nodes; - private final Map nodesMap; +public class NodeGroup implements Collection { + private final Collection nodes; + private final Map nodesMap; public NodeGroup() { this.nodes = new ArrayList<>(); this.nodesMap = new HashMap<>(); } - public NodeGroup(Collection nodes) { + public NodeGroup(Collection 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().peek(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().peek(nodesMap::remove); } } @@ -52,11 +52,22 @@ public class NodeGroup implements Collection { /** * 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. + * @param name The name of the node to get. + * @return The node with the given name, or {@link Option.None} if no node with the given name exists. */ - public Optional findNode(String name) { - return Optional.ofNullable(nodesMap.get(name)); + public Option findNode(String name) { + return Option.of(nodesMap.get(name)); + } + + /** + * Gets the node with the given name. + * + * @param name The name of the node to get. + * @param type The type of the node to get. + * @return The node with the given name, or {@link Option.None} if no node with the given name exists. + */ + public Option findNode(String name, Class type) { + return Option.of(nodesMap.get(name)).map(node -> type.isAssignableFrom(node.getClass()) ? (T) node : null); } @Override @@ -76,7 +87,7 @@ public class NodeGroup implements Collection { @NotNull @Override - public Iterator iterator() { + public Iterator iterator() { return nodes.iterator(); } @@ -91,9 +102,9 @@ public class NodeGroup implements Collection { } @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 +113,7 @@ public class NodeGroup implements Collection { @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 +125,7 @@ public class NodeGroup implements Collection { } @Override - public boolean addAll(@NotNull Collection collection) { + public boolean addAll(@NotNull Collection collection) { return nodes.addAll(collection); } diff --git a/src/main/java/com/onarandombox/MultiverseCore/configuration/node/ValueNode.java b/src/main/java/com/onarandombox/MultiverseCore/configuration/node/ValueNode.java new file mode 100644 index 00000000..6ef0cf81 --- /dev/null +++ b/src/main/java/com/onarandombox/MultiverseCore/configuration/node/ValueNode.java @@ -0,0 +1,45 @@ +package com.onarandombox.MultiverseCore.configuration.node; + +import io.vavr.control.Option; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +public interface ValueNode extends Node { + + /** + * Gets the name of this node. Used for identifying the node from user input. + * + * @return An {@link Option} containing the name of this node, or {@link Option.None} if the node has no name. + */ + @NotNull Option getName(); + + /** + * Gets the class type {@link T} of the node value. + * + * @return The class type of the node value. + */ + @NotNull Class 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(@Nullable T value); + + /** + * Called when the value of this node is set. + * + * @param oldValue The old value. + * @param newValue The new value. + */ + void onSetValue(@Nullable T oldValue, @Nullable T newValue); +} diff --git a/src/main/java/com/onarandombox/MultiverseCore/utils/MVCorei18n.java b/src/main/java/com/onarandombox/MultiverseCore/utils/MVCorei18n.java index 1e5cbe35..388b619d 100644 --- a/src/main/java/com/onarandombox/MultiverseCore/utils/MVCorei18n.java +++ b/src/main/java/com/onarandombox/MultiverseCore/utils/MVCorei18n.java @@ -9,6 +9,7 @@ import org.jetbrains.annotations.NotNull; public enum MVCorei18n implements MessageKeyProvider { // config status CONFIG_SAVE_FAILED, + CONFIG_NODE_NOTFOUND, // check command CHECK_CHECKING, diff --git a/src/main/resources/multiverse-core_en.properties b/src/main/resources/multiverse-core_en.properties index 0cd452ea..31ed2ea7 100644 --- a/src/main/resources/multiverse-core_en.properties +++ b/src/main/resources/multiverse-core_en.properties @@ -1,5 +1,6 @@ # configuration mv-core.config.save.failed=Unable to save Multiverse-Core config.yml. Your changes will be temporary! +mv-core.config.node.notfound=Node not found in config: {node} # /mv check mv-core.check.description=Checks if a player can teleport to a destination. @@ -113,4 +114,3 @@ mv-core.unload.success=&aUnloaded world '{world}'! # /mv usage mv-core.usage.description=Show Multiverse-Core command usage. - diff --git a/src/test/java/org/mvplugins/multiverse/core/config/ConfigTest.kt b/src/test/java/org/mvplugins/multiverse/core/config/ConfigTest.kt index d106b727..221c6e2a 100644 --- a/src/test/java/org/mvplugins/multiverse/core/config/ConfigTest.kt +++ b/src/test/java/org/mvplugins/multiverse/core/config/ConfigTest.kt @@ -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).isSuccess) + 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").isSuccess) + 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).isSuccess) + 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) } }