Implement better suggestion and string parsing for config node

This commit is contained in:
Ben Woo 2023-09-14 16:48:07 +08:00
parent 738101d5d4
commit 362d871ab8
No known key found for this signature in database
GPG Key ID: FB2A3645536E12C8
19 changed files with 441 additions and 189 deletions

View File

@ -10,12 +10,12 @@ import co.aikar.commands.annotation.Syntax;
import com.dumptruckman.minecraft.util.Logging; import com.dumptruckman.minecraft.util.Logging;
import jakarta.inject.Inject; import jakarta.inject.Inject;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jvnet.hk2.annotations.Optional;
import org.jvnet.hk2.annotations.Service; import org.jvnet.hk2.annotations.Service;
import org.mvplugins.multiverse.core.commandtools.MVCommandIssuer; import org.mvplugins.multiverse.core.commandtools.MVCommandIssuer;
import org.mvplugins.multiverse.core.commandtools.MVCommandManager; import org.mvplugins.multiverse.core.commandtools.MVCommandManager;
import org.mvplugins.multiverse.core.commandtools.MultiverseCommand; 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.configuration.handle.ConfigModifyType;
import org.mvplugins.multiverse.core.world.MultiverseWorld; import org.mvplugins.multiverse.core.world.MultiverseWorld;
import org.mvplugins.multiverse.core.world.WorldManager; import org.mvplugins.multiverse.core.world.WorldManager;
@ -54,19 +54,20 @@ class ModifyCommand extends MultiverseCommand {
@Description("") @Description("")
String propertyName, String propertyName,
@Optional
@Syntax("[value]") @Syntax("[value]")
@Description("") @Description("")
WorldConfigValue propertyValue) { String propertyValue) {
Logging.fine("ModifyCommand.onModifyCommand: world=%s, configModifyType=%s, propertyName=%s, propertyValue=%s", Logging.fine("ModifyCommand.onModifyCommand: world=%s, configModifyType=%s, propertyName=%s, propertyValue=%s",
world, configModifyType, propertyName, propertyValue); 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() issuer.sendMessage("Property " + propertyName + " set to " + world.getProperty(propertyName).getOrNull()
+ " for world " + world.getName() + "."); + " for world " + world.getName() + ".");
worldManager.saveWorldsConfig(); worldManager.saveWorldsConfig();
}).onFailure(exception -> { }).onFailure(exception -> {
issuer.sendMessage("Failed to " + configModifyType.name().toLowerCase() + " property " + propertyName 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()); issuer.sendMessage(exception.getMessage());
}); });
} }

View File

@ -170,24 +170,11 @@ public class MVCommandCompletions extends PaperCommandCompletions {
} }
private Collection<String> suggestMVWorldPropsValue(BukkitCommandCompletionContext context) { private Collection<String> suggestMVWorldPropsValue(BukkitCommandCompletionContext context) {
//noinspection unchecked
return Try.of(() -> { return Try.of(() -> {
MultiverseWorld mvWorld = context.getContextValue(MultiverseWorld.class); MultiverseWorld mvWorld = context.getContextValue(MultiverseWorld.class);
ConfigModifyType modifyType = context.getContextValue(ConfigModifyType.class);
String propertyName = context.getContextValue(String.class); String propertyName = context.getContextValue(String.class);
Class type = mvWorld.getPropertyType(propertyName).get(); return mvWorld.suggestPropertyValues(modifyType, propertyName, context.getInput());
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();
}).getOrElse(Collections.emptyList()); }).getOrElse(Collections.emptyList());
} }

View File

