From 362d871ab8dea34c902fab088c3149623fa42347 Mon Sep 17 00:00:00 2001 From: Ben Woo <30431861+benwoo1110@users.noreply.github.com> Date: Thu, 14 Sep 2023 16:48:07 +0800 Subject: [PATCH] Implement better suggestion and string parsing for config node --- .../core/commands/ModifyCommand.java | 9 +- .../commandtools/MVCommandCompletions.java | 17 +-- .../core/commandtools/MVCommandContexts.java | 54 -------- .../context/WorldConfigValue.java | 20 --- .../multiverse/core/config/MVCoreConfig.java | 4 +- .../functions/DefaultSerializerProvider.java | 56 ++++++++ .../DefaultStringParserProvider.java | 54 ++++++++ .../functions/DefaultSuggesterProvider.java | 46 +++++++ .../{node => functions}/NodeSerializer.java | 2 +- .../functions/NodeStringParser.java | 10 ++ .../functions/NodeSuggester.java | 8 ++ .../handle/GenericConfigHandle.java | 122 +++++++++++------- .../core/configuration/node/ConfigNode.java | 63 ++++++++- .../node/EnumNodeSerializer.java | 14 -- .../configuration/node/ListConfigNode.java | 75 +++++++++-- .../configuration/node/ListValueNode.java | 19 +++ .../core/configuration/node/ValueNode.java | 20 +++ .../core/world/MultiverseWorld.java | 14 +- .../core/world/config/WorldConfig.java | 23 ++-- 19 files changed, 441 insertions(+), 189 deletions(-) delete mode 100644 src/main/java/org/mvplugins/multiverse/core/commandtools/context/WorldConfigValue.java create mode 100644 src/main/java/org/mvplugins/multiverse/core/configuration/functions/DefaultSerializerProvider.java create mode 100644 src/main/java/org/mvplugins/multiverse/core/configuration/functions/DefaultStringParserProvider.java create mode 100644 src/main/java/org/mvplugins/multiverse/core/configuration/functions/DefaultSuggesterProvider.java rename src/main/java/org/mvplugins/multiverse/core/configuration/{node => functions}/NodeSerializer.java (68%) create mode 100644 src/main/java/org/mvplugins/multiverse/core/configuration/functions/NodeStringParser.java create mode 100644 src/main/java/org/mvplugins/multiverse/core/configuration/functions/NodeSuggester.java delete mode 100644 src/main/java/org/mvplugins/multiverse/core/configuration/node/EnumNodeSerializer.java diff --git a/src/main/java/org/mvplugins/multiverse/core/commands/ModifyCommand.java b/src/main/java/org/mvplugins/multiverse/core/commands/ModifyCommand.java index 6ba192cc..0f5a6bad 100644 --- a/src/main/java/org/mvplugins/multiverse/core/commands/ModifyCommand.java +++ b/src/main/java/org/mvplugins/multiverse/core/commands/ModifyCommand.java @@ -10,12 +10,12 @@ import co.aikar.commands.annotation.Syntax; import com.dumptruckman.minecraft.util.Logging; import jakarta.inject.Inject; import org.jetbrains.annotations.NotNull; +import org.jvnet.hk2.annotations.Optional; import org.jvnet.hk2.annotations.Service; import org.mvplugins.multiverse.core.commandtools.MVCommandIssuer; import org.mvplugins.multiverse.core.commandtools.MVCommandManager; import org.mvplugins.multiverse.core.commandtools.MultiverseCommand; -import org.mvplugins.multiverse.core.commandtools.context.WorldConfigValue; import org.mvplugins.multiverse.core.configuration.handle.ConfigModifyType; import org.mvplugins.multiverse.core.world.MultiverseWorld; import org.mvplugins.multiverse.core.world.WorldManager; @@ -54,19 +54,20 @@ class ModifyCommand extends MultiverseCommand { @Description("") String propertyName, + @Optional @Syntax("[value]") @Description("") - WorldConfigValue propertyValue) { + String propertyValue) { Logging.fine("ModifyCommand.onModifyCommand: world=%s, configModifyType=%s, propertyName=%s, propertyValue=%s", world, configModifyType, propertyName, propertyValue); - world.modifyProperty(configModifyType, propertyName, propertyValue.getValue()).onSuccess(ignore -> { + world.modifyProperty(configModifyType, propertyName, propertyValue).onSuccess(ignore -> { issuer.sendMessage("Property " + propertyName + " set to " + world.getProperty(propertyName).getOrNull() + " for world " + world.getName() + "."); worldManager.saveWorldsConfig(); }).onFailure(exception -> { issuer.sendMessage("Failed to " + configModifyType.name().toLowerCase() + " property " + propertyName - + " to " + propertyValue.getValue() + " for world " + world.getName() + "."); + + " to " + propertyValue + " for world " + world.getName() + "."); issuer.sendMessage(exception.getMessage()); }); } diff --git a/src/main/java/org/mvplugins/multiverse/core/commandtools/MVCommandCompletions.java b/src/main/java/org/mvplugins/multiverse/core/commandtools/MVCommandCompletions.java index 6e039e32..0328eab7 100644 --- a/src/main/java/org/mvplugins/multiverse/core/commandtools/MVCommandCompletions.java +++ b/src/main/java/org/mvplugins/multiverse/core/commandtools/MVCommandCompletions.java @@ -170,24 +170,11 @@ public class MVCommandCompletions extends PaperCommandCompletions { } private Collection suggestMVWorldPropsValue(BukkitCommandCompletionContext context) { - //noinspection unchecked return Try.of(() -> { MultiverseWorld mvWorld = context.getContextValue(MultiverseWorld.class); + ConfigModifyType modifyType = context.getContextValue(ConfigModifyType.class); String propertyName = context.getContextValue(String.class); - Class type = mvWorld.getPropertyType(propertyName).get(); - if (type.isEnum()) { - return suggestEnums(type); - } - if (type == Boolean.class) { - return List.of("true", "false"); - } - if (type == Integer.class) { - return List.of("0", "1", "2", "3", "4", "5", "6", "7", "8", "9"); - } - if (type == Double.class) { - return List.of("0.0", "1.0", "2.0", "3.0", "4.0", "5.0"); - } - return Collections.emptyList(); + return mvWorld.suggestPropertyValues(modifyType, propertyName, context.getInput()); }).getOrElse(Collections.emptyList()); } diff --git a/src/main/java/org/mvplugins/multiverse/core/commandtools/MVCommandContexts.java b/src/main/java/org/mvplugins/multiverse/core/commandtools/MVCommandContexts.java index cc3e34a4..8ccf2abb 100644 --- a/src/main/java/org/mvplugins/multiverse/core/commandtools/MVCommandContexts.java +++ b/src/main/java/org/mvplugins/multiverse/core/commandtools/MVCommandContexts.java @@ -1,9 +1,6 @@ package org.mvplugins.multiverse.core.commandtools; -import java.util.Arrays; import java.util.HashSet; -import java.util.List; -import java.util.Objects; import java.util.Set; import co.aikar.commands.BukkitCommandExecutionContext; @@ -11,10 +8,8 @@ import co.aikar.commands.BukkitCommandIssuer; import co.aikar.commands.InvalidCommandArgument; import co.aikar.commands.PaperCommandContexts; import co.aikar.commands.contexts.ContextResolver; -import com.dumptruckman.minecraft.util.Logging; import com.google.common.base.Strings; import io.vavr.control.Option; -import io.vavr.control.Try; import jakarta.inject.Inject; import org.bukkit.GameRule; import org.bukkit.entity.Player; @@ -22,9 +17,7 @@ import org.jvnet.hk2.annotations.Service; import org.mvplugins.multiverse.core.commandtools.context.GameRuleValue; import org.mvplugins.multiverse.core.commandtools.context.MVConfigValue; -import org.mvplugins.multiverse.core.commandtools.context.WorldConfigValue; import org.mvplugins.multiverse.core.config.MVCoreConfig; -import org.mvplugins.multiverse.core.configuration.handle.ConfigModifyType; import org.mvplugins.multiverse.core.configuration.node.Node; import org.mvplugins.multiverse.core.configuration.node.ValueNode; import org.mvplugins.multiverse.core.destination.DestinationsProvider; @@ -69,7 +62,6 @@ class MVCommandContexts extends PaperCommandContexts { registerIssuerAwareContext(LoadedMultiverseWorld[].class, this::parseLoadedWorldArray); registerIssuerAwareContext(Player.class, this::parsePlayer); registerIssuerAwareContext(Player[].class, this::parsePlayerArray); - registerIssuerAwareContext(WorldConfigValue.class, this::parseWorldConfigValue); } private MVCommandIssuer parseMVCommandIssuer(BukkitCommandExecutionContext context) { @@ -409,50 +401,4 @@ class MVCommandContexts extends PaperCommandContexts { } throw new InvalidCommandArgument("Player " + playerIdentifier + " not found."); } - - private WorldConfigValue parseWorldConfigValue(BukkitCommandExecutionContext context) { - MultiverseWorld mvWorld = (MultiverseWorld) context.getResolvedArg(MultiverseWorld.class); - ConfigModifyType modifyType = (ConfigModifyType) context.getResolvedArg(ConfigModifyType.class); - String propertyName = (String) context.getResolvedArg(String.class); - if (mvWorld == null || modifyType == null || propertyName == null) { - throw new InvalidCommandArgument("No world or property specified."); - } - - if (modifyType == ConfigModifyType.RESET) { - if (context.popFirstArg() != null) { - throw new InvalidCommandArgument("No value should be specified for reset."); - } - return new WorldConfigValue(null); - } - - Class type = mvWorld.getPropertyType(propertyName) - .getOrElseThrow(() -> { - return new InvalidCommandArgument("The property " + propertyName + " is not valid for world " - + mvWorld.getName() + "."); - }); - return new WorldConfigValue(parseType(context, type)); - } - - private Object parseType(BukkitCommandExecutionContext context, Class type) { - Object value = context.getFirstArg(); - if (value == null) { - throw new InvalidCommandArgument("No value specified."); - } - - // Special case for enums - if (type.isEnum()) { - return Try.of(() -> Enum.valueOf((Class) type, context.popFirstArg().toUpperCase())) - .getOrElseThrow(() -> new InvalidCommandArgument(("The value %s is not a valid %s. " - + "Valid values are: %s").formatted( - value, - type.getSimpleName(), - Arrays.stream(((Class) type).getEnumConstants()) - .map(enumValue -> enumValue.name().toLowerCase()) - .reduce((a, b) -> a + ", " + b) - .orElse("")))); - } - - ContextResolver resolver = getResolver(type); - return resolver == null ? value : resolver.getContext(context); - } } diff --git a/src/main/java/org/mvplugins/multiverse/core/commandtools/context/WorldConfigValue.java b/src/main/java/org/mvplugins/multiverse/core/commandtools/context/WorldConfigValue.java deleted file mode 100644 index 406e6110..00000000 --- a/src/main/java/org/mvplugins/multiverse/core/commandtools/context/WorldConfigValue.java +++ /dev/null @@ -1,20 +0,0 @@ -package org.mvplugins.multiverse.core.commandtools.context; - -public class WorldConfigValue { - private final Object value; - - public WorldConfigValue(Object value) { - this.value = value; - } - - public Object getValue() { - return value; - } - - @Override - public String toString() { - return "WorldConfigValue{" - + "value=" + value - + '}'; - } -} diff --git a/src/main/java/org/mvplugins/multiverse/core/config/MVCoreConfig.java b/src/main/java/org/mvplugins/multiverse/core/config/MVCoreConfig.java index 576e482d..5db93c35 100644 --- a/src/main/java/org/mvplugins/multiverse/core/config/MVCoreConfig.java +++ b/src/main/java/org/mvplugins/multiverse/core/config/MVCoreConfig.java @@ -114,12 +114,12 @@ public class MVCoreConfig implements MVConfig { @Override public Try getProperty(String name) { - return configHandle.get(name); + return configHandle.getProperty(name); } @Override public Try setProperty(String name, Object value) { - return configHandle.set(name, value); + return configHandle.setProperty(name, value); } @Override diff --git a/src/main/java/org/mvplugins/multiverse/core/configuration/functions/DefaultSerializerProvider.java b/src/main/java/org/mvplugins/multiverse/core/configuration/functions/DefaultSerializerProvider.java new file mode 100644 index 00000000..56faf6d5 --- /dev/null +++ b/src/main/java/org/mvplugins/multiverse/core/configuration/functions/DefaultSerializerProvider.java @@ -0,0 +1,56 @@ +package org.mvplugins.multiverse.core.configuration.functions; + +import java.util.HashMap; +import java.util.Map; + +import co.aikar.commands.ACFUtil; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +public final class DefaultSerializerProvider { + + private static final Map, NodeSerializer> SERIALIZERS = new HashMap<>(); + + public static void addDefaultSerializer(@NotNull Class type, @NotNull NodeSerializer serializer) { + SERIALIZERS.put(type, serializer); + } + + public static @Nullable NodeSerializer getDefaultSerializer(Class type) { + if (type.isEnum()) { + // Special case for enums + return (NodeSerializer) ENUM_SERIALIZER; + } + return (NodeSerializer) SERIALIZERS.get(type); + } + + private static final NodeSerializer ENUM_SERIALIZER = new NodeSerializer<>() { + @Override + public Enum deserialize(Object object, Class type) { + return Enum.valueOf(type, String.valueOf(object).toUpperCase()); + } + + @Override + public Object serialize(Enum object, Class type) { + return object.name(); + } + }; + + private static final NodeSerializer BOOLEAN_SERIALIZER = new NodeSerializer<>() { + @Override + public Boolean deserialize(Object object, Class type) { + if (object instanceof Boolean) { + return (Boolean) object; + } + return ACFUtil.isTruthy(String.valueOf(object)); + } + + @Override + public Object serialize(Boolean object, Class type) { + return object; + } + }; + + static { + addDefaultSerializer(Boolean.class, BOOLEAN_SERIALIZER); + } +} diff --git a/src/main/java/org/mvplugins/multiverse/core/configuration/functions/DefaultStringParserProvider.java b/src/main/java/org/mvplugins/multiverse/core/configuration/functions/DefaultStringParserProvider.java new file mode 100644 index 00000000..e1b44150 --- /dev/null +++ b/src/main/java/org/mvplugins/multiverse/core/configuration/functions/DefaultStringParserProvider.java @@ -0,0 +1,54 @@ +package org.mvplugins.multiverse.core.configuration.functions; + +import java.util.HashMap; +import java.util.Map; + +import co.aikar.commands.ACFUtil; +import io.vavr.control.Try; + +public class DefaultStringParserProvider { + + private static final Map, NodeStringParser> PARSERS = new HashMap<>(); + + public static void addDefaultStringParser(Class clazz, NodeStringParser parser) { + PARSERS.put(clazz, parser); + } + + public static NodeStringParser getDefaultStringParser(Class clazz) { + if (clazz.isEnum()) { + // Special case for enums + return (NodeStringParser) ENUM_STRING_PARSER; + } + return (NodeStringParser) PARSERS.get(clazz); + } + + private static final NodeStringParser ENUM_STRING_PARSER = (input, type) -> Try.of( + () -> Enum.valueOf(type, input.toUpperCase())); + + private static final NodeStringParser STRING_STRING_PARSER = (input, type) -> Try.of( + () -> input); + + private static final NodeStringParser BOOLEAN_STRING_PARSER = (input, type) -> Try.of( + () -> ACFUtil.isTruthy(String.valueOf(input).toLowerCase())); + + private static final NodeStringParser INTEGER_STRING_PARSER = (input, type) -> Try.of( + () -> ACFUtil.parseInt(input)); + + private static final NodeStringParser DOUBLE_STRING_PARSER = (input, type) -> Try.of( + () -> ACFUtil.parseDouble(input)); + + private static final NodeStringParser FLOAT_STRING_PARSER = (input, type) -> Try.of( + () -> ACFUtil.parseFloat(input)); + + private static final NodeStringParser LONG_STRING_PARSER = (input, type) -> Try.of( + () -> ACFUtil.parseLong(input)); + + static { + addDefaultStringParser(String.class, STRING_STRING_PARSER); + addDefaultStringParser(Boolean.class, BOOLEAN_STRING_PARSER); + addDefaultStringParser(Integer.class, INTEGER_STRING_PARSER); + addDefaultStringParser(Double.class, DOUBLE_STRING_PARSER); + addDefaultStringParser(Float.class, FLOAT_STRING_PARSER); + addDefaultStringParser(Long.class, LONG_STRING_PARSER); + } +} diff --git a/src/main/java/org/mvplugins/multiverse/core/configuration/functions/DefaultSuggesterProvider.java b/src/main/java/org/mvplugins/multiverse/core/configuration/functions/DefaultSuggesterProvider.java new file mode 100644 index 00000000..325f8ca5 --- /dev/null +++ b/src/main/java/org/mvplugins/multiverse/core/configuration/functions/DefaultSuggesterProvider.java @@ -0,0 +1,46 @@ +package org.mvplugins.multiverse.core.configuration.functions; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; +import java.util.stream.IntStream; + +import org.jetbrains.annotations.Nullable; + +public class DefaultSuggesterProvider { + + private static final Map, NodeSuggester> SUGGESTERS = new HashMap<>(); + + public static void addDefaultSuggester(Class clazz, NodeSuggester suggester) { + SUGGESTERS.put(clazz, suggester); + } + + public static @Nullable NodeSuggester getDefaultSuggester(Class clazz) { + if (clazz.isEnum()) { + // Special case for enums + return enumSuggester(clazz); + } + return SUGGESTERS.get(clazz); + } + + private static NodeSuggester enumSuggester(Class clazz) { + return input -> Arrays.stream(clazz.getEnumConstants()) + .map(Object::toString) + .map(String::toLowerCase) + .collect(Collectors.toList()); + } + + private static final NodeSuggester BOOLEAN_SUGGESTER = input -> List.of("true", "false"); + + private static final NodeSuggester INTEGER_SUGGESTER = input -> IntStream.range(1, 10) + .boxed() + .map(String::valueOf) + .collect(Collectors.toList()); + + static { + addDefaultSuggester(Boolean.class, BOOLEAN_SUGGESTER); + addDefaultSuggester(Integer.class, INTEGER_SUGGESTER); + } +} diff --git a/src/main/java/org/mvplugins/multiverse/core/configuration/node/NodeSerializer.java b/src/main/java/org/mvplugins/multiverse/core/configuration/functions/NodeSerializer.java similarity index 68% rename from src/main/java/org/mvplugins/multiverse/core/configuration/node/NodeSerializer.java rename to src/main/java/org/mvplugins/multiverse/core/configuration/functions/NodeSerializer.java index fbcaa95f..008e3b76 100644 --- a/src/main/java/org/mvplugins/multiverse/core/configuration/node/NodeSerializer.java +++ b/src/main/java/org/mvplugins/multiverse/core/configuration/functions/NodeSerializer.java @@ -1,4 +1,4 @@ -package org.mvplugins.multiverse.core.configuration.node; +package org.mvplugins.multiverse.core.configuration.functions; public interface NodeSerializer { T deserialize(Object object, Class type); diff --git a/src/main/java/org/mvplugins/multiverse/core/configuration/functions/NodeStringParser.java b/src/main/java/org/mvplugins/multiverse/core/configuration/functions/NodeStringParser.java new file mode 100644 index 00000000..c123ce24 --- /dev/null +++ b/src/main/java/org/mvplugins/multiverse/core/configuration/functions/NodeStringParser.java @@ -0,0 +1,10 @@ +package org.mvplugins.multiverse.core.configuration.functions; + +import io.vavr.control.Try; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +@FunctionalInterface +public interface NodeStringParser { + Try parse(@Nullable String string, @NotNull Class type); +} diff --git a/src/main/java/org/mvplugins/multiverse/core/configuration/functions/NodeSuggester.java b/src/main/java/org/mvplugins/multiverse/core/configuration/functions/NodeSuggester.java new file mode 100644 index 00000000..a62b6bb9 --- /dev/null +++ b/src/main/java/org/mvplugins/multiverse/core/configuration/functions/NodeSuggester.java @@ -0,0 +1,8 @@ +package org.mvplugins.multiverse.core.configuration.functions; + +import java.util.Collection; + +@FunctionalInterface +public interface NodeSuggester { + Collection suggest(String input); +} diff --git a/src/main/java/org/mvplugins/multiverse/core/configuration/handle/GenericConfigHandle.java b/src/main/java/org/mvplugins/multiverse/core/configuration/handle/GenericConfigHandle.java index 09e2d701..6e1245e4 100644 --- a/src/main/java/org/mvplugins/multiverse/core/configuration/handle/GenericConfigHandle.java +++ b/src/main/java/org/mvplugins/multiverse/core/configuration/handle/GenericConfigHandle.java @@ -1,7 +1,9 @@ package org.mvplugins.multiverse.core.configuration.handle; import java.util.Collection; +import java.util.Collections; import java.util.List; +import java.util.function.Function; import java.util.logging.Logger; import io.vavr.control.Option; @@ -71,11 +73,11 @@ public abstract class GenericConfigHandle { }); } - public Collection getNames() { + public Collection getPropertyNames() { return nodes.getNames(); } - public Collection getNamesThatSupports(ConfigModifyType configModifyType) { + public Collection getPropertyNames(ConfigModifyType configModifyType) { return switch (configModifyType) { case SET, RESET -> nodes.getNames(); case ADD, REMOVE -> nodes.stream() @@ -87,7 +89,7 @@ public abstract class GenericConfigHandle { }; } - public Try getTypeByName(@Nullable String name) { + public Try getPropertyType(@Nullable String name) { return nodes.findNode(name, ValueNode.class) .map(valueNode -> { if (valueNode instanceof ListValueNode listValueNode) { @@ -98,22 +100,46 @@ public abstract class GenericConfigHandle { .toTry(() -> new ConfigNodeNotFoundException(name)); } + public Collection suggestPropertyValues( + @NotNull ConfigModifyType type, @Nullable String name, @Nullable String input) { + return switch (type) { + case RESET -> Collections.emptyList(); + case SET -> nodes.findNode(name, ValueNode.class) + .map(node -> node.suggest(input)) + .getOrElse(Collections.emptyList()); + case ADD -> nodes.findNode(name, ListValueNode.class) + .map(node -> node.suggestItem(input)) + .getOrElse(Collections.emptyList()); + case REMOVE -> nodes.findNode(name, ListValueNode.class) + .toTry() + .map(node -> Option.of(get((ValueNode) node)) + .map(list -> list.stream().map(Object::toString).toList()) + .getOrElse(Collections.emptyList())) + .getOrElse(Collections.emptyList()); + default -> Collections.emptyList(); + }; + } + /** * 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) { + public Try getProperty(@Nullable String name) { return nodes.findNode(name, ValueNode.class) .toTry(() -> new ConfigNodeNotFoundException(name)) .map(node -> get((ValueNode) node)); } - public Try modify(@NotNull ConfigModifyType type, @Nullable String name, @Nullable Object value) { - return nodes.findNode(name, ValueNode.class) - .toTry(() -> new ConfigNodeNotFoundException(name)) - .flatMapTry(node -> modify(type, node, value)); + public Try modifyProperty(@NotNull ConfigModifyType type, @Nullable String name, @Nullable String value) { + return switch (type) { + case SET -> setProperty(name, value); + case RESET -> resetProperty(name); + case ADD -> addProperty(name, value); + case REMOVE -> removeProperty(name, value); + default -> Try.failure(new IllegalArgumentException("Unknown config modify type: " + type)); + }; } /** @@ -123,31 +149,57 @@ public abstract class GenericConfigHandle { * @param value The value to set. * @return True if the value was set, false otherwise. */ - public Try set(@Nullable String name, Object value) { + public Try setProperty(@Nullable String name, @Nullable String value) { //noinspection unchecked - return nodes.findNode(name, ValueNode.class) - .toTry(() -> new ConfigNodeNotFoundException(name)) - .flatMapTry(node -> set(node, value)); + return propertyAction(name, ValueNode.class, node -> + node.parseFromString(value).flatMapTry(parsedValue -> set(node, parsedValue))); } - public Try add(@Nullable String name, Object value) { + public Try addProperty(@Nullable String name, @Nullable String value) { //noinspection unchecked - return nodes.findNode(name, ListValueNode.class) - .toTry(() -> new ConfigNodeNotFoundException(name)) - .flatMapTry(node -> add(node, value)); + return propertyAction(name, ListValueNode.class, node -> + node.parseItemFromString(value).flatMapTry(item -> add(node, item))); } - public Try remove(@Nullable String name, Object value) { + public Try removeProperty(@Nullable String name, @Nullable String value) { //noinspection unchecked - return nodes.findNode(name, ListValueNode.class) - .toTry(() -> new ConfigNodeNotFoundException(name)) - .flatMapTry(node -> remove(node, value)); + return propertyAction(name, ListValueNode.class, node -> + node.parseItemFromString(value).flatMapTry(item -> remove(node, item))); } - public Try reset(@Nullable String name) { - return nodes.findNode(name, ValueNode.class) + public Try resetProperty(@Nullable String name) { + return propertyAction(name, ValueNode.class, this::reset); + } + + /** + * 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 setProperty(@Nullable String name, @Nullable Object value) { + //noinspection unchecked + return propertyAction(name, ValueNode.class, node -> set(node, value)); + } + + public Try addProperty(@Nullable String name, @Nullable Object value) { + //noinspection unchecked + return propertyAction(name, ListValueNode.class, node -> add(node, value)); + } + + public Try removeProperty(@Nullable String name, @Nullable Object value) { + //noinspection unchecked + return propertyAction(name, ListValueNode.class, node -> remove(node, value)); + } + + private > Try propertyAction( + @Nullable String name, + @NotNull Class nodeClass, + @NotNull Function> action) { + return nodes.findNode(name, nodeClass) .toTry(() -> new ConfigNodeNotFoundException(name)) - .flatMapTry(this::reset); + .flatMapTry(action::apply); } /** @@ -164,30 +216,6 @@ public abstract class GenericConfigHandle { return node.getSerializer().deserialize(config.get(node.getPath(), node.getDefaultValue()), node.getType()); } - public Try modify(@NotNull ConfigModifyType type, @NotNull ValueNode node, @Nullable Object value) { - return switch (type) { - case SET -> set(node, value); - case REMOVE -> { - if (!(node instanceof ListValueNode listNode)) { - yield Try.failure(new IllegalArgumentException("Node is not a list")); - } - yield remove(listNode, value); - } - case ADD -> { - if (!(node instanceof ListValueNode listNode)) { - yield Try.failure(new IllegalArgumentException("Node is not a list")); - } - yield add(listNode, value); - } - case RESET -> { - if (value != null) { - yield Try.failure(new IllegalArgumentException("Reset type cannot have a value")); - } - yield reset(node); - } - }; - } - /** * Sets the value of a node, if the validator is not null, it will be tested first. * diff --git a/src/main/java/org/mvplugins/multiverse/core/configuration/node/ConfigNode.java b/src/main/java/org/mvplugins/multiverse/core/configuration/node/ConfigNode.java index 24fac719..a7687f6c 100644 --- a/src/main/java/org/mvplugins/multiverse/core/configuration/node/ConfigNode.java +++ b/src/main/java/org/mvplugins/multiverse/core/configuration/node/ConfigNode.java @@ -1,5 +1,7 @@ package org.mvplugins.multiverse.core.configuration.node; +import java.util.Collection; +import java.util.Collections; import java.util.function.BiConsumer; import java.util.function.Function; import java.util.function.Supplier; @@ -9,6 +11,13 @@ import io.vavr.control.Try; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import org.mvplugins.multiverse.core.configuration.functions.DefaultSerializerProvider; +import org.mvplugins.multiverse.core.configuration.functions.DefaultStringParserProvider; +import org.mvplugins.multiverse.core.configuration.functions.DefaultSuggesterProvider; +import org.mvplugins.multiverse.core.configuration.functions.NodeSerializer; +import org.mvplugins.multiverse.core.configuration.functions.NodeStringParser; +import org.mvplugins.multiverse.core.configuration.functions.NodeSuggester; + /** * A node that contains a value. * @@ -33,6 +42,8 @@ public class ConfigNode extends ConfigHeaderNode implements ValueNode { protected final @Nullable String name; protected final @NotNull Class type; protected final @Nullable Supplier defaultValueSupplier; + protected final @Nullable NodeSuggester suggester; + protected final @Nullable NodeStringParser stringParser; protected final @Nullable NodeSerializer serializer; protected final @Nullable Function> validator; protected final @Nullable BiConsumer onSetValue; @@ -43,6 +54,8 @@ public class ConfigNode extends ConfigHeaderNode implements ValueNode { @Nullable String name, @NotNull Class type, @Nullable Supplier defaultValueSupplier, + @Nullable NodeSuggester suggester, + @Nullable NodeStringParser stringParser, @Nullable NodeSerializer serializer, @Nullable Function> validator, @Nullable BiConsumer onSetValue) { @@ -50,7 +63,15 @@ public class ConfigNode extends ConfigHeaderNode implements ValueNode { this.name = name; this.type = type; this.defaultValueSupplier = defaultValueSupplier; - this.serializer = serializer; + this.suggester = suggester != null + ? suggester + : DefaultSuggesterProvider.getDefaultSuggester(type); + this.stringParser = stringParser != null + ? stringParser + : DefaultStringParserProvider.getDefaultStringParser(type); + this.serializer = serializer != null + ? serializer + : DefaultSerializerProvider.getDefaultSerializer(type); this.validator = validator; this.onSetValue = onSetValue; } @@ -79,6 +100,28 @@ public class ConfigNode extends ConfigHeaderNode implements ValueNode { return defaultValueSupplier != null ? defaultValueSupplier.get() : null; } + /** + * {@inheritDoc} + */ + @Override + public @NotNull Collection suggest(@Nullable String input) { + if (suggester != null) { + return suggester.suggest(input); + } + return Collections.emptyList(); + } + + /** + * {@inheritDoc} + */ + @Override + public @NotNull Try parseFromString(@Nullable String input) { + if (stringParser != null) { + return stringParser.parse(input, type); + } + return Try.failure(new UnsupportedOperationException("No string parser for type " + type.getName())); + } + public @Nullable NodeSerializer getSerializer() { return serializer; } @@ -111,11 +154,12 @@ public class ConfigNode extends ConfigHeaderNode implements ValueNode { * @param The type of the builder. */ public static class Builder> extends ConfigHeaderNode.Builder { - private static final NodeSerializer ENUM_NODE_SERIALIZER = new EnumNodeSerializer<>(); protected @Nullable String name; protected @NotNull final Class type; protected @Nullable Supplier defaultValueSupplier; + protected @Nullable NodeSuggester suggester; + protected @Nullable NodeStringParser stringParser; protected @Nullable NodeSerializer serializer; protected @Nullable Function> validator; protected @Nullable BiConsumer onSetValue; @@ -130,9 +174,6 @@ public class ConfigNode extends ConfigHeaderNode implements ValueNode { super(path); this.name = path; this.type = type; - if (type.isEnum()) { - this.serializer = (NodeSerializer) ENUM_NODE_SERIALIZER; - } } /** @@ -168,6 +209,16 @@ public class ConfigNode extends ConfigHeaderNode implements ValueNode { return self(); } + public @NotNull B suggester(@NotNull NodeSuggester suggester) { + this.suggester = suggester; + return self(); + } + + public @NotNull B stringParser(@NotNull NodeStringParser stringParser) { + this.stringParser = stringParser; + return self(); + } + public @NotNull B serializer(@NotNull NodeSerializer serializer) { this.serializer = serializer; return self(); @@ -200,6 +251,8 @@ public class ConfigNode extends ConfigHeaderNode implements ValueNode { name, type, defaultValueSupplier, + suggester, + stringParser, serializer, validator, onSetValue); diff --git a/src/main/java/org/mvplugins/multiverse/core/configuration/node/EnumNodeSerializer.java b/src/main/java/org/mvplugins/multiverse/core/configuration/node/EnumNodeSerializer.java deleted file mode 100644 index 9e157412..00000000 --- a/src/main/java/org/mvplugins/multiverse/core/configuration/node/EnumNodeSerializer.java +++ /dev/null @@ -1,14 +0,0 @@ -package org.mvplugins.multiverse.core.configuration.node; - -public class EnumNodeSerializer> implements NodeSerializer { - - @Override - public T deserialize(Object object, Class type) { - return Enum.valueOf(type, object.toString().toUpperCase()); - } - - @Override - public Object serialize(T object, Class type) { - return object.toString(); - } -} diff --git a/src/main/java/org/mvplugins/multiverse/core/configuration/node/ListConfigNode.java b/src/main/java/org/mvplugins/multiverse/core/configuration/node/ListConfigNode.java index 6354d0a4..27b5e11b 100644 --- a/src/main/java/org/mvplugins/multiverse/core/configuration/node/ListConfigNode.java +++ b/src/main/java/org/mvplugins/multiverse/core/configuration/node/ListConfigNode.java @@ -1,6 +1,8 @@ package org.mvplugins.multiverse.core.configuration.node; import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; import java.util.List; import java.util.function.BiConsumer; import java.util.function.Function; @@ -10,6 +12,12 @@ import io.vavr.control.Try; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import org.mvplugins.multiverse.core.configuration.functions.DefaultStringParserProvider; +import org.mvplugins.multiverse.core.configuration.functions.DefaultSuggesterProvider; +import org.mvplugins.multiverse.core.configuration.functions.NodeSerializer; +import org.mvplugins.multiverse.core.configuration.functions.NodeStringParser; +import org.mvplugins.multiverse.core.configuration.functions.NodeSuggester; + public class ListConfigNode extends ConfigNode> implements ListValueNode { /** @@ -27,6 +35,8 @@ public class ListConfigNode extends ConfigNode> implements ListValueN } protected final Class itemType; + protected final NodeSuggester itemSuggester; + protected final NodeStringParser itemStringParser; protected final NodeSerializer itemSerializer; protected final Function> itemValidator; protected final BiConsumer onSetItemValue; @@ -37,15 +47,26 @@ public class ListConfigNode extends ConfigNode> implements ListValueN @Nullable String name, @NotNull Class> type, @Nullable Supplier> defaultValueSupplier, + @Nullable NodeSuggester suggester, + @Nullable NodeStringParser> stringParser, @Nullable NodeSerializer> serializer, @Nullable Function, Try> validator, @Nullable BiConsumer, List> onSetValue, @NotNull Class itemType, + @Nullable NodeSuggester itemSuggester, + @Nullable NodeStringParser itemStringParser, @Nullable NodeSerializer itemSerializer, @Nullable Function> itemValidator, @Nullable BiConsumer onSetItemValue) { - super(path, comments, name, type, defaultValueSupplier, serializer, validator, onSetValue); + super(path, comments, name, type, defaultValueSupplier, suggester, stringParser, serializer, + validator, onSetValue); this.itemType = itemType; + this.itemSuggester = itemSuggester != null + ? itemSuggester + : DefaultSuggesterProvider.getDefaultSuggester(itemType); + this.itemStringParser = itemStringParser != null + ? itemStringParser + : DefaultStringParserProvider.getDefaultStringParser(itemType); this.itemSerializer = itemSerializer; this.itemValidator = itemValidator; this.onSetItemValue = onSetItemValue; @@ -59,6 +80,22 @@ public class ListConfigNode extends ConfigNode> implements ListValueN return itemType; } + @Override + public @NotNull Collection suggestItem(@Nullable String input) { + if (itemSuggester != null) { + return itemSuggester.suggest(input); + } + return Collections.emptyList(); + } + + @Override + public @NotNull Try parseItemFromString(@Nullable String input) { + if (itemStringParser != null) { + return itemStringParser.parse(input, itemType); + } + return Try.failure(new UnsupportedOperationException("No item string parser for type " + itemType)); + } + /** * {@inheritDoc} */ @@ -91,9 +128,11 @@ public class ListConfigNode extends ConfigNode> implements ListValueN public static class Builder> extends ConfigNode.Builder, B> { protected final @NotNull Class itemType; - protected NodeSerializer itemSerializer; - protected Function> itemValidator; - protected BiConsumer onSetItemValue; + protected @Nullable NodeSuggester itemSuggester; + protected @Nullable NodeStringParser itemStringParser; + protected @Nullable NodeSerializer itemSerializer; + protected @Nullable Function> itemValidator; + protected @Nullable BiConsumer onSetItemValue; /** * Creates a new builder. @@ -108,13 +147,23 @@ public class ListConfigNode extends ConfigNode> implements ListValueN this.defaultValueSupplier = () -> (List) new ArrayList(); } + public @NotNull B itemSuggester(@NotNull NodeSuggester itemSuggester) { + this.itemSuggester = itemSuggester; + return self(); + } + + public @NotNull B itemStringParser(@NotNull NodeStringParser itemStringParser) { + this.itemStringParser = itemStringParser; + return self(); + } + /** * Sets the serializer for the node. * * @param serializer The serializer for the node. * @return This builder. */ - public @NotNull B itemSerializer(@Nullable NodeSerializer serializer) { + public @NotNull B itemSerializer(@NotNull NodeSerializer serializer) { this.itemSerializer = serializer; return self(); } @@ -122,11 +171,11 @@ public class ListConfigNode extends ConfigNode> implements ListValueN /** * Sets the validator for the node. * - * @param validator The validator for the node. + * @param itemValidator The validator for the node. * @return This builder. */ - public @NotNull B itemValidator(@Nullable Function> validator) { - this.itemValidator = validator; + public @NotNull B itemValidator(@NotNull Function> itemValidator) { + this.itemValidator = itemValidator; if (validator == null) { setDefaultValidator(); } @@ -145,11 +194,11 @@ public class ListConfigNode extends ConfigNode> implements ListValueN /** * Sets the onSetValue for the node. * - * @param onSetValue The onSetValue for the node. + * @param onSetItemValue The onSetValue for the node. * @return This builder. */ - public @NotNull B onSetItemValue(@Nullable BiConsumer onSetValue) { - this.onSetItemValue = onSetValue; + public @NotNull B onSetItemValue(@Nullable BiConsumer onSetItemValue) { + this.onSetItemValue = onSetItemValue; if (onSetValue == null) { setDefaultOnSetValue(); } @@ -178,11 +227,13 @@ public class ListConfigNode extends ConfigNode> implements ListValueN name, type, defaultValueSupplier, + suggester, + stringParser, serializer, validator, onSetValue, itemType, - itemSerializer, + itemSuggester, itemStringParser, itemSerializer, itemValidator, onSetItemValue); } diff --git a/src/main/java/org/mvplugins/multiverse/core/configuration/node/ListValueNode.java b/src/main/java/org/mvplugins/multiverse/core/configuration/node/ListValueNode.java index 65823483..5b716dce 100644 --- a/src/main/java/org/mvplugins/multiverse/core/configuration/node/ListValueNode.java +++ b/src/main/java/org/mvplugins/multiverse/core/configuration/node/ListValueNode.java @@ -1,11 +1,14 @@ package org.mvplugins.multiverse.core.configuration.node; +import java.util.Collection; import java.util.List; import io.vavr.control.Try; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import org.mvplugins.multiverse.core.configuration.functions.NodeSerializer; + public interface ListValueNode extends ValueNode> { /** @@ -15,6 +18,22 @@ public interface ListValueNode extends ValueNode> { */ @NotNull Class getItemType(); + /** + * Suggests possible string values for this node. + * + * @param input The input string. + * @return A collection of possible string values. + */ + @NotNull Collection suggestItem(@Nullable String input); + + /** + * Parses the given string into a value of type {@link I}. Used for property set by user input. + * + * @param input The string to parse. + * @return The parsed value, or given exception if parsing failed. + */ + @NotNull Try parseItemFromString(@Nullable String input); + /** * Gets the serializer for this node. * diff --git a/src/main/java/org/mvplugins/multiverse/core/configuration/node/ValueNode.java b/src/main/java/org/mvplugins/multiverse/core/configuration/node/ValueNode.java index d7c2a105..7febbe54 100644 --- a/src/main/java/org/mvplugins/multiverse/core/configuration/node/ValueNode.java +++ b/src/main/java/org/mvplugins/multiverse/core/configuration/node/ValueNode.java @@ -1,10 +1,14 @@ package org.mvplugins.multiverse.core.configuration.node; +import java.util.Collection; + import io.vavr.control.Option; import io.vavr.control.Try; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import org.mvplugins.multiverse.core.configuration.functions.NodeSerializer; + public interface ValueNode extends Node { /** @@ -28,6 +32,22 @@ public interface ValueNode extends Node { */ @Nullable T getDefaultValue(); + /** + * Suggests possible string values for this node. + * + * @param input The input string. + * @return A collection of possible string values. + */ + @NotNull Collection suggest(@Nullable String input); + + /** + * Parses the given string into a value of type {@link T}. Used for property set by user input. + * + * @param input The string to parse. + * @return The parsed value, or given exception if parsing failed. + */ + @NotNull Try parseFromString(@Nullable String input); + /** * Gets the serializer for this node. * diff --git a/src/main/java/org/mvplugins/multiverse/core/world/MultiverseWorld.java b/src/main/java/org/mvplugins/multiverse/core/world/MultiverseWorld.java index 5f533683..f3dfe71d 100644 --- a/src/main/java/org/mvplugins/multiverse/core/world/MultiverseWorld.java +++ b/src/main/java/org/mvplugins/multiverse/core/world/MultiverseWorld.java @@ -11,6 +11,7 @@ import org.bukkit.GameMode; import org.bukkit.Location; import org.bukkit.Material; import org.bukkit.World; +import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.mvplugins.multiverse.core.configuration.handle.ConfigModifyType; @@ -71,8 +72,9 @@ public class MultiverseWorld { return worldConfig.getConfigurablePropertyNames(configModifyType); } - public Try getPropertyType(String name) { - return worldConfig.getPropertyType(name); + public Collection suggestPropertyValues( + @NotNull ConfigModifyType type, @Nullable String name, @Nullable String input) { + return worldConfig.suggestPropertyValues(type, name, input); } /** @@ -85,6 +87,10 @@ public class MultiverseWorld { return worldConfig.getProperty(name); } + public Try modifyProperty(ConfigModifyType type, String name, String value) { + return worldConfig.modifyProperty(type, name, value); + } + /** * Sets a property on this world. * @@ -96,10 +102,6 @@ public class MultiverseWorld { return worldConfig.setProperty(name, value); } - public Try modifyProperty(ConfigModifyType type, String name, Object value) { - return worldConfig.modifyProperty(type, name, value); - } - /** * Gets whether or not Multiverse should auto-adjust the spawn for this world. * diff --git a/src/main/java/org/mvplugins/multiverse/core/world/config/WorldConfig.java b/src/main/java/org/mvplugins/multiverse/core/world/config/WorldConfig.java index e1a5e383..8a6c0924 100644 --- a/src/main/java/org/mvplugins/multiverse/core/world/config/WorldConfig.java +++ b/src/main/java/org/mvplugins/multiverse/core/world/config/WorldConfig.java @@ -116,27 +116,32 @@ public final class WorldConfig { } public Collection getConfigurablePropertyNames() { - return configHandle.getNames(); + return configHandle.getPropertyNames(); } public Collection getConfigurablePropertyNames(ConfigModifyType configModifyType) { - return configHandle.getNamesThatSupports(configModifyType); + return configHandle.getPropertyNames(configModifyType); } public Try getPropertyType(String name) { - return configHandle.getTypeByName(name); + return configHandle.getPropertyType(name); + } + + public Collection suggestPropertyValues( + @NotNull ConfigModifyType type, @Nullable String name, @Nullable String input) { + return configHandle.suggestPropertyValues(type, name, input); } public Try getProperty(String name) { - return configHandle.get(name); + return configHandle.getProperty(name); + } + + public Try modifyProperty(ConfigModifyType type, String name, String value) { + return configHandle.modifyProperty(type, name, value); } public Try setProperty(String name, Object value) { - return configHandle.set(name, value); - } - - public Try modifyProperty(ConfigModifyType type, String name, Object value) { - return configHandle.modify(type, name, value); + return configHandle.setProperty(name, value); } public boolean getAdjustSpawn() {