mirror of
https://github.com/Multiverse/Multiverse-Core.git
synced 2025-01-09 17:57:36 +01:00
Implement autocomplete and string parsing for config handling
This commit is contained in:
parent
6611ec4457
commit
6fe87fbc7b
@ -1,5 +1,7 @@
|
||||
package org.mvplugins.multiverse.core.api;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
import io.vavr.control.Try;
|
||||
import org.jvnet.hk2.annotations.Contract;
|
||||
|
||||
@ -33,6 +35,15 @@ public interface MVConfig {
|
||||
*/
|
||||
NodeGroup getNodes();
|
||||
|
||||
/**
|
||||
* Auto-complete suggestions for a property.
|
||||
*
|
||||
* @param name The name of the node.
|
||||
* @param input The current user input.
|
||||
* @return A collection of possible string values.
|
||||
*/
|
||||
Collection<String> suggestPropertyValues(String name, String input);
|
||||
|
||||
/**
|
||||
* Gets a property from the config.
|
||||
*
|
||||
@ -48,10 +59,20 @@ public interface MVConfig {
|
||||
* @param name The name of the property.
|
||||
* @param value The value of the property.
|
||||
* @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.
|
||||
* exception explaining why the property could not be set.
|
||||
*/
|
||||
Try<Void> setProperty(String name, Object value);
|
||||
|
||||
/**
|
||||
* Sets a string property in the config.
|
||||
*
|
||||
* @param name The name of the property.
|
||||
* @param value The string value of the property.
|
||||
* @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.
|
||||
*/
|
||||
Try<Void> setPropertyString(String name, String value);
|
||||
|
||||
/**
|
||||
* Sets world access permissions should be enforced.
|
||||
* @param enforceAccess The new value.
|
||||
|
@ -15,7 +15,6 @@ 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.MVConfigValue;
|
||||
import org.mvplugins.multiverse.core.config.MVCoreConfig;
|
||||
import org.mvplugins.multiverse.core.exceptions.MultiverseException;
|
||||
|
||||
@ -33,25 +32,25 @@ class ConfigCommand extends MultiverseCommand {
|
||||
|
||||
@Subcommand("config")
|
||||
@CommandPermission("multiverse.core.config")
|
||||
@CommandCompletion("@mvconfigs")
|
||||
@Syntax("<name> [new-value]")
|
||||
@Description("") // TODO: Description
|
||||
@CommandCompletion("@mvconfigs @mvconfigvalues")
|
||||
@Syntax("<name> [value]")
|
||||
@Description("Show or set a config value.")
|
||||
void onConfigCommand(
|
||||
MVCommandIssuer issuer,
|
||||
|
||||
@Syntax("<name>")
|
||||
@Description("") // TODO: Description
|
||||
@Description("The name of the config to set or show.")
|
||||
String name,
|
||||
|
||||
@Optional
|
||||
@Syntax("[new-value]")
|
||||
@Description("") // TODO: Description
|
||||
MVConfigValue value) {
|
||||
@Syntax("[value]")
|
||||
@Description("The value to set the config to. If not specified, the current value will be shown.")
|
||||
String value) {
|
||||
if (value == null) {
|
||||
showConfigValue(issuer, name);
|
||||
return;
|
||||
}
|
||||
updateConfigValue(issuer, name, value.getValue());
|
||||
updateConfigValue(issuer, name, value);
|
||||
}
|
||||
|
||||
private void showConfigValue(MVCommandIssuer issuer, String name) {
|
||||
@ -60,8 +59,8 @@ class ConfigCommand extends MultiverseCommand {
|
||||
.onFailure(e -> issuer.sendMessage(e.getMessage()));
|
||||
}
|
||||
|
||||
private void updateConfigValue(MVCommandIssuer issuer, String name, Object value) {
|
||||
config.setProperty(name, value)
|
||||
private void updateConfigValue(MVCommandIssuer issuer, String name, String value) {
|
||||
config.setPropertyString(name, value)
|
||||
.onSuccess(ignore -> {
|
||||
config.save();
|
||||
issuer.sendMessage("Successfully set " + name + " to " + value);
|
||||
|
@ -39,6 +39,7 @@ class MVCommandCompletions extends PaperCommandCompletions {
|
||||
private final MVCommandManager commandManager;
|
||||
private final WorldManager worldManager;
|
||||
private final DestinationsProvider destinationsProvider;
|
||||
private final MVCoreConfig config;
|
||||
|
||||
@Inject
|
||||
MVCommandCompletions(
|
||||
@ -50,6 +51,7 @@ class MVCommandCompletions extends PaperCommandCompletions {
|
||||
this.commandManager = mvCommandManager;
|
||||
this.worldManager = worldManager;
|
||||
this.destinationsProvider = destinationsProvider;
|
||||
this.config = config;
|
||||
|
||||
registerAsyncCompletion("commands", this::suggestCommands);
|
||||
registerAsyncCompletion("destinations", this::suggestDestinations);
|
||||
@ -59,6 +61,7 @@ class MVCommandCompletions extends PaperCommandCompletions {
|
||||
registerStaticCompletion("gamemodes", suggestEnums(GameMode.class));
|
||||
registerStaticCompletion("gamerules", this::suggestGamerules);
|
||||
registerStaticCompletion("mvconfigs", config.getNodes().getNames());
|
||||
registerAsyncCompletion("mvconfigvalues", this::suggestMVConfigValues);
|
||||
registerAsyncCompletion("mvworlds", this::suggestMVWorlds);
|
||||
|
||||
setDefaultCompletion("destinations", ParsedDestination.class);
|
||||
@ -118,6 +121,12 @@ class MVCommandCompletions extends PaperCommandCompletions {
|
||||
return Arrays.stream(GameRule.values()).map(GameRule::getName).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
private Collection<String> suggestMVConfigValues(BukkitCommandCompletionContext context) {
|
||||
return Try.of(() -> context.getContextValue(String.class))
|
||||
.map(propertyName -> config.suggestPropertyValues(propertyName, context.getInput()))
|
||||
.getOrElse(Collections.emptyList());
|
||||
}
|
||||
|
||||
private Collection<String> suggestMVWorlds(BukkitCommandCompletionContext context) {
|
||||
if (context.hasConfig("playerOnly") && !context.getIssuer().isPlayer()) {
|
||||
return Collections.emptyList();
|
||||
|
@ -9,17 +9,13 @@ import co.aikar.commands.InvalidCommandArgument;
|
||||
import co.aikar.commands.PaperCommandContexts;
|
||||
import co.aikar.commands.contexts.ContextResolver;
|
||||
import com.google.common.base.Strings;
|
||||
import io.vavr.control.Option;
|
||||
import jakarta.inject.Inject;
|
||||
import org.bukkit.GameRule;
|
||||
import org.bukkit.entity.Player;
|
||||
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.config.MVCoreConfig;
|
||||
import org.mvplugins.multiverse.core.configuration.node.Node;
|
||||
import org.mvplugins.multiverse.core.configuration.node.ValueNode;
|
||||
import org.mvplugins.multiverse.core.destination.DestinationsProvider;
|
||||
import org.mvplugins.multiverse.core.destination.ParsedDestination;
|
||||
import org.mvplugins.multiverse.core.display.filters.ContentFilter;
|
||||
@ -59,7 +55,6 @@ class MVCommandContexts extends PaperCommandContexts {
|
||||
registerIssuerAwareContext(LoadedMultiverseWorld.class, this::parseLoadedMultiverseWorld);
|
||||
registerIssuerAwareContext(LoadedMultiverseWorld[].class, this::parseLoadedMultiverseWorldArray);
|
||||
registerIssuerAwareContext(MultiverseWorld.class, this::parseMultiverseWorld);
|
||||
registerContext(MVConfigValue.class, this::parseMVConfigValue);
|
||||
registerIssuerAwareContext(Player.class, this::parsePlayer);
|
||||
registerIssuerAwareContext(Player[].class, this::parsePlayerArray);
|
||||
}
|
||||
@ -276,39 +271,6 @@ class MVCommandContexts extends PaperCommandContexts {
|
||||
throw new InvalidCommandArgument("World " + worldName + " is not a loaded multiverse world.");
|
||||
}
|
||||
|
||||
private MVConfigValue parseMVConfigValue(BukkitCommandExecutionContext context) {
|
||||
String configName = (String) context.getResolvedArg(String.class);
|
||||
if (Strings.isNullOrEmpty(configName)) {
|
||||
throw new InvalidCommandArgument("No config name specified.");
|
||||
}
|
||||
Option<Node> node = config.getNodes().findNode(configName);
|
||||
if (node.isEmpty()) {
|
||||
throw new InvalidCommandArgument("The config " + configName + " is not valid.");
|
||||
}
|
||||
|
||||
String valueString = context.getFirstArg();
|
||||
if (Strings.isNullOrEmpty(valueString)) {
|
||||
throw new InvalidCommandArgument("No config value specified.");
|
||||
}
|
||||
|
||||
if (!(node.get() instanceof ValueNode)) {
|
||||
context.popFirstArg();
|
||||
return new MVConfigValue(valueString);
|
||||
}
|
||||
|
||||
ContextResolver<?, BukkitCommandExecutionContext> resolver = getResolver(((ValueNode<?>) node.get()).getType());
|
||||
if (resolver == null) {
|
||||
context.popFirstArg();
|
||||
return new MVConfigValue(valueString);
|
||||
}
|
||||
|
||||
Object resolvedValue = resolver.getContext(context);
|
||||
if (resolvedValue == null) {
|
||||
throw new InvalidCommandArgument("The config value " + valueString + " is not valid for config " + configName + ".");
|
||||
}
|
||||
return new MVConfigValue(resolvedValue);
|
||||
}
|
||||
|
||||
private Player parsePlayer(BukkitCommandExecutionContext context) {
|
||||
String resolve = context.getFlagValue("resolve", "");
|
||||
|
||||
|
@ -1,13 +0,0 @@
|
||||
package org.mvplugins.multiverse.core.commandtools.context;
|
||||
|
||||
public class MVConfigValue {
|
||||
private final Object value;
|
||||
|
||||
public MVConfigValue(Object value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public Object getValue() {
|
||||
return value;
|
||||
}
|
||||
}
|
@ -3,6 +3,7 @@ package org.mvplugins.multiverse.core.config;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Collection;
|
||||
import java.util.Objects;
|
||||
|
||||
import com.dumptruckman.minecraft.util.Logging;
|
||||
@ -112,14 +113,24 @@ public class MVCoreConfig implements MVConfig {
|
||||
return configNodes.getNodes();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<String> suggestPropertyValues(String name, String input) {
|
||||
return configHandle.suggestPropertyValues(name, input);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Try<Object> getProperty(String name) {
|
||||
return configHandle.get(name);
|
||||
return configHandle.getProperty(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Try<Void> setProperty(String name, Object value) {
|
||||
return configHandle.set(name, value);
|
||||
return configHandle.setProperty(name, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Try<Void> setPropertyString(String name, String value) {
|
||||
return configHandle.setPropertyString(name, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -0,0 +1,77 @@
|
||||
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;
|
||||
|
||||
/**
|
||||
* Provides default serializers for common types.
|
||||
*/
|
||||
public final class DefaultSerializerProvider {
|
||||
|
||||
private static final Map<Class<?>, NodeSerializer<?>> SERIALIZERS = new HashMap<>();
|
||||
|
||||
/**
|
||||
* Adds a default serializer for the given type.
|
||||
*
|
||||
* @param type The type.
|
||||
* @param serializer The serializer.
|
||||
* @param <T> The type.
|
||||
*/
|
||||
public static <T> void addDefaultSerializer(@NotNull Class<T> type, @NotNull NodeSerializer<T> serializer) {
|
||||
SERIALIZERS.put(type, serializer);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the default serializer for the given type.
|
||||
*
|
||||
* @param type The type.
|
||||
* @param <T> The type.
|
||||
* @return The default serializer for the given type, or null if no default serializer exists.
|
||||
*/
|
||||
public static <T> @Nullable NodeSerializer<T> getDefaultSerializer(Class<T> type) {
|
||||
if (type.isEnum()) {
|
||||
// Special case for enums
|
||||
return (NodeSerializer<T>) ENUM_SERIALIZER;
|
||||
}
|
||||
return (NodeSerializer<T>) SERIALIZERS.get(type);
|
||||
}
|
||||
|
||||
private static final NodeSerializer<Enum> ENUM_SERIALIZER = new NodeSerializer<>() {
|
||||
@Override
|
||||
public Enum<?> deserialize(Object object, Class<Enum> type) {
|
||||
return Enum.valueOf(type, String.valueOf(object).toUpperCase());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object serialize(Enum object, Class<Enum> type) {
|
||||
return object.name();
|
||||
}
|
||||
};
|
||||
|
||||
private static final NodeSerializer<Boolean> BOOLEAN_SERIALIZER = new NodeSerializer<>() {
|
||||
@Override
|
||||
public Boolean deserialize(Object object, Class<Boolean> type) {
|
||||
if (object instanceof Boolean) {
|
||||
return (Boolean) object;
|
||||
}
|
||||
return ACFUtil.isTruthy(String.valueOf(object));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object serialize(Boolean object, Class<Boolean> type) {
|
||||
return object;
|
||||
}
|
||||
};
|
||||
|
||||
static {
|
||||
addDefaultSerializer(Boolean.class, BOOLEAN_SERIALIZER);
|
||||
}
|
||||
|
||||
private DefaultSerializerProvider() {
|
||||
// Prevent instantiation as this is a static utility class
|
||||
}
|
||||
}
|
@ -0,0 +1,74 @@
|
||||
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;
|
||||
|
||||
/**
|
||||
* Provides default string parsers for common types.
|
||||
*/
|
||||
public final class DefaultStringParserProvider {
|
||||
|
||||
private static final Map<Class<?>, NodeStringParser<?>> PARSERS = new HashMap<>();
|
||||
|
||||
/**
|
||||
* Adds a default string parser for the given type.
|
||||
*
|
||||
* @param clazz The type.
|
||||
* @param parser The string parser.
|
||||
*/
|
||||
public static void addDefaultStringParser(Class<?> clazz, NodeStringParser<?> parser) {
|
||||
PARSERS.put(clazz, parser);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the default string parser for the given type.
|
||||
*
|
||||
* @param clazz The type.
|
||||
* @param <T> The type.
|
||||
* @return The default string parser for the given type, or null if no default string parser exists.
|
||||
*/
|
||||
public static <T> NodeStringParser<T> getDefaultStringParser(Class<T> clazz) {
|
||||
if (clazz.isEnum()) {
|
||||
// Special case for enums
|
||||
return (NodeStringParser<T>) ENUM_STRING_PARSER;
|
||||
}
|
||||
return (NodeStringParser<T>) PARSERS.get(clazz);
|
||||
}
|
||||
|
||||
private static final NodeStringParser<Enum> ENUM_STRING_PARSER = (input, type) -> Try.of(
|
||||
() -> Enum.valueOf(type, input.toUpperCase()));
|
||||
|
||||
private static final NodeStringParser<String> STRING_STRING_PARSER = (input, type) -> Try.of(
|
||||
() -> input);
|
||||
|
||||
private static final NodeStringParser<Boolean> BOOLEAN_STRING_PARSER = (input, type) -> Try.of(
|
||||
() -> ACFUtil.isTruthy(String.valueOf(input).toLowerCase()));
|
||||
|
||||
private static final NodeStringParser<Integer> INTEGER_STRING_PARSER = (input, type) -> Try.of(
|
||||
() -> ACFUtil.parseInt(input));
|
||||
|
||||
private static final NodeStringParser<Double> DOUBLE_STRING_PARSER = (input, type) -> Try.of(
|
||||
() -> ACFUtil.parseDouble(input));
|
||||
|
||||
private static final NodeStringParser<Float> FLOAT_STRING_PARSER = (input, type) -> Try.of(
|
||||
() -> ACFUtil.parseFloat(input));
|
||||
|
||||
private static final NodeStringParser<Long> 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);
|
||||
}
|
||||
|
||||
private DefaultStringParserProvider() {
|
||||
// Prevent instantiation as this is a static utility class
|
||||
}
|
||||
}
|
@ -0,0 +1,65 @@
|
||||
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;
|
||||
|
||||
/**
|
||||
* Provides default suggestions for common types.
|
||||
*/
|
||||
public final class DefaultSuggesterProvider {
|
||||
|
||||
private static final Map<Class<?>, NodeSuggester> SUGGESTERS = new HashMap<>();
|
||||
|
||||
/**
|
||||
* Adds a default suggester for the given type.
|
||||
*
|
||||
* @param clazz The type.
|
||||
* @param suggester The suggester.
|
||||
*/
|
||||
public static void addDefaultSuggester(Class<?> clazz, NodeSuggester suggester) {
|
||||
SUGGESTERS.put(clazz, suggester);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the default suggester for the given type.
|
||||
*
|
||||
* @param clazz The type.
|
||||
* @return The default suggester for the given type, or null if no default suggester exists.
|
||||
*/
|
||||
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);
|
||||
}
|
||||
|
||||
private DefaultSuggesterProvider() {
|
||||
// Prevent instantiation as this is a static utility class
|
||||
}
|
||||
}
|
@ -0,0 +1,26 @@
|
||||
package org.mvplugins.multiverse.core.configuration.functions;
|
||||
|
||||
/**
|
||||
* A function that serializes and deserializes objects to and from YAML.
|
||||
*
|
||||
* @param <T> The type of the object to serialize and deserialize.
|
||||
*/
|
||||
public interface NodeSerializer<T> {
|
||||
/**
|
||||
* Deserializes an object from YAML.
|
||||
*
|
||||
* @param object The object to deserialize.
|
||||
* @param type The type of the object to deserialize.
|
||||
* @return The deserialized typed value.
|
||||
*/
|
||||
T deserialize(Object object, Class<T> type);
|
||||
|
||||
/**
|
||||
* Serializes an object to YAML.
|
||||
*
|
||||
* @param object The object to serialize.
|
||||
* @param type The type of the object to serialize.
|
||||
* @return The serialized object.
|
||||
*/
|
||||
Object serialize(T object, Class<T> type);
|
||||
}
|
@ -0,0 +1,22 @@
|
||||
package org.mvplugins.multiverse.core.configuration.functions;
|
||||
|
||||
import io.vavr.control.Try;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
/**
|
||||
* A function that parses a string into a node value object of type {@link T}.
|
||||
*
|
||||
* @param <T> The type of the object to parse.
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface NodeStringParser<T> {
|
||||
/**
|
||||
* Parses a string into a node value object of type {@link T}.
|
||||
*
|
||||
* @param string The string to parse.
|
||||
* @param type The type of the object to parse.
|
||||
* @return The parsed object, or {@link Try.Failure} if the string could not be parsed.
|
||||
*/
|
||||
@NotNull Try<T> parse(@Nullable String string, @NotNull Class<T> type);
|
||||
}
|
@ -0,0 +1,20 @@
|
||||
package org.mvplugins.multiverse.core.configuration.functions;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
/**
|
||||
* A function that suggests possible values for a node value.
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface NodeSuggester {
|
||||
/**
|
||||
* Suggests possible values for a node value.
|
||||
*
|
||||
* @param input The current user input
|
||||
* @return The possible values.
|
||||
*/
|
||||
@NotNull Collection<String> suggest(@Nullable String input);
|
||||
}
|
@ -0,0 +1 @@
|
||||
package org.mvplugins.multiverse.core.configuration.functions;
|
@ -1,5 +1,7 @@
|
||||
package org.mvplugins.multiverse.core.configuration.handle;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import io.vavr.control.Try;
|
||||
@ -10,6 +12,7 @@ import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import org.mvplugins.multiverse.core.configuration.migration.ConfigMigrator;
|
||||
import org.mvplugins.multiverse.core.configuration.node.ConfigNodeNotFoundException;
|
||||
import org.mvplugins.multiverse.core.configuration.node.Node;
|
||||
import org.mvplugins.multiverse.core.configuration.node.NodeGroup;
|
||||
import org.mvplugins.multiverse.core.configuration.node.ValueNode;
|
||||
|
||||
@ -68,14 +71,55 @@ public abstract class GenericConfigHandle<C extends ConfigurationSection> {
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the value of a node, if the node has a default value, it will be returned if the node is not found.
|
||||
* Auto-complete suggestions for a property.
|
||||
*
|
||||
* @param name The name of the node.
|
||||
* @return The value of the node.
|
||||
* @param input The current user input.
|
||||
* @return A collection of possible string values.
|
||||
*/
|
||||
public Try<Object> get(@Nullable String name) {
|
||||
return nodes.findNode(name, ValueNode.class)
|
||||
.toTry(() -> new ConfigNodeNotFoundException(name))
|
||||
.map(node -> get((ValueNode<Object>) node));
|
||||
public Collection<String> suggestPropertyValues(@Nullable String name, @Nullable String input) {
|
||||
return findNode(name, ValueNode.class)
|
||||
.map(node -> node.suggest(input))
|
||||
.getOrElse(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, or an error if the node was not found.
|
||||
*/
|
||||
public Try<Object> getProperty(@Nullable String name) {
|
||||
return findNode(name, ValueNode.class).map(this::get);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the string 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 string value to set.
|
||||
* @return Empty try if the value was set, try containing an error otherwise.
|
||||
*/
|
||||
public Try<Void> setPropertyString(@Nullable String name, @Nullable String value) {
|
||||
return findNode(name, ValueNode.class)
|
||||
.flatMap(node -> node.parseFromString(value)
|
||||
.flatMap(parsedValue -> set(node, parsedValue)));
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 Empty try if the value was set, try containing an error otherwise.
|
||||
*/
|
||||
public Try<Void> setProperty(@Nullable String name, @Nullable Object value) {
|
||||
return findNode(name, ValueNode.class).flatMap(node -> set(node, value));
|
||||
}
|
||||
|
||||
private <T extends Node> Try<T> findNode(@Nullable String name, @NotNull Class<T> type) {
|
||||
return nodes.findNode(name, type)
|
||||
.toTry(() -> new ConfigNodeNotFoundException(name));
|
||||
}
|
||||
|
||||
/**
|
||||
@ -91,26 +135,13 @@ public abstract class GenericConfigHandle<C extends ConfigurationSection> {
|
||||
return node.getSerializer().deserialize(config.get(node.getPath(), node.getDefaultValue()), node.getType());
|
||||
}
|
||||
|
||||
/**
|
||||
* 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<Void> 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 <T> The type of the node value.
|
||||
* @return Empty try if the value was set, try containing an error otherwise.
|
||||
*/
|
||||
public <T> Try<Void> set(@NotNull ValueNode<T> node, T value) {
|
||||
return node.validate(value).map(ignore -> {
|
||||
@ -130,8 +161,9 @@ public abstract class GenericConfigHandle<C extends ConfigurationSection> {
|
||||
* Sets the default value of a node.
|
||||
*
|
||||
* @param node The node to set the default value of.
|
||||
* @param <T> The type of the node value.
|
||||
*/
|
||||
public void setDefault(@NotNull ValueNode node) {
|
||||
public <T> void setDefault(@NotNull ValueNode<T> node) {
|
||||
config.set(node.getPath(), node.getDefaultValue());
|
||||
}
|
||||
|
||||
@ -141,13 +173,14 @@ public abstract class GenericConfigHandle<C extends ConfigurationSection> {
|
||||
* @param <C> The configuration type.
|
||||
* @param <B> The builder type.
|
||||
*/
|
||||
public static abstract class Builder<C extends ConfigurationSection, B extends GenericConfigHandle.Builder<C, B>> {
|
||||
public abstract static class Builder<C extends ConfigurationSection, B extends GenericConfigHandle.Builder<C, B>> {
|
||||
|
||||
protected @Nullable Logger logger;
|
||||
protected @Nullable NodeGroup nodes;
|
||||
protected @Nullable ConfigMigrator migrator;
|
||||
|
||||
protected Builder() {}
|
||||
protected Builder() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the logger.
|
||||
|
@ -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<T> extends ConfigHeaderNode implements ValueNode<T> {
|
||||
protected final @Nullable String name;
|
||||
protected final @NotNull Class<T> type;
|
||||
protected final @Nullable Supplier<T> defaultValue;
|
||||
protected final @Nullable NodeSuggester suggester;
|
||||
protected final @Nullable NodeStringParser<T> stringParser;
|
||||
protected final @Nullable NodeSerializer<T> serializer;
|
||||
protected final @Nullable Function<T, Try<Void>> validator;
|
||||
protected final @Nullable BiConsumer<T, T> onSetValue;
|
||||
@ -43,6 +54,8 @@ public class ConfigNode<T> extends ConfigHeaderNode implements ValueNode<T> {
|
||||
@Nullable String name,
|
||||
@NotNull Class<T> type,
|
||||
@Nullable Supplier<T> defaultValue,
|
||||
@Nullable NodeSuggester suggester,
|
||||
@Nullable NodeStringParser<T> stringParser,
|
||||
@Nullable NodeSerializer<T> serializer,
|
||||
@Nullable Function<T, Try<Void>> validator,
|
||||
@Nullable BiConsumer<T, T> onSetValue) {
|
||||
@ -50,7 +63,15 @@ public class ConfigNode<T> extends ConfigHeaderNode implements ValueNode<T> {
|
||||
this.name = name;
|
||||
this.type = type;
|
||||
this.defaultValue = defaultValue;
|
||||
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;
|
||||
}
|
||||
@ -82,6 +103,31 @@ public class ConfigNode<T> extends ConfigHeaderNode implements ValueNode<T> {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public @NotNull Collection<String> suggest(@Nullable String input) {
|
||||
if (suggester != null) {
|
||||
return suggester.suggest(input);
|
||||
}
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public @NotNull Try<T> parseFromString(@Nullable String input) {
|
||||
if (stringParser != null) {
|
||||
return stringParser.parse(input, type);
|
||||
}
|
||||
return Try.failure(new UnsupportedOperationException("No string parser for type " + type.getName()));
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public @Nullable NodeSerializer<T> getSerializer() {
|
||||
return serializer;
|
||||
}
|
||||
@ -114,11 +160,12 @@ public class ConfigNode<T> extends ConfigHeaderNode implements ValueNode<T> {
|
||||
* @param <B> The type of the builder.
|
||||
*/
|
||||
public static class Builder<T, B extends ConfigNode.Builder<T, B>> extends ConfigHeaderNode.Builder<B> {
|
||||
private static final NodeSerializer<?> ENUM_NODE_SERIALIZER = new EnumNodeSerializer<>();
|
||||
|
||||
protected @Nullable String name;
|
||||
protected @NotNull final Class<T> type;
|
||||
protected @Nullable Supplier<T> defaultValue;
|
||||
protected @Nullable NodeSuggester suggester;
|
||||
protected @Nullable NodeStringParser<T> stringParser;
|
||||
protected @Nullable NodeSerializer<T> serializer;
|
||||
protected @Nullable Function<T, Try<Void>> validator;
|
||||
protected @Nullable BiConsumer<T, T> onSetValue;
|
||||
@ -133,9 +180,6 @@ public class ConfigNode<T> extends ConfigHeaderNode implements ValueNode<T> {
|
||||
super(path);
|
||||
this.name = path;
|
||||
this.type = type;
|
||||
if (type.isEnum()) {
|
||||
this.serializer = (NodeSerializer<T>) ENUM_NODE_SERIALIZER;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -171,6 +215,28 @@ public class ConfigNode<T> extends ConfigHeaderNode implements ValueNode<T> {
|
||||
return self();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the suggester for this node.
|
||||
*
|
||||
* @param suggester The suggester for this node.
|
||||
* @return This builder.
|
||||
*/
|
||||
public @NotNull B suggester(@NotNull NodeSuggester suggester) {
|
||||
this.suggester = suggester;
|
||||
return self();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the string parser for this node.
|
||||
*
|
||||
* @param stringParser The string parser for this node.
|
||||
* @return This builder.
|
||||
*/
|
||||
public @NotNull B stringParser(@NotNull NodeStringParser<T> stringParser) {
|
||||
this.stringParser = stringParser;
|
||||
return self();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the serializer for this node.
|
||||
*
|
||||
@ -210,7 +276,7 @@ public class ConfigNode<T> extends ConfigHeaderNode implements ValueNode<T> {
|
||||
@Override
|
||||
public @NotNull ConfigNode<T> build() {
|
||||
return new ConfigNode<>(path, comments.toArray(new String[0]),
|
||||
name, type, defaultValue,serializer, validator, onSetValue);
|
||||
name, type, defaultValue, suggester, stringParser, serializer, validator, onSetValue);
|
||||
}
|
||||
|
||||
protected @NotNull B self() {
|
||||
|
@ -1,14 +0,0 @@
|
||||
package org.mvplugins.multiverse.core.configuration.node;
|
||||
|
||||
public class EnumNodeSerializer<T extends Enum<T>> implements NodeSerializer<T> {
|
||||
|
||||
@Override
|
||||
public T deserialize(Object object, Class<T> type) {
|
||||
return Enum.valueOf(type, object.toString().toUpperCase());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object serialize(T object, Class<T> type) {
|
||||
return object.toString();
|
||||
}
|
||||
}
|
@ -1,6 +0,0 @@
|
||||
package org.mvplugins.multiverse.core.configuration.node;
|
||||
|
||||
public interface NodeSerializer<T> {
|
||||
T deserialize(Object object, Class<T> type);
|
||||
Object serialize(T object, Class<T> type);
|
||||
}
|
@ -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<T> extends Node {
|
||||
|
||||
/**
|
||||
@ -28,6 +32,22 @@ public interface ValueNode<T> 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<String> 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<T> parseFromString(@Nullable String input);
|
||||
|
||||
/**
|
||||
* Gets the serializer for this node.
|
||||
*
|
||||
|
@ -119,11 +119,11 @@ public final class WorldConfig {
|
||||
}
|
||||
|
||||
public Try<Object> getProperty(String name) {
|
||||
return configHandle.get(name);
|
||||
return configHandle.getProperty(name);
|
||||
}
|
||||
|
||||
public Try<Void> setProperty(String name, Object value) {
|
||||
return configHandle.set(name, value);
|
||||
return configHandle.setProperty(name, value);
|
||||
}
|
||||
|
||||
public boolean getAdjustSpawn() {
|
||||
|
@ -80,6 +80,18 @@ class ConfigTest : TestWithMockBukkit() {
|
||||
assertEquals(1, config.getProperty("global-debug").get())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Updating an existing config property with setPropertyString reflects the changes in getProperty`() {
|
||||
assertTrue(config.setPropertyString("enforce-access", "true").isSuccess)
|
||||
assertEquals(true, config.getProperty("enforce-access").get())
|
||||
|
||||
assertTrue(config.setPropertyString("first-spawn-location", "world2").isSuccess)
|
||||
assertEquals("world2", config.getProperty("first-spawn-location").get())
|
||||
|
||||
assertTrue(config.setPropertyString("global-debug", "1").isSuccess)
|
||||
assertEquals(1, config.getProperty("global-debug").get())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Updating a non-existing property with setProperty returns false`() {
|
||||
assertTrue(config.setProperty("invalid-property", false).isFailure)
|
||||
|
Loading…
Reference in New Issue
Block a user