@ -1,9 +1,6 @@
package org.mvplugins.multiverse.core.commandtools; package org.mvplugins.multiverse.core.commandtools;
import java.util.Arrays;
import java.util.HashSet; import java.util.HashSet;
import java.util.List;
import java.util.Objects;
import java.util.Set; import java.util.Set;
import co.aikar.commands.BukkitCommandExecutionContext; import co.aikar.commands.BukkitCommandExecutionContext;
@ -11,10 +8,8 @@ import co.aikar.commands.BukkitCommandIssuer;
import co.aikar.commands.InvalidCommandArgument; import co.aikar.commands.InvalidCommandArgument;
import co.aikar.commands.PaperCommandContexts; import co.aikar.commands.PaperCommandContexts;
import co.aikar.commands.contexts.ContextResolver; import co.aikar.commands.contexts.ContextResolver;
import com.dumptruckman.minecraft.util.Logging;
import com.google.common.base.Strings; import com.google.common.base.Strings;
import io.vavr.control.Option; import io.vavr.control.Option;
import io.vavr.control.Try;
import jakarta.inject.Inject; import jakarta.inject.Inject;
import org.bukkit.GameRule; import org.bukkit.GameRule;
import org.bukkit.entity.Player; 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.GameRuleValue;
import org.mvplugins.multiverse.core.commandtools.context.MVConfigValue; 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.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.Node;
import org.mvplugins.multiverse.core.configuration.node.ValueNode; import org.mvplugins.multiverse.core.configuration.node.ValueNode;
import org.mvplugins.multiverse.core.destination.DestinationsProvider; import org.mvplugins.multiverse.core.destination.DestinationsProvider;
@ -69,7 +62,6 @@ class MVCommandContexts extends PaperCommandContexts {
registerIssuerAwareContext(LoadedMultiverseWorld[].class, this::parseLoadedWorldArray); registerIssuerAwareContext(LoadedMultiverseWorld[].class, this::parseLoadedWorldArray);
registerIssuerAwareContext(Player.class, this::parsePlayer); registerIssuerAwareContext(Player.class, this::parsePlayer);
registerIssuerAwareContext(Player[].class, this::parsePlayerArray); registerIssuerAwareContext(Player[].class, this::parsePlayerArray);
registerIssuerAwareContext(WorldConfigValue.class, this::parseWorldConfigValue);
} }
private MVCommandIssuer parseMVCommandIssuer(BukkitCommandExecutionContext context) { private MVCommandIssuer parseMVCommandIssuer(BukkitCommandExecutionContext context) {
@ -409,50 +401,4 @@ class MVCommandContexts extends PaperCommandContexts {
} }
throw new InvalidCommandArgument("Player " + playerIdentifier + " not found."); 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<? extends Enum>) 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<? extends Enum>) type).getEnumConstants())
.map(enumValue -> enumValue.name().toLowerCase())
.reduce((a, b) -> a + ", " + b)
.orElse(""))));
}
ContextResolver<?, BukkitCommandExecutionContext> resolver = getResolver(type);
return resolver == null ? value : resolver.getContext(context);
}
} }

View File

@ -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
+ '}';
}
}

View File

@ -114,12 +114,12 @@ public class MVCoreConfig implements MVConfig {
@Override @Override
public Try<Object> getProperty(String name) { public Try<Object> getProperty(String name) {
return configHandle.get(name); return configHandle.getProperty(name);
} }
@Override @Override
public Try<Void> setProperty(String name, Object value) { public Try<Void> setProperty(String name, Object value) {
return configHandle.set(name, value); return configHandle.setProperty(name, value);
} }
@Override @Override

View File

@ -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<Class<?>, NodeSerializer<?>> SERIALIZERS = new HashMap<>();
public static <T> void addDefaultSerializer(@NotNull Class<T> type, @NotNull NodeSerializer<T> serializer) {
SERIALIZERS.put(type, serializer);
}
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);
}
}

View File

@ -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<Class<?>, NodeStringParser<?>> PARSERS = new HashMap<>();
public static void addDefaultStringParser(Class<?> clazz, NodeStringParser<?> parser) {
PARSERS.put(clazz, parser);
}
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);
}
}

View File

@ -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<Class<?>, 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);
}
}

View File

