feat(default-exts): Introduce presets extensions, use in validation fallback

This commit is contained in:
2025-11-02 09:26:17 +01:00
parent 7c625e6cca
commit cd65c52538
7 changed files with 134 additions and 52 deletions

View File

@@ -0,0 +1,41 @@
package de.siphalor.tweed5.defaultextensions.presets.api;
import de.siphalor.tweed5.core.api.entry.ConfigEntry;
import de.siphalor.tweed5.core.api.extension.TweedExtension;
import de.siphalor.tweed5.defaultextensions.presets.impl.PresetsExtensionImpl;
import org.jspecify.annotations.Nullable;
import java.util.function.Consumer;
import java.util.function.Function;
public interface PresetsExtension extends TweedExtension {
Class<? extends PresetsExtension> DEFAULT = PresetsExtensionImpl.class;
String EXTENSION_ID = "presets";
String DEFAULT_PRESET_NAME = "default";
@Override
default String getId() {
return EXTENSION_ID;
}
static <C extends ConfigEntry<T>, T> Consumer<C> presetValue(String name, T value) {
return entry -> {
PresetsExtension extension = entry.container().extension(PresetsExtension.class)
.orElseThrow(() -> new IllegalStateException("No presets extension registered"));
extension.presetValue(entry, name, value);
};
}
static <C extends ConfigEntry<T>, T> Function<C, @Nullable T> presetValue(String name) {
return entry -> {
PresetsExtension extension = entry.container().extension(PresetsExtension.class)
.orElseThrow(() -> new IllegalStateException("No presets extension registered"));
return extension.presetValue(entry, name);
};
}
<T extends @Nullable Object> void presetValue(ConfigEntry<T> entry, String name, T value);
<T extends @Nullable Object> @Nullable T presetValue(ConfigEntry<T> entry, String name);
}

View File

@@ -0,0 +1,4 @@
@NullMarked
package de.siphalor.tweed5.defaultextensions.presets.api;
import org.jspecify.annotations.NullMarked;

View File

@@ -0,0 +1,54 @@
package de.siphalor.tweed5.defaultextensions.presets.impl;
import de.siphalor.tweed5.core.api.entry.ConfigEntry;
import de.siphalor.tweed5.core.api.entry.ConfigEntryValueVisitor;
import de.siphalor.tweed5.core.api.extension.TweedExtensionSetupContext;
import de.siphalor.tweed5.defaultextensions.presets.api.PresetsExtension;
import de.siphalor.tweed5.patchwork.api.Patchwork;
import de.siphalor.tweed5.patchwork.api.PatchworkPartAccess;
import org.jspecify.annotations.Nullable;
import java.util.*;
public class PresetsExtensionImpl implements PresetsExtension {
private final PatchworkPartAccess<Map<String, Object>> presetsDataAccess;
public PresetsExtensionImpl(TweedExtensionSetupContext setupContext) {
//noinspection unchecked
presetsDataAccess = setupContext.registerEntryExtensionData((Class<Map<String, Object>>)(Class<?>) Map.class);
}
@Override
public <T extends @Nullable Object> void presetValue(ConfigEntry<T> entry, String name, T value) {
if (value != null && !entry.valueClass().isAssignableFrom(value.getClass())) {
throw new IllegalArgumentException(
"The preset value is not of the expected type " + entry.valueClass().getName()
);
}
entry.visitInOrder(new ConfigEntryValueVisitor() {
@Override
public <U> void visitEntry(ConfigEntry<U> entry, U value) {
getOrCreatePresetsData(entry.extensionsData()).put(name, value);
}
}, value);
}
@Override
public <T> @Nullable T presetValue(ConfigEntry<T> entry, String name) {
//noinspection unchecked
return (T) getPresetsData(entry.extensionsData()).get(name);
}
private Map<String, Object> getOrCreatePresetsData(Patchwork extensionsData) {
Map<String, Object> data = extensionsData.get(presetsDataAccess);
if (data == null) {
extensionsData.set(presetsDataAccess, data = new HashMap<>());
}
return data;
}
private Map<String, Object> getPresetsData(Patchwork extensionsData) {
Map<String, Object> data = extensionsData.get(presetsDataAccess);
return data != null ? data : Collections.emptyMap();
}
}

