Compare commits

...
Author SHA1 Message Date
Ben Woo ee1bf8216e Better defaults and completions for ListConfigNode 2023-09-14 19:07:39 +08:00
Ben Woo 362d871ab8 Implement better suggestion and string parsing for config node 2023-09-14 16:48:07 +08:00
Ben Woo 738101d5d4 Fix imports from world manager merge 2023-09-14 11:40:08 +08:00
Ben Woo 782d58fcac Merge branch 'MV5' into ben/mv5/modify-command
# Conflicts:
#	src/main/java/org/mvplugins/multiverse/core/commandtools/MVCommandContexts.java
#	src/main/java/org/mvplugins/multiverse/core/world/MultiverseWorld.java
2023-09-14 11:36:01 +08:00
Ben Woo 94b1e77a98 Implement support for property modification with ConfigModifyType and ListValueNode 2023-09-13 18:21:14 +08:00
26 changed files with 1096 additions and 120 deletions

No files matched your search

@@ -0,0 +1,76 @@
package org.mvplugins.multiverse.core.commands;
import co.aikar.commands.annotation.CommandAlias;
import co.aikar.commands.annotation.CommandCompletion;
import co.aikar.commands.annotation.CommandPermission;
import co.aikar.commands.annotation.Description;
import co.aikar.commands.annotation.Flags;
import co.aikar.commands.annotation.Single;
import co.aikar.commands.annotation.Subcommand;
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.configuration.handle.ConfigModifyType;
import org.mvplugins.multiverse.core.world.MultiverseWorld;
import org.mvplugins.multiverse.core.world.WorldManager;
@Service
@CommandAlias("mv")
class ModifyCommand extends MultiverseCommand {
private final WorldManager worldManager;
@Inject
ModifyCommand(@NotNull MVCommandManager commandManager, WorldManager worldManager) {
super(commandManager);
this.worldManager = worldManager;
}
/* /mv modify [world] <set|add|remove|reset> <property> <value> */
@Subcommand("modify")
@CommandPermission("multiverse.core.modify")
@CommandCompletion("@mvworlds:scope=both @configmodifytype @mvworldpropsname @mvworldpropsvalue")
@Syntax("[world] <set|add|remove|reset> <property> <value>")
@Description("")
void onModifyCommand(
MVCommandIssuer issuer,
@Flags("resolve=issuerAware")
@Syntax("[world]")
@Description("")
MultiverseWorld world,
@Syntax("<set|add|remove|reset>")
@Description("")
ConfigModifyType configModifyType,
@Syntax("<property>")
@Description("")
String propertyName,
@Optional
@Single
@Syntax("[value]")
@Description("")
String propertyValue) {
Logging.fine("ModifyCommand.onModifyCommand: world=%s, configModifyType=%s, propertyName=%s, propertyValue=%s",
world, configModifyType, propertyName, propertyValue);
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 + " for world " + world.getName() + ".");
issuer.sendMessage(exception.getMessage());
});
}
}
@@ -3,6 +3,7 @@ package org.mvplugins.multiverse.core.commandtools;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.EnumSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
@@ -19,10 +20,12 @@ import com.google.common.collect.Sets;
import io.vavr.control.Try;
import jakarta.inject.Inject;
import org.bukkit.GameRule;
import org.bukkit.World;
import org.jetbrains.annotations.NotNull;
import org.jvnet.hk2.annotations.Service;
import org.mvplugins.multiverse.core.config.MVCoreConfig;
import org.mvplugins.multiverse.core.configuration.handle.ConfigModifyType;
import org.mvplugins.multiverse.core.destination.DestinationsProvider;
import org.mvplugins.multiverse.core.destination.ParsedDestination;
import org.mvplugins.multiverse.core.world.LoadedMultiverseWorld;
@@ -37,27 +40,32 @@ public class MVCommandCompletions extends PaperCommandCompletions {
private final DestinationsProvider destinationsProvider;
@Inject
public MVCommandCompletions(
MVCommandCompletions(
@NotNull MVCommandManager mvCommandManager,
@NotNull WorldManager worldManager,
@NotNull DestinationsProvider destinationsProvider,
@NotNull MVCoreConfig config
) {
@NotNull MVCoreConfig config) {
super(mvCommandManager);
this.commandManager = mvCommandManager;
this.worldManager = worldManager;
this.destinationsProvider = destinationsProvider;
registerAsyncCompletion("commands", this::suggestCommands);
registerStaticCompletion("configmodifytype", suggestEnums(ConfigModifyType.class));
registerAsyncCompletion("destinations", this::suggestDestinations);
registerStaticCompletion("environments", suggestEnums(World.Environment.class));
registerAsyncCompletion("flags", this::suggestFlags);
registerStaticCompletion("gamerules", this::suggestGamerules);
registerStaticCompletion("mvconfigs", config.getNodes().getNames());
registerAsyncCompletion("mvworlds", this::suggestMVWorlds);
registerAsyncCompletion("mvworldpropsname", this::suggestMVWorldPropsName);
registerAsyncCompletion("mvworldpropsvalue", this::suggestMVWorldPropsValue);
setDefaultCompletion("configmodifytype", ConfigModifyType.class);
setDefaultCompletion("destinations", ParsedDestination.class);
setDefaultCompletion("flags", String[].class);
setDefaultCompletion("environments", World.Environment.class);
setDefaultCompletion("gamerules", GameRule.class);
setDefaultCompletion("mvworlds", MultiverseWorld.class);
setDefaultCompletion("mvworlds", LoadedMultiverseWorld.class);
}
@@ -152,4 +160,28 @@ public class MVCommandCompletions extends PaperCommandCompletions {
Logging.severe("Invalid MVWorld scope: " + scope);
return Collections.emptyList();
}
private Collection<String> suggestMVWorldPropsName(BukkitCommandCompletionContext context) {
return Try.of(() -> {
MultiverseWorld mvWorld = context.getContextValue(MultiverseWorld.class);
ConfigModifyType modifyType = context.getContextValue(ConfigModifyType.class);
return mvWorld.getConfigurablePropertyNames(modifyType);
}).getOrElse(Collections.emptyList());
}
private Collection<String> suggestMVWorldPropsValue(BukkitCommandCompletionContext context) {
return Try.of(() -> {
MultiverseWorld mvWorld = context.getContextValue(MultiverseWorld.class);
ConfigModifyType modifyType = context.getContextValue(ConfigModifyType.class);
String propertyName = context.getContextValue(String.class);
return mvWorld.suggestPropertyValues(modifyType, propertyName, context.getInput());
}).getOrElse(Collections.emptyList());
}
private <T extends Enum<T>> Collection<String> suggestEnums(Class<T> enumClass) {
return EnumSet.allOf(enumClass).stream()
.map(Enum::name)
.map(String::toLowerCase)
.toList();
}
}
@@ -27,10 +27,11 @@ import org.mvplugins.multiverse.core.display.filters.DefaultContentFilter;
import org.mvplugins.multiverse.core.display.filters.RegexContentFilter;
import org.mvplugins.multiverse.core.utils.PlayerFinder;
import org.mvplugins.multiverse.core.world.LoadedMultiverseWorld;
import org.mvplugins.multiverse.core.world.MultiverseWorld;
import org.mvplugins.multiverse.core.world.WorldManager;
@Service
public class MVCommandContexts extends PaperCommandContexts {
class MVCommandContexts extends PaperCommandContexts {
private final MVCommandManager mvCommandManager;
private final DestinationsProvider destinationsProvider;
@@ -38,12 +39,11 @@ public class MVCommandContexts extends PaperCommandContexts {
private final MVCoreConfig config;
@Inject
public MVCommandContexts(
MVCommandContexts(
MVCommandManager mvCommandManager,
DestinationsProvider destinationsProvider,
WorldManager worldManager,
MVCoreConfig config
) {
MVCoreConfig config) {
super(mvCommandManager);
this.mvCommandManager = mvCommandManager;
this.destinationsProvider = destinationsProvider;
@@ -57,8 +57,9 @@ public class MVCommandContexts extends PaperCommandContexts {
registerContext(GameRule.class, this::parseGameRule);
registerContext(GameRuleValue.class, this::parseGameRuleValue);
registerContext(MVConfigValue.class, this::parseMVConfigValue);
registerIssuerAwareContext(LoadedMultiverseWorld.class, this::parseMVWorld);
registerIssuerAwareContext(LoadedMultiverseWorld[].class, this::parseMVWorldArray);
registerIssuerAwareContext(MultiverseWorld.class, this::parseWorld);
registerIssuerAwareContext(LoadedMultiverseWorld.class, this::parseLoadedWorld);
registerIssuerAwareContext(LoadedMultiverseWorld[].class, this::parseLoadedWorldArray);
registerIssuerAwareContext(Player.class, this::parsePlayer);
registerIssuerAwareContext(Player[].class, this::parsePlayerArray);
}
@@ -162,13 +163,14 @@ public class MVCommandContexts extends PaperCommandContexts {
return new MVConfigValue(resolvedValue);
}
private LoadedMultiverseWorld parseMVWorld(BukkitCommandExecutionContext context) {
private MultiverseWorld parseWorld(BukkitCommandExecutionContext context) {
String resolve = context.getFlagValue("resolve", "");
// Get world based on sender only
if (resolve.equals("issuerOnly")) {
if (context.getIssuer().isPlayer()) {
return worldManager.getLoadedWorld(context.getIssuer().getPlayer().getWorld()).getOrNull();
return worldManager.getWorld(context.getIssuer().getPlayer().getWorld())
.getOrElseThrow(() -> new InvalidCommandArgument("Player is not in a Multiverse World."));
}
if (context.isOptional()) {
return null;
@@ -177,7 +179,7 @@ public class MVCommandContexts extends PaperCommandContexts {
}
String worldName = context.getFirstArg();
LoadedMultiverseWorld world = worldManager.getLoadedWorld(worldName).getOrNull();
MultiverseWorld world = worldManager.getWorld(worldName).getOrNull();
// Get world based on input, fallback to sender if input is not a world
if (resolve.equals("issuerAware")) {
@@ -186,7 +188,8 @@ public class MVCommandContexts extends PaperCommandContexts {
return world;
}
if (context.getIssuer().isPlayer()) {
return worldManager.getLoadedWorld(context.getIssuer().getPlayer().getWorld()).getOrNull();
return worldManager.getWorld(context.getIssuer().getPlayer().getWorld())
.getOrElseThrow(() -> new InvalidCommandArgument("Player is not in a Multiverse World."));
}
if (context.isOptional()) {
return null;
@@ -205,7 +208,53 @@ public class MVCommandContexts extends PaperCommandContexts {
throw new InvalidCommandArgument("World " + worldName + " is not a loaded multiverse world.");
}
private LoadedMultiverseWorld[] parseMVWorldArray(BukkitCommandExecutionContext context) {
private LoadedMultiverseWorld parseLoadedWorld(BukkitCommandExecutionContext context) {
String resolve = context.getFlagValue("resolve", "");
// Get world based on sender only
if (resolve.equals("issuerOnly")) {
if (context.getIssuer().isPlayer()) {
return worldManager.getLoadedWorld(context.getIssuer().getPlayer().getWorld())
.getOrElseThrow(() -> new InvalidCommandArgument("Player is not in a Multiverse World."));
}
if (context.isOptional()) {
return null;
}
throw new InvalidCommandArgument("This command can only be used by a player in a Multiverse World.");
}
String worldName = context.getFirstArg();
LoadedMultiverseWorld world = worldManager.getLoadedWorld(worldName).getOrNull();
// Get world based on input, fallback to sender if input is not a world
if (resolve.equals("issuerAware")) {
if (world != null) {
context.popFirstArg();
return world;
}
if (context.getIssuer().isPlayer()) {
return worldManager.getLoadedWorld(context.getIssuer().getPlayer().getWorld())
.getOrElseThrow(() -> new InvalidCommandArgument("Player is not in a Multiverse World."));
}
if (context.isOptional()) {
return null;
}
throw new InvalidCommandArgument("Player is not in a Multiverse World.");
}
// Get world based on input only
if (world != null) {
context.popFirstArg();
return world;
}
if (context.isOptional()) {
return null;
}
throw new InvalidCommandArgument("World " + worldName + " is not a loaded multiverse world.");
}
private LoadedMultiverseWorld[] parseLoadedWorldArray(BukkitCommandExecutionContext context) {
String resolve = context.getFlagValue("resolve", "");
LoadedMultiverseWorld playerWorld = null;
@@ -309,7 +358,6 @@ public class MVCommandContexts extends PaperCommandContexts {
throw new InvalidCommandArgument("Player " + playerIdentifier + " not found.");
}
private Player[] parsePlayerArray(BukkitCommandExecutionContext context) {
String resolve = context.getFlagValue("resolve", "");
@@ -12,7 +12,7 @@ import org.mvplugins.multiverse.core.commandtools.flags.CommandFlagsManager;
import org.mvplugins.multiverse.core.commandtools.flags.ParsedCommandFlags;
@Contract
public abstract class MultiverseCommand extends BaseCommand {
public abstract class MultiverseCommand extends BaseCommand {
protected final MVCommandManager commandManager;
private String flagGroupName;
@@ -114,12 +114,12 @@ public class MVCoreConfig implements MVConfig {
@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
@@ -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);
}
}
@@ -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);
}
}
@@ -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);
}
}
@@ -1,4 +1,4 @@
package org.mvplugins.multiverse.core.configuration.node;
package org.mvplugins.multiverse.core.configuration.functions;
public interface NodeSerializer<T> {
T deserialize(Object object, Class<T> type);
@@ -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);
}
@@ -0,0 +1,8 @@
package org.mvplugins.multiverse.core.configuration.functions;
import java.util.Collection;
@FunctionalInterface
public interface NodeSuggester {
Collection<String> suggest(String input);
}
@@ -76,7 +76,7 @@ public class CommentedYamlConfigHandle extends FileConfigHandle<CommentedConfigu
valueNode.getDefaultValue())).onFailure(e -> {
Logging.warning("Failed to set node " + valueNode.getPath()
+ " to " + valueNode.getDefaultValue());
setDefault(valueNode);
reset(valueNode);
});
}
});
@@ -0,0 +1,8 @@
package org.mvplugins.multiverse.core.configuration.handle;
public enum ConfigModifyType {
SET,
ADD,
REMOVE,
RESET
}
@@ -1,7 +1,12 @@
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;
import io.vavr.control.Try;
import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.plugin.Plugin;
@@ -10,6 +15,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.ListValueNode;
import org.mvplugins.multiverse.core.configuration.node.NodeGroup;
import org.mvplugins.multiverse.core.configuration.node.ValueNode;
@@ -18,12 +24,12 @@ import org.mvplugins.multiverse.core.configuration.node.ValueNode;
*/
public abstract class GenericConfigHandle<C extends ConfigurationSection> {
protected final @Nullable Logger logger;
protected final @Nullable NodeGroup nodes;
protected final @NotNull NodeGroup nodes;
protected final @Nullable ConfigMigrator migrator;
protected C config;
protected GenericConfigHandle(@Nullable Logger logger, @Nullable NodeGroup nodes, @Nullable ConfigMigrator migrator) {
protected GenericConfigHandle(@Nullable Logger logger, @NotNull NodeGroup nodes, @Nullable ConfigMigrator migrator) {
this.logger = logger;
this.nodes = nodes;
this.migrator = migrator;
@@ -67,21 +73,140 @@ public abstract class GenericConfigHandle<C extends ConfigurationSection> {
});
}
public Collection<String> getPropertyNames() {
return nodes.getNames();
}
public Collection<String> getPropertyNames(ConfigModifyType configModifyType) {
return switch (configModifyType) {
case SET, RESET -> nodes.getNames();
case ADD, REMOVE -> nodes.stream()
.filter(node -> node instanceof ListValueNode)
.map(node -> ((ValueNode<?>) node).getName())
.filter(Option::isDefined)
.map(Option::get)
.toList();
};
}
public Try<Class> getPropertyType(@Nullable String name) {
return nodes.findNode(name, ValueNode.class)
.map(valueNode -> {
if (valueNode instanceof ListValueNode listValueNode) {
return listValueNode.getItemType();
}
return valueNode.getType();
})
.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.
*
* @param name The name 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)
.toTry(() -> new ConfigNodeNotFoundException(name))
.map(node -> get((ValueNode<Object>) node));
}
public Try<Void> 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));
};
}
/**
* 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 String value) {
//noinspection unchecked
return propertyAction(name, ValueNode.class, node ->
node.parseFromString(value).flatMapTry(parsedValue -> set(node, parsedValue)));
}
public Try<Void> addProperty(@Nullable String name, @Nullable String value) {
//noinspection unchecked
return propertyAction(name, ListValueNode.class, node ->
node.parseItemFromString(value).flatMapTry(item -> add(node, item)));
}
public Try<Void> removeProperty(@Nullable String name, @Nullable String value) {
//noinspection unchecked
return propertyAction(name, ListValueNode.class, node ->
node.parseItemFromString(value).flatMapTry(item -> remove(node, item)));
}
public Try<Void> 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<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))
.flatMapTry(action::apply);
}
/**
* Gets the value of a node, if the node has a default value, it will be returned if the node is not found.
*
* @param node The node to get the value of.
* @param <T> The type of the node value.
* @return The value of the node.
*/
public <T> T get(@NotNull ValueNode<T> node) {
@@ -91,48 +216,67 @@ 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 -> {
T oldValue = get(node);
if (node.getSerializer() != null) {
var serialized = node.getSerializer().serialize(value, node.getType());
config.set(node.getPath(), serialized);
} else {
config.set(node.getPath(), value);
}
var serialized = node.getSerializer() != null
? node.getSerializer().serialize(value, node.getType())
: value;
config.set(node.getPath(), serialized);
node.onSetValue(oldValue, get(node));
return null;
});
}
public <I> Try<Void> add(@NotNull ListValueNode<I> node, I value) {
// TODO: Serialize value, Validate value
return Try.run(() -> {
var serialized = node.getItemSerializer() != null
? node.getItemSerializer().serialize(value, node.getItemType())
: value;
List list = get(node);
if (list == null) {
throw new IllegalArgumentException("List is null");
}
list.add(serialized);
config.set(node.getPath(), list);
node.onSetValue(list, get(node));
});
}
public <I> Try<Void> remove(@NotNull ListValueNode<I> node, I value) {
return Try.run(() -> {
var serialized = node.getItemSerializer() != null
? node.getItemSerializer().serialize(value, node.getItemType())
: value;
List list = get(node);
if (list == null) {
throw new IllegalArgumentException("List is null");
}
if (!list.remove(serialized)) {
throw new IllegalArgumentException("Value not found in list");
}
config.set(node.getPath(), list);
node.onSetItemValue(value, null);
});
}
/**
* Sets the default value of a node.
*
* @param node The node to set the default value of.
* @return Empty try if the value was set, try containing an error otherwise.
*/
public void setDefault(@NotNull ValueNode node) {
config.set(node.getPath(), node.getDefaultValue());
public <T> Try<Void> reset(@NotNull ValueNode<T> node) {
return Try.run(() -> config.set(node.getPath(), node.getDefaultValue()));
}
/**
@@ -147,7 +291,8 @@ public abstract class GenericConfigHandle<C extends ConfigurationSection> {
protected @Nullable NodeGroup nodes;
protected @Nullable ConfigMigrator migrator;
protected Builder() {}
protected Builder() {
}
/**
* Sets the logger.
@@ -66,7 +66,7 @@ public class ConfigHeaderNode implements CommentedNode {
comment = "# " + comment;
}
comments.add(comment);
return (B) this;
return self();
}
/**
@@ -77,5 +77,10 @@ public class ConfigHeaderNode implements CommentedNode {
public @NotNull ConfigHeaderNode build() {
return new ConfigHeaderNode(path, comments.toArray(new String[0]));
}
protected B self() {
//noinspection unchecked
return (B) this;
}
}
}
@@ -1,15 +1,26 @@
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;
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.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.
*
* @param <T> The type of the value.
*/
public class ConfigNode<T> extends ConfigHeaderNode implements ValueNode<T> {
@@ -19,38 +30,48 @@ public class ConfigNode<T> extends ConfigHeaderNode implements ValueNode<T> {
*
* @param path The path of the node.
* @param type The type of the value.
* @return The new builder.
* @param <T> The type of the value.
* @return The new builder.
*/
public static @NotNull <T> ConfigNode.Builder<T, ? extends ConfigNode.Builder<T, ?>> builder(
@NotNull String path,
@NotNull Class<T> type
) {
@NotNull Class<T> type) {
return new ConfigNode.Builder<>(path, type);
}
protected final @Nullable String name;
protected final @NotNull Class<T> type;
protected final @Nullable T defaultValue;
protected final @Nullable NodeSerializer<T> serializer;
protected final @Nullable Function<T, Try<Void>> validator;
protected final @Nullable BiConsumer<T, T> onSetValue;
protected final @Nullable Supplier<T> defaultValueSupplier;
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;
protected ConfigNode(
@NotNull String path,
@NotNull String[] comments,
@Nullable String name,
@NotNull Class<T> type,
@Nullable T defaultValue,
@Nullable Supplier<T> defaultValueSupplier,
@Nullable NodeSuggester suggester,
@Nullable NodeStringParser<T> stringParser,
@Nullable NodeSerializer<T> serializer,
@Nullable Function<T, Try<Void>> validator,
@Nullable BiConsumer<T, T> onSetValue
) {
@Nullable BiConsumer<T, T> onSetValue) {
super(path, comments);
this.name = name;
this.type = type;
this.defaultValue = defaultValue;
this.serializer = serializer;
this.defaultValueSupplier = defaultValueSupplier;
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;
}
@@ -76,7 +97,29 @@ public class ConfigNode<T> extends ConfigHeaderNode implements ValueNode<T> {
*/
@Override
public @Nullable T getDefaultValue() {
return defaultValue;
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() {
@@ -111,11 +154,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 T defaultValue;
protected @Nullable Supplier<T> defaultValueSupplier;
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;
@@ -130,9 +174,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;
}
}
/**
@@ -142,8 +183,19 @@ public class ConfigNode<T> extends ConfigHeaderNode implements ValueNode<T> {
* @return This builder.
*/
public @NotNull B defaultValue(@NotNull T defaultValue) {
this.defaultValue = defaultValue;
return (B) this;
this.defaultValueSupplier = () -> defaultValue;
return self();
}
/**
* Sets the default value for this node.
*
* @param defaultValueSupplier The supplier for the default value.
* @return This builder.
*/
public @NotNull B defaultValue(@NotNull Supplier<T> defaultValueSupplier) {
this.defaultValueSupplier = defaultValueSupplier;
return self();
}
/**
@@ -154,17 +206,27 @@ public class ConfigNode<T> extends ConfigHeaderNode implements ValueNode<T> {
*/
public @NotNull B name(@Nullable String name) {
this.name = name;
return (B) this;
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) {
this.serializer = serializer;
return (B) this;
return self();
}
public @NotNull B validator(@NotNull Function<T, Try<Void>> validator) {
this.validator = validator;
return (B) this;
return self();
}
/**
@@ -175,7 +237,7 @@ public class ConfigNode<T> extends ConfigHeaderNode implements ValueNode<T> {
*/
public @NotNull B onSetValue(@NotNull BiConsumer<T, T> onSetValue) {
this.onSetValue = onSetValue;
return (B) this;
return self();
}
/**
@@ -183,7 +245,17 @@ 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);
return new ConfigNode<>(
path,
comments.toArray(new String[0]),
name,
type,
defaultValueSupplier,
suggester,
stringParser,
serializer,
validator,
onSetValue);
}
}
}
@@ -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();
}
}
@@ -0,0 +1,310 @@
package org.mvplugins.multiverse.core.configuration.node;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.function.BiConsumer;
import java.util.function.Function;
import java.util.function.Supplier;
import com.dumptruckman.minecraft.util.Logging;
import io.vavr.Value;
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<I> extends ConfigNode<List<I>> implements ListValueNode<I> {
/**
* Creates a new builder for a {@link ConfigNode}.
*
* @param path The path of the node.
* @param type The type of the value.
* @param <I> The type of the value.
* @return The new builder.
*/
public static @NotNull <I> Builder<I, ? extends Builder<I, ?>> listBuilder(
@NotNull String path,
@NotNull Class<I> type) {
return new Builder<>(path, type);
}
protected final Class<I> itemType;
protected final NodeSuggester itemSuggester;
protected final NodeStringParser<I> itemStringParser;
protected final NodeSerializer<I> itemSerializer;
protected final Function<I, Try<Void>> itemValidator;
protected final BiConsumer<I, I> onSetItemValue;
protected ListConfigNode(
@NotNull String path,
@NotNull String[] comments,
@Nullable String name,
@NotNull Class<List<I>> type,
@Nullable Supplier<List<I>> defaultValueSupplier,
@Nullable NodeSuggester suggester,
@Nullable NodeStringParser<List<I>> stringParser,
@Nullable NodeSerializer<List<I>> serializer,
@Nullable Function<List<I>, Try<Void>> validator,
@Nullable BiConsumer<List<I>, List<I>> onSetValue,
@NotNull Class<I> itemType,
@Nullable NodeSuggester itemSuggester,
@Nullable NodeStringParser<I> itemStringParser,
@Nullable NodeSerializer<I> itemSerializer,
@Nullable Function<I, Try<Void>> itemValidator,
@Nullable BiConsumer<I, I> onSetItemValue) {
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;
if (this.itemSuggester != null && this.suggester == null) {
setDefaultSuggester();
}
if (this.itemStringParser != null && this.stringParser == null) {
Logging.fine("Setting default string parser for list node " + path);
setDefaultStringParser();
}
if (this.itemValidator != null && this.validator == null) {
setDefaultValidator();
}
if (this.itemSerializer != null && this.serializer == null) {
setDefaultSerialiser();
}
if (this.onSetItemValue != null && this.onSetValue == null) {
setDefaultOnSetValue();
}
}
private void setDefaultSuggester() {
this.suggester = input -> {
int lastIndexOf = input.lastIndexOf(',');
if (lastIndexOf == -1) {
return itemSuggester.suggest(input);
}
String lastInput = input.substring(lastIndexOf + 1);
String inputBeforeLast = input.substring(0, lastIndexOf + 1);
Set<String> inputs = Set.of(inputBeforeLast.split(","));
return itemSuggester.suggest(lastInput).stream()
.filter(item -> !inputs.contains(item))
.map(item -> inputBeforeLast + item)
.toList();
};
}
private void setDefaultStringParser() {
this.stringParser = (input, type) -> {
if (input == null) {
return Try.failure(new IllegalArgumentException("Input cannot be null"));
}
return Try.sequence(Arrays.stream(input.split(","))
.map(inputItem -> itemStringParser.parse(inputItem, itemType))
.toList()).map(Value::toJavaList);
};
}
private void setDefaultValidator() {
this.validator = value -> {
if (value != null) {
return Try.sequence(value.stream().map(itemValidator).toList()).map(v -> null);
}
return Try.success(null);
};
}
private void setDefaultSerialiser() {
this.serializer = new NodeSerializer<>() {
@Override
public List<I> deserialize(Object object, Class<List<I>> type) {
if (object instanceof List list) {
return list.stream().map(item -> itemSerializer.deserialize(item, itemType)).toList();
}
return new ArrayList<>();
}
@Override
public Object serialize(List<I> object, Class<List<I>> type) {
if (object != null) {
return object.stream().map(item -> itemSerializer.serialize(item, itemType)).toList();
}
return new ArrayList<>();
}
};
}
private void setDefaultOnSetValue() {
this.onSetValue = (oldValue, newValue) -> {
if (oldValue != null) {
oldValue.stream()
.filter(value -> !newValue.contains(value))
.forEach(item -> onSetItemValue.accept(item, null));
}
newValue.forEach(item -> onSetItemValue.accept(null, item));
};
}
/**
* {@inheritDoc}
*/
@Override
public @NotNull Class<I> getItemType() {
return itemType;
}
/**
* {@inheritDoc}
*/
@Override
public @NotNull Collection<String> suggestItem(@Nullable String input) {
if (itemSuggester != null) {
return itemSuggester.suggest(input);
}
return Collections.emptyList();
}
/**
* {@inheritDoc}
*/
@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}
*/
@Override
public @Nullable NodeSerializer<I> getItemSerializer() {
return itemSerializer;
}
/**
* {@inheritDoc}
*/
@Override
public Try<Void> validateItem(@Nullable I value) {
if (itemValidator != null) {
return itemValidator.apply(value);
}
return Try.success(null);
}
/**
* {@inheritDoc}
*/
@Override
public void onSetItemValue(@Nullable I oldValue, @Nullable I newValue) {
if (onSetItemValue != null) {
onSetItemValue.accept(oldValue, newValue);
}
}
public static class Builder<I, B extends ListConfigNode.Builder<I, B>> extends ConfigNode.Builder<List<I>, B> {
protected final @NotNull Class<I> itemType;
protected @Nullable NodeSuggester itemSuggester;
protected @Nullable NodeStringParser<I> itemStringParser;
protected @Nullable NodeSerializer<I> itemSerializer;
protected @Nullable Function<I, Try<Void>> itemValidator;
protected @Nullable BiConsumer<I, I> onSetItemValue;
/**
* Creates a new builder.
*
* @param path The path of the node.
* @param itemType The type of the item value in list.
*/
protected Builder(@NotNull String path, @NotNull Class<I> itemType) {
//noinspection unchecked
super(path, (Class<List<I>>) (Object) List.class);
this.itemType = itemType;
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.
*
* @param serializer The serializer for the node.
* @return This builder.
*/
public @NotNull B itemSerializer(@NotNull NodeSerializer<I> serializer) {
this.itemSerializer = serializer;
return self();
}
/**
* Sets the validator for the node.
*
* @param itemValidator The validator for the node.
* @return This builder.
*/
public @NotNull B itemValidator(@NotNull Function<I, Try<Void>> itemValidator) {
this.itemValidator = itemValidator;
return self();
}
/**
* Sets the onSetValue for the node.
*
* @param onSetItemValue The onSetValue for the node.
* @return This builder.
*/
public @NotNull B onSetItemValue(@Nullable BiConsumer<I, I> onSetItemValue) {
this.onSetItemValue = onSetItemValue;
return self();
}
/**
* {@inheritDoc}
*/
@Override
public @NotNull ListConfigNode<I> build() {
return new ListConfigNode<>(
path,
comments.toArray(new String[0]),
name,
type,
defaultValueSupplier,
suggester,
stringParser,
serializer,
validator,
onSetValue,
itemType,
itemSuggester, itemStringParser, itemSerializer,
itemValidator,
onSetItemValue);
}
}
}
@@ -0,0 +1,59 @@
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<I> extends ValueNode<List<I>> {
/**
* Gets the class type of list item.
*
* @return The class type of list item.
*/
@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.
*
* @return The serializer for this node.
*/
@Nullable NodeSerializer<I> getItemSerializer();
/**
* Validates the value of this node.
*
* @param value The value to validate.
* @return True if the value is valid, false otherwise.
*/
Try<Void> validateItem(@Nullable I value);
/**
* Called when the value of this node is set.
*
* @param oldValue The old value.
* @param newValue The new value.
*/
void onSetItemValue(@Nullable I oldValue, @Nullable I newValue);
}
@@ -9,6 +9,7 @@ import java.util.Map;
import io.github.townyadvanced.commentedconfiguration.setting.CommentedNode;
import io.vavr.control.Option;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
/**
* A collection of {@link CommentedNode}s, with mappings to nodes by name.
@@ -49,10 +50,6 @@ public class NodeGroup implements Collection<Node> {
return nodesMap.keySet();
}
public Map<String, Node> getNodesMap() {
return nodesMap;
}
/**
* Gets the node with the given name.
*
@@ -68,9 +65,10 @@ public class NodeGroup implements Collection<Node> {
*
* @param name The name of the node to get.
* @param type The type of the node to get.
* @param <T> The type of the node.
* @return The node with the given name, or {@link Option.None} if no node with the given name exists.
*/
public <T extends Node> Option<T> findNode(String name, Class<T> type) {
public <T extends Node> @NotNull Option<T> findNode(@Nullable String name, @NotNull Class<T> type) {
return Option.of(nodesMap.get(name)).map(node -> type.isAssignableFrom(node.getClass()) ? (T) node : null);
}
@@ -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.
*
@@ -11,8 +11,10 @@ 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;
import org.mvplugins.multiverse.core.world.config.AllowedPortalType;
import org.mvplugins.multiverse.core.world.config.WorldConfig;
@@ -66,6 +68,15 @@ public class MultiverseWorld {
return worldConfig.getConfigurablePropertyNames();
}
public Collection<String> getConfigurablePropertyNames(ConfigModifyType configModifyType) {
return worldConfig.getConfigurablePropertyNames(configModifyType);
}
public Collection<String> suggestPropertyValues(
@NotNull ConfigModifyType type, @Nullable String name, @Nullable String input) {
return worldConfig.suggestPropertyValues(type, name, input);
}
/**
* Gets a property on this world.
*
@@ -76,6 +87,10 @@ public class MultiverseWorld {
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.
*
@@ -737,6 +737,17 @@ public class WorldManager {
return !isLoadedWorld(worldName) && isWorld(worldName);
}
/**
* Get a world that may or may not be loaded. It will an {@link LoadedMultiverseWorld} if the world is loaded,
* otherwise returns an {@link MultiverseWorld} instance.
*
* @param world The bukkit world to get.
* @return The world if it exists.
*/
public Option<MultiverseWorld> getWorld(@Nullable World world) {
return world == null ? Option.none() : getWorld(world.getName());
}
/**
* Get a world that may or may not be loaded. It will an {@link LoadedMultiverseWorld} if the world is loaded,
* otherwise returns an {@link MultiverseWorld} instance.
@@ -14,6 +14,8 @@ import org.bukkit.configuration.ConfigurationSection;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.mvplugins.multiverse.core.MultiverseCore;
import org.mvplugins.multiverse.core.configuration.handle.ConfigModifyType;
import org.mvplugins.multiverse.core.configuration.handle.ConfigurationSectionHandle;
import org.mvplugins.multiverse.core.configuration.migration.BooleanMigratorAction;
import org.mvplugins.multiverse.core.configuration.migration.ConfigMigrator;
@@ -23,7 +25,6 @@ import org.mvplugins.multiverse.core.configuration.migration.MoveMigratorAction;
import org.mvplugins.multiverse.core.configuration.migration.NullStringMigratorAction;
import org.mvplugins.multiverse.core.configuration.migration.VersionMigrator;
import org.mvplugins.multiverse.core.world.LoadedMultiverseWorld;
import org.mvplugins.multiverse.core.world.helpers.EnforcementHandler;
/**
* Represents a world configuration.
@@ -37,9 +38,9 @@ public final class WorldConfig {
WorldConfig(
@NotNull String worldName,
@NotNull ConfigurationSection configSection,
@NotNull EnforcementHandler enforcementHandler) {
@NotNull MultiverseCore multiverseCore) {
this.worldName = worldName;
this.configNodes = new WorldConfigNodes(enforcementHandler);
this.configNodes = new WorldConfigNodes(multiverseCore);
this.configHandle = ConfigurationSectionHandle.builder(configSection)
.logger(Logging.getLogger())
.nodes(configNodes.getNodes())
@@ -115,15 +116,32 @@ public final class WorldConfig {
}
public Collection<String> getConfigurablePropertyNames() {
return configNodes.getNodes().getNames();
return configHandle.getPropertyNames();
}
public Collection<String> getConfigurablePropertyNames(ConfigModifyType configModifyType) {
return configHandle.getPropertyNames(configModifyType);
}
public Try<Class> getPropertyType(String 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) {
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) {
return configHandle.set(name, value);
return configHandle.setProperty(name, value);
}
public boolean getAdjustSpawn() {
@@ -1,8 +1,5 @@
package org.mvplugins.multiverse.core.world.config;
import java.util.ArrayList;
import java.util.List;
import org.bukkit.Difficulty;
import org.bukkit.GameMode;
import org.bukkit.Location;
@@ -10,11 +7,14 @@ import org.bukkit.Material;
import org.bukkit.World;
import org.jetbrains.annotations.NotNull;
import org.mvplugins.multiverse.core.MultiverseCore;
import org.mvplugins.multiverse.core.configuration.node.ConfigNode;
import org.mvplugins.multiverse.core.configuration.node.ListConfigNode;
import org.mvplugins.multiverse.core.configuration.node.Node;
import org.mvplugins.multiverse.core.configuration.node.NodeGroup;
import org.mvplugins.multiverse.core.world.config.AllowedPortalType;
import org.mvplugins.multiverse.core.world.LoadedMultiverseWorld;
import org.mvplugins.multiverse.core.world.MultiverseWorld;
import org.mvplugins.multiverse.core.world.WorldManager;
import org.mvplugins.multiverse.core.world.helpers.EnforcementHandler;
/**
@@ -25,10 +25,12 @@ public class WorldConfigNodes {
private final NodeGroup nodes = new NodeGroup();
private EnforcementHandler enforcementHandler;
private WorldManager worldManager;
private LoadedMultiverseWorld world = null;
WorldConfigNodes(@NotNull EnforcementHandler enforcementHandler) {
this.enforcementHandler = enforcementHandler;
WorldConfigNodes(@NotNull MultiverseCore multiverseCore) {
this.enforcementHandler = multiverseCore.getService(EnforcementHandler.class);
this.worldManager = multiverseCore.getService(WorldManager.class);
}
LoadedMultiverseWorld getWorld() {
@@ -212,9 +214,8 @@ public class WorldConfigNodes {
})
.build());
final ConfigNode<List> SPAWNING_ANIMALS_EXCEPTIONS = node(ConfigNode
.builder("spawning.animals.exceptions", List.class)
.defaultValue(new ArrayList<>())
final ListConfigNode<String> SPAWNING_ANIMALS_EXCEPTIONS = node(ListConfigNode
.listBuilder("spawning.animals.exceptions", String.class)
.name("spawning-animals-exceptions")
.build());
@@ -238,14 +239,14 @@ public class WorldConfigNodes {
})
.build());
final ConfigNode<List> SPAWNING_MONSTERS_EXCEPTIONS = node(ConfigNode
.builder("spawning.monsters.exceptions", List.class)
.defaultValue(new ArrayList<>())
final ListConfigNode<String> SPAWNING_MONSTERS_EXCEPTIONS = node(ListConfigNode
.listBuilder("spawning.monsters.exceptions", String.class)
.name("spawning-monsters-exceptions")
.build());
final ConfigNode<List> WORLD_BLACKLIST = node(ConfigNode.builder("world-blacklist", List.class)
.defaultValue(new ArrayList<>())
final ListConfigNode<String> WORLD_BLACKLIST = node(ListConfigNode
.listBuilder("world-blacklist", String.class)
.itemSuggester(input -> worldManager.getWorlds().stream().map(MultiverseWorld::getName).toList())
.build());
final ConfigNode<Double> VERSION = node(ConfigNode.builder("version", Double.class)
@@ -33,16 +33,14 @@ public final class WorldsConfigManager {
private final Map<String, WorldConfig> worldConfigMap;
private final File worldConfigFile;
private final MultiverseCore multiverseCore;
private YamlConfiguration worldsConfig;
private final EnforcementHandler enforcementHandler;
@Inject
WorldsConfigManager(@NotNull MultiverseCore core, @NotNull EnforcementHandler enforcementHandler) {
WorldsConfigManager(@NotNull MultiverseCore multiverseCore) {
worldConfigMap = new HashMap<>();
worldConfigFile = core.getDataFolder().toPath().resolve(CONFIG_FILENAME).toFile();
this.enforcementHandler = enforcementHandler;
worldConfigFile = multiverseCore.getDataFolder().toPath().resolve(CONFIG_FILENAME).toFile();
this.multiverseCore = multiverseCore;
}
/**
@@ -128,7 +126,7 @@ public final class WorldsConfigManager {
WorldConfig newWorldConfig = new WorldConfig(
worldName,
getWorldConfigSection(worldName),
enforcementHandler);
multiverseCore);
worldConfigMap.put(worldName, newWorldConfig);
newWorldsAdded.add(newWorldConfig);
});
@@ -183,7 +181,7 @@ public final class WorldsConfigManager {
if (worldConfigMap.containsKey(worldName)) {
throw new IllegalArgumentException("WorldConfig for world " + worldName + " already exists.");
}
WorldConfig worldConfig = new WorldConfig(worldName, getWorldConfigSection(worldName), enforcementHandler);
WorldConfig worldConfig = new WorldConfig(worldName, getWorldConfigSection(worldName), multiverseCore);
worldConfigMap.put(worldName, worldConfig);
return worldConfig;
}