@ -1,4 +1,4 @@
package org.mvplugins.multiverse.core.configuration.node; package org.mvplugins.multiverse.core.configuration.functions;
public interface NodeSerializer<T> { public interface NodeSerializer<T> {
T deserialize(Object object, Class<T> type); T deserialize(Object object, Class<T> type);

View File

@ -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<T> {
Try<T> parse(@Nullable String string, @NotNull Class<T> type);
}

View File

@ -0,0 +1,8 @@
package org.mvplugins.multiverse.core.configuration.functions;
import java.util.Collection;
@FunctionalInterface
public interface NodeSuggester {
Collection<String> suggest(String input);
}

View File

@ -1,7 +1,9 @@
package org.mvplugins.multiverse.core.configuration.handle; package org.mvplugins.multiverse.core.configuration.handle;
import java.util.Collection; import java.util.Collection;
import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.function.Function;
import java.util.logging.Logger; import java.util.logging.Logger;
import io.vavr.control.Option; import io.vavr.control.Option;
@ -71,11 +73,11 @@ public abstract class GenericConfigHandle<C extends ConfigurationSection> {
}); });
} }
public Collection<String> getNames() { public Collection<String> getPropertyNames() {
return nodes.getNames(); return nodes.getNames();
} }
public Collection<String> getNamesThatSupports(ConfigModifyType configModifyType) { public Collection<String> getPropertyNames(ConfigModifyType configModifyType) {
return switch (configModifyType) { return switch (configModifyType) {
case SET, RESET -> nodes.getNames(); case SET, RESET -> nodes.getNames();
case ADD, REMOVE -> nodes.stream() case ADD, REMOVE -> nodes.stream()
@ -87,7 +89,7 @@ public abstract class GenericConfigHandle<C extends ConfigurationSection> {
}; };
} }
public Try<Class> getTypeByName(@Nullable String name) { public Try<Class> getPropertyType(@Nullable String name) {
return nodes.findNode(name, ValueNode.class) return nodes.findNode(name, ValueNode.class)
.map(valueNode -> { .map(valueNode -> {
if (valueNode instanceof ListValueNode listValueNode) { if (valueNode instanceof ListValueNode listValueNode) {
@ -98,22 +100,46 @@ public abstract class GenericConfigHandle<C extends ConfigurationSection> {
.toTry(() -> new ConfigNodeNotFoundException(name)); .toTry(() -> new ConfigNodeNotFoundException(name));
} }
public Collection<String> 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<List>) 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. * 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. * @param name The name of the node.
* @return The value of the node. * @return The value of the node.
*/ */
public Try<Object> get(@Nullable String name) { public Try<Object> getProperty(@Nullable String name) {
return nodes.findNode(name, ValueNode.class) return nodes.findNode(name, ValueNode.class)
.toTry(() -> new ConfigNodeNotFoundException(name)) .toTry(() -> new ConfigNodeNotFoundException(name))
.map(node -> get((ValueNode<Object>) node)); .map(node -> get((ValueNode<Object>) node));
} }
public Try<Void> modify(@NotNull ConfigModifyType type, @Nullable String name, @Nullable Object value) { public Try<Void> modifyProperty(@NotNull ConfigModifyType type, @Nullable String name, @Nullable String value) {
return nodes.findNode(name, ValueNode.class) return switch (type) {
.toTry(() -> new ConfigNodeNotFoundException(name)) case SET -> setProperty(name, value);
.flatMapTry(node -> modify(type, node, 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<C extends ConfigurationSection> {
* @param value The value to set. * @param value The value to set.
* @return True if the value was set, false otherwise. * @return True if the value was set, false otherwise.
*/ */
public Try<Void> set(@Nullable String name, Object value) { public Try<Void> setProperty(@Nullable String name, @Nullable String value) {
//noinspection unchecked //noinspection unchecked
return nodes.findNode(name, ValueNode.class) return propertyAction(name, ValueNode.class, node ->
.toTry(() -> new ConfigNodeNotFoundException(name)) node.parseFromString(value).flatMapTry(parsedValue -> set(node, parsedValue)));
.flatMapTry(node -> set(node, value));
} }
public Try<Void> add(@Nullable String name, Object value) { public Try<Void> addProperty(@Nullable String name, @Nullable String value) {
//noinspection unchecked //noinspection unchecked
return nodes.findNode(name, ListValueNode.class) return propertyAction(name, ListValueNode.class, node ->
.toTry(() -> new ConfigNodeNotFoundException(name)) node.parseItemFromString(value).flatMapTry(item -> add(node, item)));
.flatMapTry(node -> add(node, value));
} }
public Try<Void> remove(@Nullable String name, Object value) { public Try<Void> removeProperty(@Nullable String name, @Nullable String value) {
//noinspection unchecked //noinspection unchecked
return nodes.findNode(name, ListValueNode.class) return propertyAction(name, ListValueNode.class, node ->
.toTry(() -> new ConfigNodeNotFoundException(name)) node.parseItemFromString(value).flatMapTry(item -> remove(node, item)));
.flatMapTry(node -> remove(node, value));
} }
public Try<Void> reset(@Nullable String name) { public Try<Void> resetProperty(@Nullable String name) {
return nodes.findNode(name, ValueNode.class) 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<Void> setProperty(@Nullable String name, @Nullable Object value) {
//noinspection unchecked
return propertyAction(name, ValueNode.class, node -> set(node, value));
}
public Try<Void> addProperty(@Nullable String name, @Nullable Object value) {
//noinspection unchecked
return propertyAction(name, ListValueNode.class, node -> add(node, value));
}
public Try<Void> removeProperty(@Nullable String name, @Nullable Object value) {
//noinspection unchecked
return propertyAction(name, ListValueNode.class, node -> remove(node, value));
}
private <T extends ValueNode<?>> Try<Void> propertyAction(
@Nullable String name,
@NotNull Class<T> nodeClass,
@NotNull Function<T, Try<Void>> action) {
return nodes.findNode(name, nodeClass)
.toTry(() -> new ConfigNodeNotFoundException(name)) .toTry(() -> new ConfigNodeNotFoundException(name))
.flatMapTry(this::reset); .flatMapTry(action::apply);
} }
/** /**
@ -164,30 +216,6 @@ public abstract class GenericConfigHandle<C extends ConfigurationSection> {
return node.getSerializer().deserialize(config.get(node.getPath(), node.getDefaultValue()), node.getType()); return node.getSerializer().deserialize(config.get(node.getPath(), node.getDefaultValue()), node.getType());
} }
public Try<Void> 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. * Sets the value of a node, if the validator is not null, it will be tested first.
* *

View File

@ -1,5 +1,7 @@
package org.mvplugins.multiverse.core.configuration.node; package org.mvplugins.multiverse.core.configuration.node;
import java.util.Collection;
import java.util.Collections;
import java.util.function.BiConsumer; import java.util.function.BiConsumer;
import java.util.function.Function; import java.util.function.Function;
import java.util.function.Supplier; import java.util.function.Supplier;
@ -9,6 +11,13 @@ import io.vavr.control.Try;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable; 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. * 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 @Nullable String name;
protected final @NotNull Class<T> type; protected final @NotNull Class<T> type;
protected final @Nullable Supplier<T> defaultValueSupplier; protected final @Nullable Supplier<T> defaultValueSupplier;
protected final @Nullable NodeSuggester suggester;
protected final @Nullable NodeStringParser<T> stringParser;
protected final @Nullable NodeSerializer<T> serializer; protected final @Nullable NodeSerializer<T> serializer;
protected final @Nullable Function<T, Try<Void>> validator; protected final @Nullable Function<T, Try<Void>> validator;
protected final @Nullable BiConsumer<T, T> onSetValue; protected final @Nullable BiConsumer<T, T> onSetValue;
@ -43,6 +54,8 @@ public class ConfigNode<T> extends ConfigHeaderNode implements ValueNode<T> {
@Nullable String name, @Nullable String name,
@NotNull Class<T> type, @NotNull Class<T> type,
@Nullable Supplier<T> defaultValueSupplier, @Nullable Supplier<T> defaultValueSupplier,
@Nullable NodeSuggester suggester,
@Nullable NodeStringParser<T> stringParser,
@Nullable NodeSerializer<T> serializer, @Nullable NodeSerializer<T> serializer,
@Nullable Function<T, Try<Void>> validator, @Nullable Function<T, Try<Void>> validator,
@Nullable BiConsumer<T, T> onSetValue) { @Nullable BiConsumer<T, T> onSetValue) {
@ -50,7 +63,15 @@ public class ConfigNode<T> extends ConfigHeaderNode implements ValueNode<T> {
this.name = name; this.name = name;
this.type = type; this.type = type;
this.defaultValueSupplier = defaultValueSupplier; 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.validator = validator;
this.onSetValue = onSetValue; this.onSetValue = onSetValue;
} }
@ -79,6 +100,28 @@ public class ConfigNode<T> extends ConfigHeaderNode implements ValueNode<T> {
return defaultValueSupplier != null ? defaultValueSupplier.get() : null; return defaultValueSupplier != null ? defaultValueSupplier.get() : 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()));
}
public @Nullable NodeSerializer<T> getSerializer() { public @Nullable NodeSerializer<T> getSerializer() {
return serializer; return serializer;
} }
@ -111,11 +154,12 @@ public class ConfigNode<T> extends ConfigHeaderNode implements ValueNode<T> {
* @param <B> The type of the builder. * @param <B> The type of the builder.
*/ */
public static class Builder<T, B extends ConfigNode.Builder<T, B>> extends ConfigHeaderNode.Builder<B> { 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 @Nullable String name;
protected @NotNull final Class<T> type; protected @NotNull final Class<T> type;
protected @Nullable Supplier<T> defaultValueSupplier; protected @Nullable Supplier<T> defaultValueSupplier;
protected @Nullable NodeSuggester suggester;
protected @Nullable NodeStringParser<T> stringParser;
protected @Nullable NodeSerializer<T> serializer; protected @Nullable NodeSerializer<T> serializer;
protected @Nullable Function<T, Try<Void>> validator; protected @Nullable Function<T, Try<Void>> validator;
protected @Nullable BiConsumer<T, T> onSetValue; protected @Nullable BiConsumer<T, T> onSetValue;
@ -130,9 +174,6 @@ public class ConfigNode<T> extends ConfigHeaderNode implements ValueNode<T> {
super(path); super(path);
this.name = path; this.name = path;
this.type = type; this.type = type;
if (type.isEnum()) {
this.serializer = (NodeSerializer<T>) ENUM_NODE_SERIALIZER;
}
} }
/** /**
@ -168,6 +209,16 @@ public class ConfigNode<T> extends ConfigHeaderNode implements ValueNode<T> {
return self(); return self();
} }
public @NotNull B suggester(@NotNull NodeSuggester suggester) {
this.suggester = suggester;
return self();
}
public @NotNull B stringParser(@NotNull NodeStringParser<T> stringParser) {
this.stringParser = stringParser;
return self();
}
public @NotNull B serializer(@NotNull NodeSerializer<T> serializer) { public @NotNull B serializer(@NotNull NodeSerializer<T> serializer) {
this.serializer = serializer; this.serializer = serializer;
return self(); return self();
@ -200,6 +251,8 @@ public class ConfigNode<T> extends ConfigHeaderNode implements ValueNode<T> {
name, name,
type, type,
defaultValueSupplier, defaultValueSupplier,
suggester,
stringParser,
serializer, serializer,
validator, validator,
onSetValue); onSetValue);

View File

@ -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();
}
}

View File

@ -1,6 +1,8 @@
package org.mvplugins.multiverse.core.configuration.node; package org.mvplugins.multiverse.core.configuration.node;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.function.BiConsumer; import java.util.function.BiConsumer;
import java.util.function.Function; import java.util.function.Function;
@ -10,6 +12,12 @@ import io.vavr.control.Try;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable; 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<I> extends ConfigNode<List<I>> implements ListValueNode<I> { public class ListConfigNode<I> extends ConfigNode<List<I>> implements ListValueNode<I> {
/** /**
@ -27,6 +35,8 @@ public class ListConfigNode<I> extends ConfigNode<List<I>> implements ListValueN
} }
protected final Class<I> itemType; protected final Class<I> itemType;
protected final NodeSuggester itemSuggester;
protected final NodeStringParser<I> itemStringParser;
protected final NodeSerializer<I> itemSerializer; protected final NodeSerializer<I> itemSerializer;
protected final Function<I, Try<Void>> itemValidator; protected final Function<I, Try<Void>> itemValidator;
protected final BiConsumer<I, I> onSetItemValue; protected final BiConsumer<I, I> onSetItemValue;
@ -37,15 +47,26 @@ public class ListConfigNode<I> extends ConfigNode<List<I>> implements ListValueN
@Nullable String name, @Nullable String name,
@NotNull Class<List<I>> type, @NotNull Class<List<I>> type,
@Nullable Supplier<List<I>> defaultValueSupplier, @Nullable Supplier<List<I>> defaultValueSupplier,
@Nullable NodeSuggester suggester,
@Nullable NodeStringParser<List<I>> stringParser,
@Nullable NodeSerializer<List<I>> serializer, @Nullable NodeSerializer<List<I>> serializer,
@Nullable Function<List<I>, Try<Void>> validator, @Nullable Function<List<I>, Try<Void>> validator,
@Nullable BiConsumer<List<I>, List<I>> onSetValue, @Nullable BiConsumer<List<I>, List<I>> onSetValue,
@NotNull Class<I> itemType, @NotNull Class<I> itemType,
@Nullable NodeSuggester itemSuggester,
@Nullable NodeStringParser<I> itemStringParser,
@Nullable NodeSerializer<I> itemSerializer, @Nullable NodeSerializer<I> itemSerializer,
@Nullable Function<I, Try<Void>> itemValidator, @Nullable Function<I, Try<Void>> itemValidator,
@Nullable BiConsumer<I, I> onSetItemValue) { @Nullable BiConsumer<I, I> 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.itemType = itemType;
this.itemSuggester = itemSuggester != null
? itemSuggester
: DefaultSuggesterProvider.getDefaultSuggester(itemType);
this.itemStringParser = itemStringParser != null
? itemStringParser
: DefaultStringParserProvider.getDefaultStringParser(itemType);
this.itemSerializer = itemSerializer; this.itemSerializer = itemSerializer;
this.itemValidator = itemValidator; this.itemValidator = itemValidator;
this.onSetItemValue = onSetItemValue; this.onSetItemValue = onSetItemValue;
@ -59,6 +80,22 @@ public class ListConfigNode<I> extends ConfigNode<List<I>> implements ListValueN
return itemType; return itemType;
} }
@Override
public @NotNull Collection<String> suggestItem(@Nullable String input) {
if (itemSuggester != null) {
return itemSuggester.suggest(input);
}
return Collections.emptyList();
}
@Override
public @NotNull Try<I> 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} * {@inheritDoc}
*/ */
@ -91,9 +128,11 @@ public class ListConfigNode<I> extends ConfigNode<List<I>> implements ListValueN
public static class Builder<I, B extends ListConfigNode.Builder<I, B>> extends ConfigNode.Builder<List<I>, B> { public static class Builder<I, B extends ListConfigNode.Builder<I, B>> extends ConfigNode.Builder<List<I>, B> {
protected final @NotNull Class<I> itemType; protected final @NotNull Class<I> itemType;
protected NodeSerializer<I> itemSerializer; protected @Nullable NodeSuggester itemSuggester;
protected Function<I, Try<Void>> itemValidator; protected @Nullable NodeStringParser<I> itemStringParser;
protected BiConsumer<I, I> onSetItemValue; protected @Nullable NodeSerializer<I> itemSerializer;
protected @Nullable Function<I, Try<Void>> itemValidator;
protected @Nullable BiConsumer<I, I> onSetItemValue;
/** /**
* Creates a new builder. * Creates a new builder.
@ -108,13 +147,23 @@ public class ListConfigNode<I> extends ConfigNode<List<I>> implements ListValueN
this.defaultValueSupplier = () -> (List<I>) new ArrayList<Object>(); this.defaultValueSupplier = () -> (List<I>) new ArrayList<Object>();
} }
public @NotNull B itemSuggester(@NotNull NodeSuggester itemSuggester) {
this.itemSuggester = itemSuggester;
return self();
}
public @NotNull B itemStringParser(@NotNull NodeStringParser<I> itemStringParser) {
this.itemStringParser = itemStringParser;
return self();
}
/** /**
* Sets the serializer for the node. * Sets the serializer for the node.
* *
* @param serializer The serializer for the node. * @param serializer The serializer for the node.
* @return This builder. * @return This builder.
*/ */
public @NotNull B itemSerializer(@Nullable NodeSerializer<I> serializer) { public @NotNull B itemSerializer(@NotNull NodeSerializer<I> serializer) {
this.itemSerializer = serializer; this.itemSerializer = serializer;
return self(); return self();
} }
@ -122,11 +171,11 @@ public class ListConfigNode<I> extends ConfigNode<List<I>> implements ListValueN
/** /**
* Sets the validator for the node. * Sets the validator for the node.
* *
* @param validator The validator for the node. * @param itemValidator The validator for the node.
* @return This builder. * @return This builder.
*/ */
public @NotNull B itemValidator(@Nullable Function<I, Try<Void>> validator) { public @NotNull B itemValidator(@NotNull Function<I, Try<Void>> itemValidator) {
this.itemValidator = validator; this.itemValidator = itemValidator;
if (validator == null) { if (validator == null) {
setDefaultValidator(); setDefaultValidator();
} }
@ -145,11 +194,11 @@ public class ListConfigNode<I> extends ConfigNode<List<I>> implements ListValueN
/** /**
* Sets the onSetValue for the node. * Sets the onSetValue for the node.
* *
* @param onSetValue The onSetValue for the node. * @param onSetItemValue The onSetValue for the node.
* @return This builder. * @return This builder.
*/ */
public @NotNull B onSetItemValue(@Nullable BiConsumer<I, I> onSetValue) { public @NotNull B onSetItemValue(@Nullable BiConsumer<I, I> onSetItemValue) {
this.onSetItemValue = onSetValue; this.onSetItemValue = onSetItemValue;
if (onSetValue == null) { if (onSetValue == null) {
setDefaultOnSetValue(); setDefaultOnSetValue();
} }
@ -178,11 +227,13 @@ public class ListConfigNode<I> extends ConfigNode<List<I>> implements ListValueN
name, name,
type, type,
defaultValueSupplier, defaultValueSupplier,
suggester,
stringParser,
serializer, serializer,
validator, validator,
onSetValue, onSetValue,
itemType, itemType,
itemSerializer, itemSuggester, itemStringParser, itemSerializer,
itemValidator, itemValidator,
onSetItemValue); onSetItemValue);
} }

View File

@ -1,11 +1,14 @@
package org.mvplugins.multiverse.core.configuration.node; package org.mvplugins.multiverse.core.configuration.node;
import java.util.Collection;
import java.util.List; import java.util.List;
import io.vavr.control.Try; import io.vavr.control.Try;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import org.mvplugins.multiverse.core.configuration.functions.NodeSerializer;
public interface ListValueNode<I> extends ValueNode<List<I>> { public interface ListValueNode<I> extends ValueNode<List<I>> {
/** /**
@ -15,6 +18,22 @@ public interface ListValueNode<I> extends ValueNode<List<I>> {
*/ */
@NotNull Class<I> getItemType(); @NotNull Class<I> getItemType();
/**
* Suggests possible string values for this node.
*
* @param input The input string.
* @return A collection of possible string values.
*/
@NotNull Collection<String> 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<I> parseItemFromString(@Nullable String input);
/** /**
* Gets the serializer for this node. * Gets the serializer for this node.
* *

View File

@ -1,10 +1,14 @@
package org.mvplugins.multiverse.core.configuration.node; package org.mvplugins.multiverse.core.configuration.node;
import java.util.Collection;
import io.vavr.control.Option; import io.vavr.control.Option;
import io.vavr.control.Try; import io.vavr.control.Try;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import org.mvplugins.multiverse.core.configuration.functions.NodeSerializer;
public interface ValueNode<T> extends Node { public interface ValueNode<T> extends Node {
/** /**
@ -28,6 +32,22 @@ public interface ValueNode<T> extends Node {
*/ */
@Nullable T getDefaultValue(); @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. * Gets the serializer for this node.
* *

View File

@ -11,6 +11,7 @@ import org.bukkit.GameMode;
import org.bukkit.Location; import org.bukkit.Location;
import org.bukkit.Material; import org.bukkit.Material;
import org.bukkit.World; import org.bukkit.World;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import org.mvplugins.multiverse.core.configuration.handle.ConfigModifyType; import org.mvplugins.multiverse.core.configuration.handle.ConfigModifyType;
@ -71,8 +72,9 @@ public class MultiverseWorld {
return worldConfig.getConfigurablePropertyNames(configModifyType); return worldConfig.getConfigurablePropertyNames(configModifyType);
} }
public Try<Class> getPropertyType(String name) { public Collection<String> suggestPropertyValues(
return worldConfig.getPropertyType(name); @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); return worldConfig.getProperty(name);
} }
public Try<Void> modifyProperty(ConfigModifyType type, String name, String value) {
return worldConfig.modifyProperty(type, name, value);
}
/** /**
* Sets a property on this world. * Sets a property on this world.
* *
@ -96,10 +102,6 @@ public class MultiverseWorld {
return worldConfig.setProperty(name, value); return worldConfig.setProperty(name, value);
} }
public Try<Void> 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. * Gets whether or not Multiverse should auto-adjust the spawn for this world.
* *

View File

@ -116,27 +116,32 @@ public final class WorldConfig {
} }
public Collection<String> getConfigurablePropertyNames() { public Collection<String> getConfigurablePropertyNames() {
return configHandle.getNames(); return configHandle.getPropertyNames();
} }
public Collection<String> getConfigurablePropertyNames(ConfigModifyType configModifyType) { public Collection<String> getConfigurablePropertyNames(ConfigModifyType configModifyType) {
return configHandle.getNamesThatSupports(configModifyType); return configHandle.getPropertyNames(configModifyType);
} }
public Try<Class> getPropertyType(String name) { public Try<Class> getPropertyType(String name) {
return configHandle.getTypeByName(name); return configHandle.getPropertyType(name);
}
public Collection<String> suggestPropertyValues(
@NotNull ConfigModifyType type, @Nullable String name, @Nullable String input) {
return configHandle.suggestPropertyValues(type, name, input);
} }
public Try<Object> getProperty(String name) { public Try<Object> getProperty(String name) {
return configHandle.get(name); return configHandle.getProperty(name);
}
public Try<Void> modifyProperty(ConfigModifyType type, String name, String value) {
return configHandle.modifyProperty(type, name, value);
} }
public Try<Void> setProperty(String name, Object value) { public Try<Void> setProperty(String name, Object value) {
return configHandle.set(name, value); return configHandle.setProperty(name, value);
}
public Try<Void> modifyProperty(ConfigModifyType type, String name, Object value) {
return configHandle.modify(type, name, value);
} }
public boolean getAdjustSpawn() { public boolean getAdjustSpawn() {