View File

@@ -0,0 +1,6 @@
@ApiStatus.Internal
@NullMarked
package de.siphalor.tweed5.defaultextensions.presets.impl;
import org.jetbrains.annotations.ApiStatus;
import org.jspecify.annotations.NullMarked;

View File

@@ -1,11 +1,8 @@
package de.siphalor.tweed5.defaultextensions.validationfallback.api;
import de.siphalor.tweed5.core.api.entry.ConfigEntry;
import de.siphalor.tweed5.core.api.extension.TweedExtension;
import de.siphalor.tweed5.defaultextensions.validationfallback.impl.ValidationFallbackExtensionImpl;
import java.util.function.Consumer;
public interface ValidationFallbackExtension extends TweedExtension {
Class<? extends ValidationFallbackExtension> DEFAULT = ValidationFallbackExtensionImpl.class;
String EXTENSION_ID = "validation-fallback";
@@ -15,13 +12,5 @@ public interface ValidationFallbackExtension extends TweedExtension {
return EXTENSION_ID;
}
static <C extends ConfigEntry<T>, T> Consumer<C> validationFallbackValue(T value) {
return entry -> {
ValidationFallbackExtension extension = entry.container().extension(ValidationFallbackExtension.class)
.orElseThrow(() -> new IllegalStateException("ValidationFallbackExtension is not registered"));
extension.setFallbackValue(entry, value);
};
}
<T> void setFallbackValue(ConfigEntry<T> entry, T value);
void fallbackToPreset(String presetName);
}

View File

