From cd65c525382966fb4af49d9348ed5d27697c1123 Mon Sep 17 00:00:00 2001 From: Siphalor Date: Sun, 2 Nov 2025 09:26:17 +0100 Subject: [PATCH] feat(default-exts): Introduce presets extensions, use in validation fallback --- .../presets/api/PresetsExtension.java | 41 +++++++++++++ .../presets/api/package-info.java | 4 ++ .../presets/impl/PresetsExtensionImpl.java | 54 +++++++++++++++++ .../presets/impl/package-info.java | 6 ++ .../api/ValidationFallbackExtension.java | 13 +--- .../impl/ValidationFallbackExtensionImpl.java | 59 ++++++++----------- .../ValidationFallbackExtensionImplTest.java | 9 ++- 7 files changed, 134 insertions(+), 52 deletions(-) create mode 100644 tweed5/default-extensions/src/main/java/de/siphalor/tweed5/defaultextensions/presets/api/PresetsExtension.java create mode 100644 tweed5/default-extensions/src/main/java/de/siphalor/tweed5/defaultextensions/presets/api/package-info.java create mode 100644 tweed5/default-extensions/src/main/java/de/siphalor/tweed5/defaultextensions/presets/impl/PresetsExtensionImpl.java create mode 100644 tweed5/default-extensions/src/main/java/de/siphalor/tweed5/defaultextensions/presets/impl/package-info.java diff --git a/tweed5/default-extensions/src/main/java/de/siphalor/tweed5/defaultextensions/presets/api/PresetsExtension.java b/tweed5/default-extensions/src/main/java/de/siphalor/tweed5/defaultextensions/presets/api/PresetsExtension.java new file mode 100644 index 0000000..0552cb6 --- /dev/null +++ b/tweed5/default-extensions/src/main/java/de/siphalor/tweed5/defaultextensions/presets/api/PresetsExtension.java @@ -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 DEFAULT = PresetsExtensionImpl.class; + String EXTENSION_ID = "presets"; + + String DEFAULT_PRESET_NAME = "default"; + + @Override + default String getId() { + return EXTENSION_ID; + } + + static , T> Consumer 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 , T> Function presetValue(String name) { + return entry -> { + PresetsExtension extension = entry.container().extension(PresetsExtension.class) + .orElseThrow(() -> new IllegalStateException("No presets extension registered")); + return extension.presetValue(entry, name); + }; + } + + void presetValue(ConfigEntry entry, String name, T value); + + @Nullable T presetValue(ConfigEntry entry, String name); +} diff --git a/tweed5/default-extensions/src/main/java/de/siphalor/tweed5/defaultextensions/presets/api/package-info.java b/tweed5/default-extensions/src/main/java/de/siphalor/tweed5/defaultextensions/presets/api/package-info.java new file mode 100644 index 0000000..3dc9100 --- /dev/null +++ b/tweed5/default-extensions/src/main/java/de/siphalor/tweed5/defaultextensions/presets/api/package-info.java @@ -0,0 +1,4 @@ +@NullMarked +package de.siphalor.tweed5.defaultextensions.presets.api; + +import org.jspecify.annotations.NullMarked; diff --git a/tweed5/default-extensions/src/main/java/de/siphalor/tweed5/defaultextensions/presets/impl/PresetsExtensionImpl.java b/tweed5/default-extensions/src/main/java/de/siphalor/tweed5/defaultextensions/presets/impl/PresetsExtensionImpl.java new file mode 100644 index 0000000..78c4910 --- /dev/null +++ b/tweed5/default-extensions/src/main/java/de/siphalor/tweed5/defaultextensions/presets/impl/PresetsExtensionImpl.java @@ -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> presetsDataAccess; + + public PresetsExtensionImpl(TweedExtensionSetupContext setupContext) { + //noinspection unchecked + presetsDataAccess = setupContext.registerEntryExtensionData((Class>)(Class) Map.class); + } + + @Override + public void presetValue(ConfigEntry 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 void visitEntry(ConfigEntry entry, U value) { + getOrCreatePresetsData(entry.extensionsData()).put(name, value); + } + }, value); + } + + @Override + public @Nullable T presetValue(ConfigEntry entry, String name) { + //noinspection unchecked + return (T) getPresetsData(entry.extensionsData()).get(name); + } + + private Map getOrCreatePresetsData(Patchwork extensionsData) { + Map data = extensionsData.get(presetsDataAccess); + if (data == null) { + extensionsData.set(presetsDataAccess, data = new HashMap<>()); + } + return data; + } + + private Map getPresetsData(Patchwork extensionsData) { + Map data = extensionsData.get(presetsDataAccess); + return data != null ? data : Collections.emptyMap(); + } +} diff --git a/tweed5/default-extensions/src/main/java/de/siphalor/tweed5/defaultextensions/presets/impl/package-info.java b/tweed5/default-extensions/src/main/java/de/siphalor/tweed5/defaultextensions/presets/impl/package-info.java new file mode 100644 index 0000000..49f486d --- /dev/null +++ b/tweed5/default-extensions/src/main/java/de/siphalor/tweed5/defaultextensions/presets/impl/package-info.java @@ -0,0 +1,6 @@ +@ApiStatus.Internal +@NullMarked +package de.siphalor.tweed5.defaultextensions.presets.impl; + +import org.jetbrains.annotations.ApiStatus; +import org.jspecify.annotations.NullMarked; diff --git a/tweed5/default-extensions/src/main/java/de/siphalor/tweed5/defaultextensions/validationfallback/api/ValidationFallbackExtension.java b/tweed5/default-extensions/src/main/java/de/siphalor/tweed5/defaultextensions/validationfallback/api/ValidationFallbackExtension.java index d5fd857..5d7b59a 100644 --- a/tweed5/default-extensions/src/main/java/de/siphalor/tweed5/defaultextensions/validationfallback/api/ValidationFallbackExtension.java +++ b/tweed5/default-extensions/src/main/java/de/siphalor/tweed5/defaultextensions/validationfallback/api/ValidationFallbackExtension.java @@ -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 DEFAULT = ValidationFallbackExtensionImpl.class; String EXTENSION_ID = "validation-fallback"; @@ -15,13 +12,5 @@ public interface ValidationFallbackExtension extends TweedExtension { return EXTENSION_ID; } - static , T> Consumer validationFallbackValue(T value) { - return entry -> { - ValidationFallbackExtension extension = entry.container().extension(ValidationFallbackExtension.class) - .orElseThrow(() -> new IllegalStateException("ValidationFallbackExtension is not registered")); - extension.setFallbackValue(entry, value); - }; - } - - void setFallbackValue(ConfigEntry entry, T value); + void fallbackToPreset(String presetName); } diff --git a/tweed5/default-extensions/src/main/java/de/siphalor/tweed5/defaultextensions/validationfallback/impl/ValidationFallbackExtensionImpl.java b/tweed5/default-extensions/src/main/java/de/siphalor/tweed5/defaultextensions/validationfallback/impl/ValidationFallbackExtensionImpl.java index 4b516f3..2a78345 100644 --- a/tweed5/default-extensions/src/main/java/de/siphalor/tweed5/defaultextensions/validationfallback/impl/ValidationFallbackExtensionImpl.java +++ b/tweed5/default-extensions/src/main/java/de/siphalor/tweed5/defaultextensions/validationfallback/impl/ValidationFallbackExtensionImpl.java @@ -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 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 void setFallbackValue(ConfigEntry 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 { @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 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 String description(ConfigEntry 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; } }; } diff --git a/tweed5/default-extensions/src/test/java/de/siphalor/tweed5/defaultextensions/validationfallback/impl/ValidationFallbackExtensionImplTest.java b/tweed5/default-extensions/src/test/java/de/siphalor/tweed5/defaultextensions/validationfallback/impl/ValidationFallbackExtensionImplTest.java index 55f9b8d..9a24938 100644 --- a/tweed5/default-extensions/src/test/java/de/siphalor/tweed5/defaultextensions/validationfallback/impl/ValidationFallbackExtensionImplTest.java +++ b/tweed5/default-extensions/src/test/java/de/siphalor/tweed5/defaultextensions/validationfallback/impl/ValidationFallbackExtensionImplTest.java @@ -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();