Multiverse-Core/src/main/java/org/mvplugins/multiverse/core/configuration/node/ListConfigNode.java

311 lines
11 KiB
Java

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