@@ -1,16 +1,16 @@
package de.siphalor.tweed5.defaultextensions.validationfallback.impl;
import de.siphalor.tweed5.core.api.container.ConfigContainer;
import de.siphalor.tweed5.core.api.entry.ConfigEntry;
import de.siphalor.tweed5.core.api.extension.TweedExtensionSetupContext;
import de.siphalor.tweed5.core.api.middleware.Middleware;
import de.siphalor.tweed5.defaultextensions.presets.api.PresetsExtension;
import de.siphalor.tweed5.defaultextensions.validation.api.ConfigEntryValidator;
import de.siphalor.tweed5.defaultextensions.validation.api.ValidationProvidingExtension;
import de.siphalor.tweed5.defaultextensions.validation.api.result.ValidationIssue;
import de.siphalor.tweed5.defaultextensions.validation.api.result.ValidationIssueLevel;
import de.siphalor.tweed5.defaultextensions.validation.api.result.ValidationResult;
import de.siphalor.tweed5.defaultextensions.validationfallback.api.ValidationFallbackExtension;
import de.siphalor.tweed5.patchwork.api.PatchworkPartAccess;
import lombok.Data;
import org.jspecify.annotations.Nullable;
import java.util.ArrayList;
@@ -19,25 +19,27 @@ import java.util.Set;
import java.util.stream.Collectors;
public class ValidationFallbackExtensionImpl implements ValidationFallbackExtension, ValidationProvidingExtension {
private final ConfigContainer<?> configContainer;
private @Nullable PresetsExtension presetsExtension;
private final PatchworkPartAccess<CustomEntryData> customEntryDataAccess;
private String fallbackPresetName = PresetsExtension.DEFAULT_PRESET_NAME;
public ValidationFallbackExtensionImpl(TweedExtensionSetupContext context) {
customEntryDataAccess = context.registerEntryExtensionData(CustomEntryData.class);
public ValidationFallbackExtensionImpl(TweedExtensionSetupContext context, ConfigContainer<?> configContainer) {
this.configContainer = configContainer;
context.registerExtension(PresetsExtension.class);
}
private PresetsExtension getOrResolvePresetsExtension() {
if (presetsExtension == null) {
presetsExtension = configContainer.extension(PresetsExtension.class)
.orElseThrow(() -> new IllegalStateException("No presets extension registered"));
}
return presetsExtension;
}
@Override
public <T> void setFallbackValue(ConfigEntry<T> entry, T value) {
getOrCreateCustomEntryData(entry).fallbackValue(value);
}
private CustomEntryData getOrCreateCustomEntryData(ConfigEntry<?> entry) {
CustomEntryData customEntryData = entry.extensionsData().get(customEntryDataAccess);
if (customEntryData == null) {
customEntryData = new CustomEntryData();
entry.extensionsData().set(customEntryDataAccess, customEntryData);
}
return customEntryData;
public void fallbackToPreset(String presetName) {
this.fallbackPresetName = presetName;
}
@Override
@@ -45,11 +47,6 @@ public class ValidationFallbackExtensionImpl implements ValidationFallbackExtens
return new ValidationFallbackMiddleware();
}
@Data
private static class CustomEntryData {
@Nullable Object fallbackValue;
}
private class ValidationFallbackMiddleware implements Middleware<ConfigEntryValidator> {
@Override
public String id() {
@@ -75,16 +72,12 @@ public class ValidationFallbackExtensionImpl implements ValidationFallbackExtens
if (!result.hasError()) {
return result;
}
CustomEntryData entryData = configEntry.extensionsData().get(customEntryDataAccess);
if (entryData == null) {
return result;
}
PresetsExtension presetsExtension = getOrResolvePresetsExtension();
Object fallbackValue = entryData.fallbackValue();
T fallbackValue = presetsExtension.presetValue(configEntry, fallbackPresetName);
if (fallbackValue != null) {
if (configEntry.valueClass().isInstance(fallbackValue)) {
//noinspection unchecked
fallbackValue = configEntry.deepCopy((T) fallbackValue);
fallbackValue = configEntry.deepCopy(fallbackValue);
} else {
ArrayList<ValidationIssue> issues = new ArrayList<>(result.issues());
issues.add(new ValidationIssue(
@@ -96,9 +89,8 @@ public class ValidationFallbackExtensionImpl implements ValidationFallbackExtens
}
}
//noinspection unchecked
return ValidationResult.withIssues(
(T) fallbackValue,
fallbackValue,
result.issues().stream()
.map(issue -> new ValidationIssue(
issue.message(),
@@ -112,11 +104,8 @@ public class ValidationFallbackExtensionImpl implements ValidationFallbackExtens
@Override
public <T> String description(ConfigEntry<T> configEntry) {
CustomEntryData entryData = configEntry.extensionsData().get(customEntryDataAccess);
if (entryData == null) {
return inner.description(configEntry);
}
return inner.description(configEntry) + "\n\nDefault/Fallback value: " + entryData.fallbackValue();
T fallbackValue = getOrResolvePresetsExtension().presetValue(configEntry, fallbackPresetName);
return inner.description(configEntry) + "\n\nDefault/Fallback value: " + fallbackValue;
}
};
}

View File

@@ -20,7 +20,6 @@ import org.jspecify.annotations.Nullable;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.CsvSource;
import org.junit.jupiter.params.provider.ValueSource;
import java.io.StringReader;
@@ -29,11 +28,9 @@ import java.util.Collections;
import static de.siphalor.tweed5.data.extension.api.ReadWriteExtension.*;
import static de.siphalor.tweed5.data.extension.api.readwrite.TweedEntryReaderWriters.*;
import static de.siphalor.tweed5.defaultextensions.validation.api.ValidationExtension.validate;
import static de.siphalor.tweed5.defaultextensions.presets.api.PresetsExtension.presetValue;
import static de.siphalor.tweed5.defaultextensions.validation.api.ValidationExtension.validators;
import static de.siphalor.tweed5.defaultextensions.validationfallback.api.ValidationFallbackExtension.validationFallbackValue;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
class ValidationFallbackExtensionImplTest {
private DefaultConfigContainer<@Nullable Integer> configContainer;
@@ -106,7 +103,9 @@ class ValidationFallbackExtensionImplTest {
}
}
))
.apply(validationFallbackValue(3));
.apply(presetValue("custom", 3));
configContainer.extension(ValidationFallbackExtension.class).orElseThrow().fallbackToPreset("custom");
configContainer.attachTree(intEntry);
configContainer.initialize();