Compare commits

15 Commits
v0.7 ... main

Author SHA1 Message Date
52e2a52468 refactor!(core): Simplify ConfigEntryValueVisitor by introducing SubEntryKey instead of passing values separately 2026-04-13 23:26:40 +02:00
2f4011c144 refactor!(serde-extension): Replace reader/writer chain APIs with readSubEntry and writeSubEntry methods 2026-04-12 21:06:56 +02:00
a413fab8d5 refactor!(core): Add context parameter to Middleware 2026-04-12 18:27:07 +02:00
615d3810a0 refactor!(serde-extension): Fundamentally change error handling for data reading 2026-04-04 22:33:15 +02:00
e6ef13ff7f build: Update Gradle wrapper to 9.4.1 2026-04-04 22:29:11 +02:00
784309b426 refactor!(serde-*): Repackaged all serde modules 2026-03-17 11:08:14 +01:00
affc1de0f5 refactor!: Refactored type hierarchy and methods of StructuredConfigEntry 2026-03-16 21:42:47 +01:00
d69eb85d81 build: Improve development in subprojects 2026-03-16 12:32:00 +01:00
1b9958f980 fix(serde-hjson): Make inlineCommentType builder-style in HjsonWriter.Options 2026-03-01 20:16:14 +01:00
e879050a33 feat(coat-bridge): Add experimental text mapper based on Tweed Serde 2026-03-01 19:45:38 +01:00
7673399f3e docs: Update changelog 2026-03-01 19:44:20 +01:00
ad9c22243a fix(networking): Correct length calculation for SMALL_STRING_TYPE in ByteBufReader 2026-03-01 16:22:20 +01:00
56ed60e532 fix(attributes-extension): Correctly skip non-matching compound entries for attribute filters 2026-03-01 14:28:03 +01:00
0e5a907446 feat(networking): Introduce Minecraft networking support with basic data reader and writers 2026-03-01 14:24:06 +01:00
2e9f4c1689 fix(weaver-pojo-serde): Align SerdePojoReaderWriterSpec with Java's identifier rules 2026-03-01 14:24:06 +01:00
183 changed files with 2603 additions and 942 deletions

1
.gitattributes vendored
View File

@@ -1,3 +1,4 @@
.gitattributes text eol=lf
*.sh text eol=lf
gradlew text eol=lf
gradlew.bat text eol=crlf

5
.gitignore vendored
View File

@@ -5,6 +5,11 @@ build/
!**/src/test/**/build/
.kotlin/
### Gradle in Subprojects ###
*/gradlew
*/gradlew.bat
*/gradle/wrapper
### IntelliJ IDEA ###
.idea/
*.iws

View File

@@ -5,15 +5,20 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [0.7.2] - 2026-03-29
### Changed
- `minecraft`: Added support for Minecraft 26.1.
This includes some required changes in the build process. So this release is published for all versions of Minecraft.
## [Unreleased]
## [0.7.1] - 2026-02-08
### Added
- `networking`: Added module for Minecraft networking.
- `coat-bridge`: Added experimental text mapper based on Tweed Serde.
### Changed
- Relicensed to MPL-2.0.
- **Breaking**@`core`: Refactored type hierarchy and methods of `StructuredConfigEntry`.
- **Breaking**@`serde-*`: Repackaged all classes to bear `tweed5.serde` instead of `tweed5.data` in their packages.
- `weaver-pojo-serde-extension`: Slightly changed the `SerdePojoReaderWriterSpec`
to be more closely aligned with Java's identifier rules.
- `attributes-extension`: The `AttributesReadWriteFilterExtension` now correctly skips non-matching compound entries
instead of returning `null` for them.
- `serde-hjson`: `inlineCommentType` on `HjsonWriter.Options` now correctly works builder-style.
## [0.7.0] - 2025-12-19

View File

@@ -49,7 +49,7 @@ abstract class MinecraftModComponentPlugin : Plugin<Project> {
attribute(Category.CATEGORY_ATTRIBUTE, objectFactory.named(Category.LIBRARY))
attribute(LibraryElements.LIBRARY_ELEMENTS_ATTRIBUTE, objectFactory.named(LibraryElements.JAR))
attribute(Bundling.BUNDLING_ATTRIBUTE, objectFactory.named(Bundling.EXTERNAL))
attribute(Usage.USAGE_ATTRIBUTE, objectFactory.named(Usage.JAVA_API))
attribute(Usage.USAGE_ATTRIBUTE, objectFactory.named(Usage.JAVA_RUNTIME))
project.afterEvaluate {
attribute(TargetJvmVersion.TARGET_JVM_VERSION_ATTRIBUTE, targetJvmVersion.get().toInt())

View File

@@ -1,8 +1,8 @@
plugins {
java
`java-library`
jacoco
id("io.freefair.lombok")
id("de.siphalor.tweed5.unit-tests")
id("de.siphalor.tweed5.publishing")
id("de.siphalor.tweed5.local-runtime-only")
id("de.siphalor.tweed5.expanded-sources-jar")
@@ -13,12 +13,6 @@ java {
targetCompatibility = JavaVersion.VERSION_1_8
}
val testAgent = configurations.dependencyScope("mockitoAgent")
val testAgentClasspath = configurations.resolvable("testAgentClasspath") {
isTransitive = false
extendsFrom(testAgent.get())
}
lombok {
version = libs.versions.lombok.get()
}
@@ -39,40 +33,9 @@ dependencies {
testImplementation(libs.acl)
testImplementation(libs.slf4j.rt)
testImplementation(platform(libs.junit.platform))
testImplementation(libs.junit.core)
testRuntimeOnly(libs.junit.launcher)
testImplementation(libs.mockito)
testAgent(libs.mockito)
testImplementation(libs.assertj)
testImplementation(project(":generic-test-utils"))
}
tasks.compileTestJava {
sourceCompatibility = libs.versions.java.test.get()
targetCompatibility = libs.versions.java.test.get()
}
tasks.test {
val testAgentFiles = testAgentClasspath.map { it.files }
doFirst {
jvmArgs(testAgentFiles.get().map { file -> "-javaagent:${file.absolutePath}" })
}
dependsOn(testAgentClasspath)
finalizedBy(tasks.jacocoTestReport)
useJUnitPlatform()
systemProperties(
"junit.jupiter.execution.timeout.mode" to "disabled_on_debug",
"junit.jupiter.execution.timeout.testable.method.default" to "10s",
"junit.jupiter.execution.timeout.thread.mode.default" to "SEPARATE_THREAD",
)
}
tasks.jacocoTestReport {
dependsOn(tasks.test)
}
publishing {
publications {
create<MavenPublication>("lib") {

View File

@@ -1,5 +1,8 @@
import java.util.Properties
val rootProperties = Properties()
project.layout.settingsDirectory.file("../gradle.properties").asFile.inputStream().use { rootProperties.load(it) }
rootProperties.forEach { (key, value) -> project.ext.set(key.toString(), value.toString()) }
val rootPropertiesFile = project.layout.settingsDirectory.file("../gradle.properties").asFile
if (rootPropertiesFile.exists()) {
Properties()
.apply { rootPropertiesFile.inputStream().use { load(it) } }
.forEach { (key, value) -> project.ext.set(key.toString(), value.toString()) }
}

View File

@@ -0,0 +1,48 @@
plugins {
java
jacoco
}
val testAgent = configurations.dependencyScope("mockitoAgent")
val testAgentClasspath = configurations.resolvable("testAgentClasspath") {
isTransitive = false
extendsFrom(testAgent.get())
}
dependencies {
versionCatalogs.find("libs").ifPresent { libs ->
testImplementation(platform(libs.findLibrary("junit.platform").get()))
testImplementation(libs.findLibrary("junit.core").get())
testRuntimeOnly(libs.findLibrary("junit.launcher").get())
testImplementation(libs.findLibrary("mockito").get())
testAgent(libs.findLibrary("mockito").get())
testImplementation(libs.findLibrary("assertj").get())
}
}
tasks.compileTestJava {
versionCatalogs.find("libs").ifPresent { libs ->
sourceCompatibility = libs.findVersion("java.test").get().requiredVersion
targetCompatibility = libs.findVersion("java.test").get().requiredVersion
}
}
tasks.test {
val testAgentFiles = testAgentClasspath.map { it.files }
doFirst {
jvmArgs(testAgentFiles.get().map { file -> "-javaagent:${file.absolutePath}" })
}
dependsOn(testAgentClasspath)
finalizedBy(tasks.jacocoTestReport)
useJUnitPlatform()
systemProperties(
"junit.jupiter.execution.timeout.mode" to "disabled_on_debug",
"junit.jupiter.execution.timeout.testable.method.default" to "10s",
"junit.jupiter.execution.timeout.thread.mode.default" to "SEPARATE_THREAD",
)
}
tasks.jacocoTestReport {
dependsOn(tasks.test)
}

View File

@@ -1,6 +1,6 @@
org.gradle.jvmargs = -Xmx2G
org.gradle.configuration-cache = true
tweed5.version = 0.7.2
tweed5.version = 0.7.1
git.url = https://gitea.siphalor.de/siphalor/tweed5

Binary file not shown.

5
gradlew vendored
View File

@@ -57,7 +57,7 @@
# Darwin, MinGW, and NonStop.
#
# (3) This script is generated from the Groovy template
# https://github.com/gradle/gradle/blob/2d6327017519d23b96af35865dc997fcb544fb40/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# within the Gradle project.
#
# You can find Gradle at https://github.com/gradle/gradle/.
@@ -114,6 +114,7 @@ case "$( uname )" in #(
NONSTOP* ) nonstop=true ;;
esac
CLASSPATH="\\\"\\\""
# Determine the Java command to use to start the JVM.
@@ -171,6 +172,7 @@ fi
# For Cygwin or MSYS, switch paths to Windows format before running java
if "$cygwin" || "$msys" ; then
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
JAVACMD=$( cygpath --unix "$JAVACMD" )
@@ -210,6 +212,7 @@ DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
set -- \
"-Dorg.gradle.appname=$APP_BASE_NAME" \
-classpath "$CLASSPATH" \
-jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \
"$@"

3
gradlew.bat vendored
View File

@@ -70,10 +70,11 @@ goto fail
:execute
@rem Setup the command line
set CLASSPATH=
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %*
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %*
:end
@rem End local scope for the variables with windows NT shell

View File

@@ -7,9 +7,8 @@ plugins {
dependencies {
implementation("de.siphalor.tweed5:tweed5-conventions")
implementation("de.siphalor.tweed5:tweed5-conventions-helpers")
implementation(pluginMarker(mcCommonLibs.plugins.smcmtk))
implementation(pluginMarker(mcCommonLibs.plugins.fabric.loom))
implementation(pluginMarker(mcCommonLibs.plugins.jcyo))
implementation(pluginMarker(mcLibs.plugins.fabric.loom))
implementation(pluginMarker(libs.plugins.lombok))
implementation(pluginMarker(libs.plugins.shadow))
}

View File

@@ -1,15 +1,13 @@
import de.siphalor.jcyo.gradle.JcyoTask
import de.siphalor.tweed5.gradle.plugin.minecraft.mod.MinecraftModded
import net.fabricmc.loom.api.LoomGradleExtensionAPI
import net.fabricmc.loom.task.RemapJarTask
import net.fabricmc.loom.util.Constants
import java.nio.file.Files
import java.nio.file.StandardCopyOption
import java.util.Properties
plugins {
java
id("de.siphalor.minecraft-modding-toolkit.project-plugin")
id("fabric-loom")
id("de.siphalor.tweed5.unit-tests")
id("de.siphalor.tweed5.publishing")
id("de.siphalor.tweed5.expanded-sources-jar")
id("de.siphalor.jcyo")
@@ -19,21 +17,14 @@ plugins {
id("de.siphalor.tweed5.minecraft.mod.base")
}
val mcCatalog = versionCatalogs.named("mcLibs")
val loomPluginId = mcCatalog.findPlugin("fabric.loom").get().get().pluginId
if (!loomPluginId.endsWith("-remap")) {
project.extensions.extraProperties.set(Constants.Properties.DISABLE_OBFUSCATION, "true")
project.extensions.extraProperties.set(Constants.Properties.DONT_REMAP, "true")
}
apply(plugin = loomPluginId)
val minecraftVersionDescriptor = project.property("minecraft.version.descriptor") as String
val mcProps = Properties().apply {
val propFile = project.layout.settingsDirectory.file("gradle/mc-$minecraftVersionDescriptor/gradle.properties").asFile
propFile.inputStream().use { load(it) }
}
val mcCatalog = versionCatalogs.named("mcLibs")
group = "de.siphalor.tweed5.minecraft"
val archivesBaseName = "${project.name}-mc$minecraftVersionDescriptor"
base {
@@ -48,12 +39,7 @@ val testmod by sourceSets.creating {
runtimeClasspath += sourceSets.main.get().runtimeClasspath
}
smcmtk {
useMojangMappings()
createModConfigurations(listOf(sourceSets.main.get(), testmod))
}
extensions.configure<LoomGradleExtensionAPI>() {
loom {
runs {
create("testmodClient") {
client()
@@ -61,6 +47,7 @@ extensions.configure<LoomGradleExtensionAPI>() {
source(testmod)
}
}
createRemapConfigurations(testmod)
}
// For some reason dependencyResolutionManagement from the settings.gradle doesn't seem to be passed through correctly,
@@ -92,8 +79,12 @@ configurations {
}
dependencies {
"minecraft"(mcCatalog.findLibrary("minecraft").get())
"modImplementation"(mcCommonLibs.fabric.loader)
minecraft(mcCatalog.findLibrary("minecraft").get())
mappings(loom.layered {
officialMojangMappings()
parchment("org.parchmentmc.data:parchment-$minecraftVersion:${getMcCatalogVersion("parchment")}@zip")
})
modImplementation(mcCommonLibs.fabric.loader)
"modTestmodImplementation"(mcCommonLibs.fabric.loader)
compileOnly(libs.jspecify.annotations)
@@ -101,11 +92,6 @@ dependencies {
"testmodImplementation"(sourceSets.main.map { it.output })
}
java {
sourceCompatibility = JavaVersion.toVersion(mcCatalog.findVersion("java").get())
targetCompatibility = JavaVersion.toVersion(mcCatalog.findVersion("java").get())
}
val jcyoVars = mcProps.stringPropertyNames()
.filter { it.startsWith("preprocessor.") }
.map { it to mcProps[it] }
@@ -131,6 +117,31 @@ lombok {
version = libs.versions.lombok.get()
}
val testmodLombokConfigSource = project.layout.settingsDirectory.file("lombok.testmod.config").asFile
val testmodLombokConfigTarget = file("src/testmod/lombok.config")
val copyTestmodLombokConfig by tasks.register("copyTestmodLombokConfig") {
val source = testmodLombokConfigSource
val target = testmodLombokConfigTarget
inputs.file(source)
outputs.file(target)
doFirst {
target.parentFile.mkdirs()
Files.copy(source.toPath(), target.toPath(), StandardCopyOption.REPLACE_EXISTING)
}
}
tasks.named("compileTestmodJava") {
inputs.file(testmodLombokConfigSource)
dependsOn(copyTestmodLombokConfig)
}
afterEvaluate {
tasks.named("generateTestmodEffectiveLombokConfig") {
inputs.file(testmodLombokConfigSource)
dependsOn(copyTestmodLombokConfig)
}
}
tasks.named<Copy>("processResources") {
val processMinecraftModResources = tasks.named<Sync>("processMinecraftModResources")
dependsOn(processMinecraftModResources)
@@ -149,8 +160,7 @@ shadow {
addShadowVariantIntoJavaComponent = false
}
tasks.findByName("remapJar")?.apply {
this as RemapJarTask
tasks.remapJar {
dependsOn(tasks.shadowJar)
inputFile = tasks.shadowJar.get().archiveFile
}

View File

@@ -8,17 +8,16 @@ dependencies {
compileOnly("de.siphalor.tweed5:tweed5-attributes-extension")
compileOnly("de.siphalor.tweed5:tweed5-default-extensions")
compileOnly("de.siphalor.tweed5:tweed5-weaver-pojo")
compileOnly(project(":tweed5-logging", configuration = "minecraftModApiElements"))
modCompileOnly(mcLibs.coat)
listOf(smcmtk.mcProps.getting("fabric.api.key_mapping").get(), "fabric-resource-loader-v0").forEach {
listOf("fabric-key-binding-api-v1", "fabric-resource-loader-v0").forEach {
modTestmodImplementation(fabricApi.module(it, mcLibs.versions.fabric.api.get()))
}
testmodImplementation(project(":tweed5-logging", configuration = "minecraftModElements"))
testmodImplementation(project(":tweed5-logging", configuration = "minecraftModApiElements"))
testmodImplementation(project(":tweed5-bundle", configuration = "runtimeElements"))
testmodImplementation(project(":tweed5-bundle-pojo-weaving", configuration = "runtimeElements"))
testmodImplementation(project(":tweed5-fabric-helper", configuration = "namedElements"))
modTestmodImplementation(mcLibs.coat)
modTestmodImplementation(mcLibs.amecs.priorityKeyMappings)
modTestmodImplementation(mcLibs.amecs.api)
testmodImplementation("de.siphalor.tweed5:tweed5-serde-hjson")
}

View File

@@ -5,6 +5,7 @@ import de.siphalor.tweed5.coat.bridge.api.mapping.TweedCoatMapper;
import de.siphalor.tweed5.coat.bridge.impl.TweedCoatMappersImpl;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import org.jetbrains.annotations.ApiStatus;
import java.util.function.Function;
@@ -66,4 +67,10 @@ public class TweedCoatMappers {
//noinspection unchecked
return TweedCoatMappersImpl.convertingTextMapper(new Class[]{valueClass}, textMapper, textParser);
}
@ApiStatus.Experimental
public static <T> TweedCoatMapper<T> serdeTextMapper(Class<T> valueClass) {
//noinspection unchecked
return TweedCoatMappersImpl.serdeTextMapper(new Class[]{valueClass});
}
}

View File

@@ -7,7 +7,7 @@ import de.siphalor.tweed5.defaultextensions.validation.api.ValidationExtension;
import de.siphalor.tweed5.defaultextensions.validation.api.result.ValidationIssueLevel;
import de.siphalor.tweed5.defaultextensions.validation.api.result.ValidationIssues;
import de.siphalor.tweed5.defaultextensions.validation.api.result.ValidationResult;
import lombok.CustomLog;
import lombok.extern.apachecommons.CommonsLog;
import net.minecraft.network.chat.Component;
import org.jspecify.annotations.Nullable;
@@ -18,7 +18,7 @@ import java.util.stream.Collectors;
import static de.siphalor.tweed5.coat.bridge.api.TweedCoatMappingUtils.literalComponent;
@CustomLog
@CommonsLog
public class BasicTweedCoatEntryHandler<T extends @Nullable Object> implements ConfigEntryHandler<T> {
protected final ConfigEntry<T> configEntry;
protected final T defaultValue;

View File

@@ -2,8 +2,8 @@ package de.siphalor.tweed5.coat.bridge.api.mapping.handler;
import de.siphalor.coat.handler.ConfigEntryHandler;
import de.siphalor.coat.handler.Message;
import lombok.CustomLog;
import lombok.RequiredArgsConstructor;
import lombok.extern.apachecommons.CommonsLog;
import net.minecraft.network.chat.Component;
import org.jspecify.annotations.Nullable;
@@ -16,7 +16,7 @@ import static de.siphalor.tweed5.coat.bridge.api.TweedCoatMappingUtils.literalCo
import static de.siphalor.tweed5.coat.bridge.api.TweedCoatMappingUtils.translatableComponent;
@RequiredArgsConstructor
@CustomLog
@CommonsLog
public class ConvertingTweedCoatEntryHandler<T extends @Nullable Object, C> implements ConfigEntryHandler<C> {
private static final String CONVERSION_EXCEPTION_TEXT_KEY = "tweed5_coat_bridge.handler.conversion.exception";

View File

@@ -1,6 +1,7 @@
package de.siphalor.tweed5.coat.bridge.impl;
import de.siphalor.coat.handler.ConfigEntryHandler;
import de.siphalor.coat.handler.Message;
import de.siphalor.coat.input.CheckBoxConfigInput;
import de.siphalor.coat.input.ConfigInput;
import de.siphalor.coat.input.CycleButtonConfigInput;
@@ -20,14 +21,24 @@ import de.siphalor.tweed5.coat.bridge.api.mapping.handler.BasicTweedCoatEntryHan
import de.siphalor.tweed5.coat.bridge.api.mapping.handler.ConvertingTweedCoatEntryHandler;
import de.siphalor.tweed5.core.api.entry.CompoundConfigEntry;
import de.siphalor.tweed5.core.api.entry.ConfigEntry;
import lombok.CustomLog;
import de.siphalor.tweed5.patchwork.api.Patchwork;
import de.siphalor.tweed5.serde.extension.api.ReadWriteExtension;
import de.siphalor.tweed5.serde.extension.api.TweedEntryReader;
import de.siphalor.tweed5.serde.extension.api.TweedReadContext;
import de.siphalor.tweed5.serde.extension.api.read.result.TweedReadResult;
import de.siphalor.tweed5.serde_api.api.TweedDataReadException;
import de.siphalor.tweed5.serde_api.api.TweedDataReader;
import de.siphalor.tweed5.serde_api.api.TweedDataToken;
import de.siphalor.tweed5.serde_api.api.TweedDataVisitor;
import de.siphalor.tweed5.serde_api.api.decoration.TweedDataDecoration;
import lombok.RequiredArgsConstructor;
import lombok.Value;
import lombok.extern.apachecommons.CommonsLog;
import net.minecraft.ChatFormatting;
import net.minecraft.client.Minecraft;
import net.minecraft.client.resources.language.I18n;
import net.minecraft.resources.Identifier;
//- import net.minecraft.resources.ResourceLocation;
import net.minecraft.network.chat.Component;
import net.minecraft.resources.ResourceLocation;
import org.jspecify.annotations.NonNull;
import org.jspecify.annotations.Nullable;
@@ -35,10 +46,9 @@ import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;
import static de.siphalor.tweed5.coat.bridge.api.TweedCoatMappingUtils.translatableComponent;
import static de.siphalor.tweed5.coat.bridge.api.TweedCoatMappingUtils.translatableComponentWithFallback;
import static de.siphalor.tweed5.coat.bridge.api.TweedCoatMappingUtils.*;
@CustomLog
@CommonsLog
@SuppressWarnings("unchecked")
public class TweedCoatMappersImpl {
public static TweedCoatMapper<Byte> BYTE_TEXT_MAPPER = convertingTextMapper(
@@ -83,6 +93,10 @@ public class TweedCoatMappersImpl {
public static TweedCoatMapper<Object> COMPOUND_CATEGORY_MAPPER = new CompoundCategoryMapper<>();
public static <T> TweedCoatMapper<T> serdeTextMapper(Class<T>[] valueClasses) {
return new SerdeTextMapper<>(valueClasses);
}
public static <T> TweedCoatMapper<T> convertingTextMapper(
Class<T>[] valueClasses,
Function<T, String> textMapper,
@@ -91,6 +105,240 @@ public class TweedCoatMappersImpl {
return new ConvertingTextMapper<>(valueClasses, textMapper, textParser);
}
@RequiredArgsConstructor
public static class SerdeTextMapper<T> implements TweedCoatMapper<T> {
private final Class<T>[] valueClasses;
@Override
public TweedCoatEntryMappingResult<T, ?> mapEntry(ConfigEntry<T> entry, TweedCoatEntryMappingContext context) {
if (!anyClassMatches(entry.valueClass(), valueClasses)) {
return TweedCoatEntryMappingResult.notApplicable();
}
ReadWriteExtension readWriteExtension = entry.container().extension(ReadWriteExtension.class)
.orElseThrow(() -> new IllegalArgumentException("No ReadWriteExtension registered"));
return new TweedCoatEntryMappingResult<T, String>() {
@Override
public boolean isApplicable() {
return true;
}
@Override
public ConfigInput<String> createInput(TweedCoatEntryCreationContext<T> context) {
return new TextConfigInput(convertToString(context.currentValue()));
}
@Override
public ConfigEntryHandler<String> createHandler(TweedCoatEntryCreationContext<T> context) {
if (context.parentSaveHandler() == null) {
throw new IllegalArgumentException("No parent save handler provided");
}
return new ConfigEntryHandler<String>() {
@Override
public String getDefault() {
return convertToString(context.defaultValue());
}
@Override
public Collection<Message> getMessages(String text) {
try {
TweedReadResult<T> readResult = convertFromString(text);
if (readResult.hasValue() && !readResult.hasIssues()) {
return Collections.emptyList();
} else if (readResult.hasIssues()) {
Message.Level messageLevel = readResult.hasValue()
? Message.Level.WARNING
: Message.Level.ERROR;
return Arrays.stream(readResult.issues()).map(issue ->
new Message(messageLevel, literalComponent(issue.exception().getMessage()))
).collect(Collectors.toList());
} else {
return Collections.emptyList();
}
} catch (Exception e) {
return Collections.singletonList(
new Message(Message.Level.ERROR, literalComponent(e.getMessage()))
);
}
}
@Override
public void save(String s) {
try {
TweedReadResult<T> readResult = convertFromString(s);
if (readResult.hasValue()) {
if (readResult.hasIssues()) {
log.warn(
"There were issues understanding value \"" + s + "\":\n - "
+ Arrays.stream(readResult.issues())
.map(issue -> issue.exception().getMessage())
.collect(Collectors.joining("\n - "))
);
}
context.parentSaveHandler().accept(readResult.value());
} else {
log.error(
"Failed to understand value \"" + s + "\":\n - "
+ Arrays.stream(readResult.issues())
.map(issue -> issue.exception().getMessage())
.collect(Collectors.joining("\n - "))
);
context.parentSaveHandler().accept(context.defaultValue());
}
} catch (Exception e) {
log.error("Failed to save value \"" + s + "\"", e);
context.parentSaveHandler().accept(context.defaultValue());
}
}
@Override
public Component asText(String text) {
return literalComponent(text);
}
};
}
@Override
public @Nullable ConfigContentWidget createContentWidget(TweedCoatEntryCreationContext<T> context) {
return null;
}
private String convertToString(T value) {
String[] wrapper = new String[]{""};
try {
readWriteExtension.write(
new TweedDataVisitor() {
@Override
public void visitNull() {
}
@Override
public void visitBoolean(boolean value) {
}
@Override
public void visitByte(byte value) {
}
@Override
public void visitShort(short value) {
}
@Override
public void visitInt(int value) {
}
@Override
public void visitLong(long value) {
}
@Override
public void visitFloat(float value) {
}
@Override
public void visitDouble(double value) {
}
@Override
public void visitString(String value) {
wrapper[0] = value;
}
@Override
public void visitListStart() {
}
@Override
public void visitListEnd() {
}
@Override
public void visitMapStart() {
}
@Override
public void visitMapEntryKey(String key) {
}
@Override
public void visitMapEnd() {
}
@Override
public void visitDecoration(TweedDataDecoration decoration) {
}
}, value, entry, readWriteExtension.createReadWriteContextExtensionsData()
);
} catch (Exception e) {
log.warn("Failed to serialize value " + value + " to string", e);
}
return wrapper[0];
}
private TweedReadResult<T> convertFromString(String text) {
TweedEntryReader<T, ConfigEntry<T>> reader =
readWriteExtension.getDefinedEntryReader(entry);
Patchwork readExtData = readWriteExtension.createReadWriteContextExtensionsData();
//noinspection DataFlowIssue
return reader.read(
new TweedDataReader() {
private boolean consumed = false;
@Override
public TweedDataToken peekToken() throws TweedDataReadException {
if (consumed) {
throw new IllegalStateException("Already consumed");
}
return new TweedDataToken() {
@Override
public boolean canReadAsString() {
return true;
}
@Override
public String readAsString() {
return text;
}
};
}
@Override
public TweedDataToken readToken() throws TweedDataReadException {
TweedDataToken token = peekToken();
consumed = true;
return token;
}
@Override
public void close() {
}
}, entry, new TweedReadContext() {
@Override
public ReadWriteExtension readWriteExtension() {
return readWriteExtension;
}
@Override
public Patchwork extensionsData() {
return readExtData;
}
@Override
public <T, C extends ConfigEntry<T>> TweedReadResult<T> readSubEntry(
TweedDataReader reader,
C entry
) {
return TweedReadResult.empty();
}
}
);
}
};
}
}
@RequiredArgsConstructor
public static class ConvertingTextMapper<T> implements TweedCoatMapper<T> {
private final Class<T>[] valueClasses;
@@ -256,20 +504,12 @@ public class TweedCoatMappersImpl {
CompoundConfigEntry<T> compoundEntry = (CompoundConfigEntry<T>) entry;
Optional<AttributesExtension> attributesExtension = entry.container().extension(AttributesExtension.class);
//# if MC_VERSION_NUMBER >= 260100
Identifier backgroundTexture = attributesExtension
//# else
//- ResourceLocation backgroundTexture = attributesExtension
//# end
ResourceLocation backgroundTexture = attributesExtension
.map(extension -> extension.getAttributeValue(
entry,
TweedCoatAttributes.BACKGROUND_TEXTURE
))
//# if MC_VERSION_NUMBER >= 260100
.map(Identifier::tryParse)
//# else
//- .map(ResourceLocation::tryParse)
//# end
.map(ResourceLocation::tryParse)
.orElse(null);
String translationKey = attributesExtension
.map(extension -> extension.getAttributeValue(

View File

@@ -1,19 +1,18 @@
package de.siphalor.tweed5.coat.bridge.testmod;
import de.siphalor.amecs.priority_key_mappings.api.AmecsPriorityKeyMapping;
import de.siphalor.amecs.api.PriorityKeyBinding;
import de.siphalor.coat.screen.ConfigScreen;
import de.siphalor.tweed5.coat.bridge.api.ConfigScreenCreateParams;
import de.siphalor.tweed5.coat.bridge.api.TweedCoatBridgeExtension;
import de.siphalor.tweed5.coat.bridge.api.TweedCoatMappers;
import de.siphalor.tweed5.core.api.container.ConfigContainer;
import de.siphalor.tweed5.data.hjson.HjsonSerde;
import de.siphalor.tweed5.data.hjson.HjsonWriter;
import de.siphalor.tweed5.serde.hjson.HjsonSerde;
import de.siphalor.tweed5.serde.hjson.HjsonWriter;
import de.siphalor.tweed5.fabric.helper.api.FabricConfigContainerHelper;
import de.siphalor.tweed5.weaver.pojo.api.TweedPojoWeaver;
import lombok.CustomLog;
import net.fabricmc.api.ClientModInitializer;
//- import net.fabricmc.fabric.api.client.keybinding.v1.KeyBindingHelper;
import net.fabricmc.fabric.api.client.keymapping.v1.KeyMappingHelper;
import net.fabricmc.fabric.api.client.keybinding.v1.KeyBindingHelper;
import net.minecraft.client.KeyMapping;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.screens.TitleScreen;
@@ -55,11 +54,7 @@ public class TweedCoatBridgeTestMod implements ClientModInitializer {
config = configContainerHelper.loadAndUpdateInConfigDirectory(() -> DEFAULT_CONFIG_VALUE);
//# if MC_VERSION_NUMBER >= 260100
KeyMappingHelper.registerKeyMapping(new ScreenKeyBinding(
//# else
//- KeyBindingHelper.registerKeyBinding(new ScreenKeyBinding(
//# end
KeyBindingHelper.registerKeyBinding(new ScreenKeyBinding(
MOD_ID + ".config",
GLFW.GLFW_KEY_T,
//# if MC_VERSION_NUMBER >= 12109
@@ -72,7 +67,7 @@ public class TweedCoatBridgeTestMod implements ClientModInitializer {
log.info("Current config: " + config);
}
private class ScreenKeyBinding extends KeyMapping implements AmecsPriorityKeyMapping {
private class ScreenKeyBinding extends KeyMapping implements PriorityKeyBinding {
//# if MC_VERSION_NUMBER >= 12109
public ScreenKeyBinding(String name, int key, Category category) {
//# else

View File

@@ -15,12 +15,11 @@ dependencies {
compileOnly("de.siphalor.tweed5:tweed5-weaver-pojo-serde-extension")
compileOnly("de.siphalor.tweed5:tweed5-weaver-pojo-validation-extension")
compileOnly("de.siphalor.tweed5:tweed5-weaver-pojo-presets-extension")
compileOnly(project(":tweed5-logging", configuration = "minecraftModApiElements"))
listOf("fabric-networking-api-v1", "fabric-lifecycle-events-v1").forEach {
modTestmodImplementation(fabricApi.module(it, mcLibs.versions.fabric.api.get()))
}
testmodImplementation(project(":tweed5-logging", configuration = "minecraftModElements"))
testmodImplementation(project(":tweed5-logging", configuration = "minecraftModApiElements"))
testmodImplementation(project(":tweed5-bundle", configuration = "runtimeElements"))
testmodImplementation(project(":tweed5-bundle-pojo-weaving", configuration = "runtimeElements"))
testmodImplementation("de.siphalor.tweed5:tweed5-comment-loader-extension")

View File

@@ -3,7 +3,7 @@ package de.siphalor.tweed5.fabric.helper.api;
import de.siphalor.tweed5.annotationinheritance.api.AnnotationInheritance;
import de.siphalor.tweed5.attributesextension.api.AttributesExtension;
import de.siphalor.tweed5.attributesextension.api.serde.filter.AttributesReadWriteFilterExtension;
import de.siphalor.tweed5.data.extension.api.ReadWriteExtension;
import de.siphalor.tweed5.serde.extension.api.ReadWriteExtension;
import de.siphalor.tweed5.defaultextensions.presets.api.PresetsExtension;
import de.siphalor.tweed5.defaultextensions.validation.api.ValidationExtension;
import de.siphalor.tweed5.defaultextensions.validationfallback.api.ValidationFallbackExtension;

View File

@@ -4,16 +4,16 @@ import com.google.gson.stream.JsonReader;
import de.siphalor.tweed5.commentloaderextension.api.CommentLoaderExtension;
import de.siphalor.tweed5.commentloaderextension.api.CommentPathProcessor;
import de.siphalor.tweed5.core.api.container.ConfigContainer;
import de.siphalor.tweed5.dataapi.api.TweedDataReader;
import de.siphalor.tweed5.data.gson.GsonReader;
import de.siphalor.tweed5.serde_api.api.TweedDataReader;
import de.siphalor.tweed5.serde.gson.GsonReader;
import lombok.Builder;
import lombok.CustomLog;
import lombok.extern.apachecommons.CommonsLog;
import org.jspecify.annotations.Nullable;
import java.io.InputStream;
import java.io.InputStreamReader;
@CustomLog
@CommonsLog
@Builder
public class FabricConfigCommentLoader {
private final ConfigContainer<?> configContainer;

View File

@@ -2,29 +2,29 @@ package de.siphalor.tweed5.fabric.helper.api;
import de.siphalor.tweed5.core.api.container.ConfigContainer;
import de.siphalor.tweed5.core.api.container.ConfigContainerSetupPhase;
import de.siphalor.tweed5.data.extension.api.ReadWriteExtension;
import de.siphalor.tweed5.dataapi.api.TweedDataReader;
import de.siphalor.tweed5.dataapi.api.TweedDataWriter;
import de.siphalor.tweed5.dataapi.api.TweedSerde;
import de.siphalor.tweed5.defaultextensions.patch.api.PatchExtension;
import de.siphalor.tweed5.defaultextensions.patch.api.PatchInfo;
import de.siphalor.tweed5.defaultextensions.presets.api.PresetsExtension;
import de.siphalor.tweed5.patchwork.api.Patchwork;
import lombok.CustomLog;
import de.siphalor.tweed5.serde.extension.api.ReadWriteExtension;
import de.siphalor.tweed5.serde.extension.api.read.result.TweedReadIssue;
import de.siphalor.tweed5.serde.extension.api.read.result.TweedReadResult;
import de.siphalor.tweed5.serde_api.api.TweedDataReader;
import de.siphalor.tweed5.serde_api.api.TweedDataWriter;
import de.siphalor.tweed5.serde_api.api.TweedSerde;
import lombok.Getter;
import lombok.extern.apachecommons.CommonsLog;
import net.fabricmc.loader.api.FabricLoader;
import org.jspecify.annotations.Nullable;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.function.Consumer;
import java.util.function.Supplier;
@CustomLog
@CommonsLog
public class FabricConfigContainerHelper<T extends @Nullable Object> {
@Getter
private final ConfigContainer<T> configContainer;
@@ -84,14 +84,24 @@ public class FabricConfigContainerHelper<T extends @Nullable Object> {
return;
}
try (TweedDataReader reader = serde.createReader(new FileInputStream(configFile))) {
try (TweedDataReader reader = serde.createReader(Files.newInputStream(configFile.toPath()))) {
Patchwork contextExtensionsData = readWriteExtension.createReadWriteContextExtensionsData();
readContextCustomizer.accept(contextExtensionsData);
PatchInfo patchInfo = patchExtension.collectPatchInfo(contextExtensionsData);
T readValue = readWriteExtension.read(reader, configContainer().rootEntry(), contextExtensionsData);
TweedReadResult<T> readResult = readWriteExtension.read(reader, configContainer().rootEntry(), contextExtensionsData);
if (readResult.hasValue()) {
patchExtension.patch(configContainer.rootEntry(), value, readResult.value(), patchInfo);
if (readResult.hasIssues()) {
log.warn(formatIssuesLogMessage(configFile, "issues", readResult.issues()));
}
} else if (readResult.hasIssues()) {
log.error(formatIssuesLogMessage(configFile, "errors", readResult.issues()));
} else {
log.debug("Reading config file " + configFile.getAbsolutePath() + " yielded empty result");
}
patchExtension.patch(configContainer.rootEntry(), value, readValue, patchInfo);
} catch (Exception e) {
log.error("Failed loading config file " + configFile.getAbsolutePath(), e);
}
@@ -108,20 +118,54 @@ public class FabricConfigContainerHelper<T extends @Nullable Object> {
return defaultValueSupplier.get();
}
try (TweedDataReader reader = serde.createReader(new FileInputStream(configFile))) {
try (TweedDataReader reader = serde.createReader(Files.newInputStream(configFile.toPath()))) {
Patchwork contextExtensionsData = readWriteExtension.createReadWriteContextExtensionsData();
return readWriteExtension.read(reader, configContainer.rootEntry(), contextExtensionsData);
TweedReadResult<T> readResult = readWriteExtension.read(
reader,
configContainer.rootEntry(),
contextExtensionsData
);
if (readResult.hasValue()) {
if (readResult.hasIssues()) {
log.warn(formatIssuesLogMessage(configFile, "issues", readResult.issues()));
}
return readResult.value();
} else if (readResult.hasIssues()) {
log.error(formatIssuesLogMessage(configFile, "errors", readResult.issues()));
} else {
log.info(
"Reading config file "
+ configFile.getAbsolutePath()
+ " yielded empty result, using default value"
);
}
return defaultValueSupplier.get();
} catch (Exception e) {
log.error("Failed loading config file " + configFile.getAbsolutePath(), e);
return defaultValueSupplier.get();
}
}
private String formatIssuesLogMessage(File file, String type, TweedReadIssue[] issues) {
String filePath = file.getAbsolutePath();
StringBuilder stringBuilder = new StringBuilder(20 + filePath.length() + type.length() + issues.length * 50);
stringBuilder.append("Encountered ");
stringBuilder.append(type);
stringBuilder.append(" while reading ");
stringBuilder.append(filePath);
stringBuilder.append(": ");
for (TweedReadIssue issue : issues) {
stringBuilder.append(" - ").append(issue).append("\n");
}
return stringBuilder.toString();
}
public void writeConfigInConfigDirectory(T configValue) {
File configFile = getConfigFile();
Path tempConfigDirectory = getOrCreateTempConfigDirectory();
File tempConfigFile = tempConfigDirectory.resolve(getConfigFileName()).toFile();
try (TweedDataWriter writer = serde.createWriter(new FileOutputStream(tempConfigFile))) {
try (TweedDataWriter writer = serde.createWriter(Files.newOutputStream(tempConfigFile.toPath()))) {
Patchwork contextExtensionsData = readWriteExtension.createReadWriteContextExtensionsData();
readWriteExtension.write(

View File

@@ -2,8 +2,8 @@ package de.siphalor.tweed5.fabric.helper.testmod;
import de.siphalor.tweed5.attributesextension.api.serde.filter.AttributesReadWriteFilterExtension;
import de.siphalor.tweed5.core.api.container.ConfigContainer;
import de.siphalor.tweed5.data.hjson.HjsonSerde;
import de.siphalor.tweed5.data.hjson.HjsonWriter;
import de.siphalor.tweed5.serde.hjson.HjsonSerde;
import de.siphalor.tweed5.serde.hjson.HjsonWriter;
import de.siphalor.tweed5.fabric.helper.api.FabricConfigCommentLoader;
import de.siphalor.tweed5.fabric.helper.api.FabricConfigContainerHelper;
import de.siphalor.tweed5.weaver.pojo.api.TweedPojoWeaver;

View File

@@ -1,3 +1,4 @@
org.gradle.jvmargs = -Xmx2G
org.gradle.configuration-cache = true
minecraft.version.descriptor = 26.1.0
minecraft.version.descriptor = 1.21.10

View File

@@ -1,3 +1 @@
fabric.api.key_mapping = fabric-key-binding-api-v1
preprocessor.mc_version_number = 11605

View File

@@ -1,17 +1,12 @@
[versions]
amecs-priorityKeyMappings = "1.0.1"
amecs-api = "1.6.2"
coat = "1.0.0-beta.24"
fabric-api = "0.42.0+1.16"
fabric-loom = "1.15-SNAPSHOT"
java = "8"
minecraft = "1.16.5"
parchment = "2022.03.06"
[plugins]
fabric-loom = { id = "net.fabricmc.fabric-loom-remap", version.ref = "fabric-loom" }
[libraries]
amecs-priorityKeyMappings = { group = "de.siphalor.amecs.amecs-priority-key-mappings", name = "amecs-priority-key-mappings-mc1.16.5", version.ref = "amecs-priorityKeyMappings" }
amecs-api = { group = "de.siphalor.amecs-api", name = "amecs-api-mc1.16.5", version.ref = "amecs-api" }
coat = { group = "de.siphalor.coat", name = "coat-mc1.16.5", version.ref = "coat" }
fabric-api = { group = "net.fabricmc.fabric-api", name = "fabric-api", version.ref = "fabric-api" }
minecraft = { group = "com.mojang", name = "minecraft", version.ref = "minecraft" }

View File

@@ -1,3 +1 @@
fabric.api.key_mapping = fabric-key-binding-api-v1
preprocessor.mc_version_number = 11701

View File

@@ -1,17 +1,12 @@
[versions]
amecs-priorityKeyMappings = "1.0.1"
amecs-api = "1.6.2"
coat = "1.0.0-beta.24"
fabric-api = "0.46.1+1.17"
fabric-loom = "1.15-SNAPSHOT"
java = "16"
minecraft = "1.17.1"
parchment = "2021.12.12"
[plugins]
fabric-loom = { id = "net.fabricmc.fabric-loom-remap", version.ref = "fabric-loom" }
[libraries]
amecs-priorityKeyMappings = { group = "de.siphalor.amecs.amecs-priority-key-mappings", name = "amecs-priority-key-mappings-mc1.17.1", version.ref = "amecs-priorityKeyMappings" }
amecs-api = { group = "de.siphalor.amecs-api", name = "amecs-api-mc1.17.1", version.ref = "amecs-api" }
coat = { group = "de.siphalor.coat", name = "coat-mc1.17.1", version.ref = "coat" }
fabric-api = { group = "net.fabricmc.fabric-api", name = "fabric-api", version.ref = "fabric-api" }
minecraft = { group = "com.mojang", name = "minecraft", version.ref = "minecraft" }

View File

@@ -1,3 +1 @@
fabric.api.key_mapping = fabric-key-binding-api-v1
preprocessor.mc_version_number = 11802

View File

@@ -1,17 +1,12 @@
[versions]
amecs-priorityKeyMappings = "1.0.1"
amecs-api = "1.6.2"
coat = "1.0.0-beta.24"
fabric-api = "0.77.0+1.18.2"
fabric-loom = "1.15-SNAPSHOT"
java = "17"
minecraft = "1.18.2"
parchment = "2022.11.06"
[plugins]
fabric-loom = { id = "net.fabricmc.fabric-loom-remap", version.ref = "fabric-loom" }
[libraries]
amecs-priorityKeyMappings = { group = "de.siphalor.amecs.amecs-priority-key-mappings", name = "amecs-priority-key-mappings-mc1.18.2", version.ref = "amecs-priorityKeyMappings" }
amecs-api = { group = "de.siphalor.amecs-api", name = "amecs-api-mc1.18.2", version.ref = "amecs-api" }
coat = { group = "de.siphalor.coat", name = "coat-mc1.18.2", version.ref = "coat" }
fabric-api = { group = "net.fabricmc.fabric-api", name = "fabric-api", version.ref = "fabric-api" }
minecraft = { group = "com.mojang", name = "minecraft", version.ref = "minecraft" }

View File

@@ -1,3 +1 @@
fabric.api.key_mapping = fabric-key-binding-api-v1
preprocessor.mc_version_number = 11904

View File

@@ -1,17 +1,12 @@
[versions]
amecs-priorityKeyMappings = "1.0.1"
amecs-api = "1.6.2"
coat = "1.0.0-beta.24"
fabric-api = "0.87.2+1.19.4"
fabric-loom = "1.15-SNAPSHOT"
java = "17"
minecraft = "1.19.4"
parchment = "2023.06.26"
[plugins]
fabric-loom = { id = "net.fabricmc.fabric-loom-remap", version.ref = "fabric-loom" }
[libraries]
amecs-priorityKeyMappings = { group = "de.siphalor.amecs.amecs-priority-key-mappings", name = "amecs-priority-key-mappings-mc1.19.4", version.ref = "amecs-priorityKeyMappings" }
amecs-api = { group = "de.siphalor.amecs-api", name = "amecs-api-mc1.19.4", version.ref = "amecs-api" }
coat = { group = "de.siphalor.coat", name = "coat-mc1.19.4", version.ref = "coat" }
fabric-api = { group = "net.fabricmc.fabric-api", name = "fabric-api", version.ref = "fabric-api" }
minecraft = { group = "com.mojang", name = "minecraft", version.ref = "minecraft" }

View File

@@ -1,3 +1 @@
fabric.api.key_mapping = fabric-key-binding-api-v1
preprocessor.mc_version_number = 12006

View File

@@ -1,17 +1,12 @@
[versions]
amecs-priorityKeyMappings = "1.0.1"
amecs-api = "1.6.2"
coat = "1.0.0-beta.24"
fabric-api = "0.100.8+1.20.6"
fabric-loom = "1.15-SNAPSHOT"
java = "17"
minecraft = "1.20.6"
parchment = "2024.06.16"
[plugins]
fabric-loom = { id = "net.fabricmc.fabric-loom-remap", version.ref = "fabric-loom" }
[libraries]
amecs-priorityKeyMappings = { group = "de.siphalor.amecs.amecs-priority-key-mappings", name = "amecs-priority-key-mappings-mc1.20.2", version.ref = "amecs-priorityKeyMappings" }
amecs-api = { group = "de.siphalor.amecs-api", name = "amecs-api-mc1.20.2", version.ref = "amecs-api" }
coat = { group = "de.siphalor.coat", name = "coat-mc1.20.5", version.ref = "coat" }
fabric-api = { group = "net.fabricmc.fabric-api", name = "fabric-api", version.ref = "fabric-api" }
minecraft = { group = "com.mojang", name = "minecraft", version.ref = "minecraft" }

View File

@@ -1,3 +1 @@
fabric.api.key_mapping = fabric-key-binding-api-v1
preprocessor.mc_version_number = 12110

View File

@@ -1,17 +1,12 @@
[versions]
amecs-priorityKeyMappings = "1.0.1"
amecs-api = "1.6.2"
coat = "1.0.0-beta.24"
fabric-api = "0.136.0+1.21.10"
fabric-loom = "1.15-SNAPSHOT"
java = "21"
minecraft = "1.21.10"
parchment = "2025.10.12"
[plugins]
fabric-loom = { id = "net.fabricmc.fabric-loom-remap", version.ref = "fabric-loom" }
[libraries]
amecs-priorityKeyMappings = { group = "de.siphalor.amecs.amecs-priority-key-mappings", name = "amecs-priority-key-mappings-mc1.21.9", version.ref = "amecs-priorityKeyMappings" }
amecs-api = { group = "de.siphalor.amecs-api", name = "amecs-api-mc1.21.9", version.ref = "amecs-api" }
coat = { group = "de.siphalor.coat", name = "coat-mc1.21.10", version.ref = "coat" }
fabric-api = { group = "net.fabricmc.fabric-api", name = "fabric-api", version.ref = "fabric-api" }
minecraft = { group = "com.mojang", name = "minecraft", version.ref = "minecraft" }

View File

@@ -1,3 +0,0 @@
fabric.api.key_mapping = fabric-key-mapping-api-v1
preprocessor.mc_version_number = 260100

View File

@@ -1,16 +0,0 @@
[versions]
amecs-priorityKeyMappings = "1.0.1"
coat = "1.1.1"
fabric-api = "0.144.3+26.1"
fabric-loom = "1.15-SNAPSHOT"
java = "25"
minecraft = "26.1"
[plugins]
fabric-loom = { id = "net.fabricmc.fabric-loom", version.ref = "fabric-loom" }
[libraries]
amecs-priorityKeyMappings = { group = "de.siphalor.amecs.amecs-priority-key-mappings", name = "amecs-priority-key-mappings-mc26.1.0", version.ref = "amecs-priorityKeyMappings" }
coat = { group = "de.siphalor.coat", name = "coat-mc26.1.0", version.ref = "coat" }
fabric-api = { group = "net.fabricmc.fabric-api", name = "fabric-api", version.ref = "fabric-api" }
minecraft = { group = "com.mojang", name = "minecraft", version.ref = "minecraft" }

View File

@@ -1,11 +1,11 @@
[versions]
fabric-loader = "0.18.5"
jcyo = "0.5.1"
smcmtk = "0.1.0"
fabric-loader = "0.17.2"
fabric-loom = "1.11-SNAPSHOT"
jcyo = "0.6.1"
[plugins]
jcyo = { id = "de.siphalor.jcyo", version.ref = "jcyo" }
smcmtk = { id = "de.siphalor.minecraft-modding-toolkit.project-plugin", version.ref = "smcmtk" }
fabric-loom = { id = "fabric-loom", version.ref = "fabric-loom" }
[libraries]
fabric-loader = { group = "net.fabricmc", name = "fabric-loader", version.ref = "fabric-loader" }

View File

@@ -1,7 +1,2 @@
lombok.accessors.fluent = true
lombok.addLombokGeneratedAnnotation = true
# Special configuration, so that the testmod can use the correct shadowed logging classes.
lombok.log.apacheCommons.flagUsage=WARNING
lombok.log.custom.declaration=de.siphalor.tweed5.shadowed.org.apache.commons.logging.Log de.siphalor.tweed5.shadowed.org.apache.commons.logging.LogFactory.getLog(TYPE)
lombok.log.custom.flagUsage=ALLOW
lombok.addLombokGeneratedAnnotation = true

View File

@@ -0,0 +1,4 @@
# Special configuration, so that the testmod can use the correct shadowed logging classes.
lombok.log.apacheCommons.flagUsage=WARNING
lombok.log.custom.declaration=de.siphalor.tweed5.shadowed.org.apache.commons.logging.Log de.siphalor.tweed5.shadowed.org.apache.commons.logging.LogFactory.getLog(TYPE)
lombok.log.custom.flagUsage=ALLOW

View File

@@ -0,0 +1,13 @@
plugins {
id("de.siphalor.tweed5.local-runtime-only")
id("de.siphalor.tweed5.minecraft.mod.cross-version")
}
dependencies {
modCompileOnly(fabricApi.module("fabric-networking-api-v1", mcLibs.versions.fabric.api.get()))
compileOnly("de.siphalor.tweed5:tweed5-core")
compileOnly("de.siphalor.tweed5:tweed5-serde-extension")
testImplementation("de.siphalor.tweed5:tweed5-core")
testImplementation("de.siphalor.tweed5:tweed5-serde-extension")
}

View File

@@ -0,0 +1,2 @@
module.name = Tweed 5 Netwoking
module.description = Minecraft networking support for Tweed 5

View File

@@ -0,0 +1,297 @@
package de.siphalor.tweed5.minecraft.networking.api;
import de.siphalor.tweed5.serde_api.api.TweedDataReadException;
import de.siphalor.tweed5.serde_api.api.TweedDataReader;
import de.siphalor.tweed5.serde_api.api.TweedDataToken;
import de.siphalor.tweed5.serde_api.api.TweedDataTokens;
import de.siphalor.tweed5.minecraft.networking.impl.ByteBufSerdeConstants;
import io.netty.buffer.ByteBuf;
import lombok.RequiredArgsConstructor;
import org.jspecify.annotations.Nullable;
import java.nio.charset.StandardCharsets;
import java.util.ArrayDeque;
import java.util.Deque;
@RequiredArgsConstructor
public class ByteBufReader implements TweedDataReader {
private final ByteBuf buf;
private final Deque<Context> contextStack = new ArrayDeque<>();
private @Nullable TweedDataToken peek;
@Override
public TweedDataToken peekToken() throws TweedDataReadException {
if (peek != null) return peek;
ensureReadable();
return (peek = nextToken());
}
@Override
public TweedDataToken readToken() throws TweedDataReadException {
if (peek != null) {
TweedDataToken token = peek;
peek = null;
return token;
}
ensureReadable();
return nextToken();
}
private void ensureReadable() throws TweedDataReadException {
if (!buf.isReadable()) {
throw TweedDataReadException.builder().message("Reached end of buffer").build();
}
}
private TweedDataToken nextToken() throws TweedDataReadException {
int b = Byte.toUnsignedInt(buf.readByte());
switch (b) {
case ByteBufSerdeConstants.NULL_VALUE:
return wrapTokenForContext(TweedDataTokens.getNull());
case ByteBufSerdeConstants.FALSE_VALUE:
return wrapTokenForContext(BooleanToken.FALSE);
case ByteBufSerdeConstants.TRUE_VALUE:
return wrapTokenForContext(BooleanToken.TRUE);
case ByteBufSerdeConstants.EMPTY_STRING_VALUE:
return wrapTokenForContext(new StringToken(""));
case ByteBufSerdeConstants.VARNUM_VARIANT_INT8:
return wrapTokenForContext(new ByteToken(buf.readByte()));
case ByteBufSerdeConstants.VARNUM_VARIANT_INT16:
return wrapTokenForContext(new IntToken(buf.readShort()));
case ByteBufSerdeConstants.VARNUM_VARIANT_INT32:
return wrapTokenForContext(new IntToken(buf.readInt()));
case ByteBufSerdeConstants.VARNUM_VARIANT_INT64: {
long value = buf.readLong();
if (value >= Integer.MIN_VALUE && value <= Integer.MAX_VALUE) {
return wrapTokenForContext(new IntToken((int) value));
} else {
return wrapTokenForContext(new TweedDataToken() {
@Override
public boolean canReadAsLong() {
return true;
}
@Override
public long readAsLong() {
return value;
}
});
}
}
case ByteBufSerdeConstants.VARNUM_VARIANT_FLOAT:
return wrapTokenForContext(new TweedDataToken() {
private final float value = buf.readFloat();
@Override
public boolean canReadAsFloat() {
return true;
}
@Override
public float readAsFloat() {
return value;
}
});
case ByteBufSerdeConstants.VARNUM_VARIANT_DOUBLE:
return wrapTokenForContext(new TweedDataToken() {
private final double value = buf.readDouble();
@Override
public boolean canReadAsDouble() {
return true;
}
@Override
public double readAsDouble() {
return value;
}
});
case ByteBufSerdeConstants.COMPLEX_VARIANT_STRING: {
int length = buf.readInt();
ByteBuf byteBuf = buf.readBytes(length);
return wrapTokenForContext(new StringToken(byteBuf.toString(StandardCharsets.UTF_8)));
}
case ByteBufSerdeConstants.COMPLEX_VARIANT_LIST: {
TweedDataToken token = wrapTokenForContext(TweedDataTokens.getListStart(), false);
contextStack.push(Context.LIST);
return token;
}
case ByteBufSerdeConstants.COMPLEX_VARIANT_MAP: {
TweedDataToken token = wrapTokenForContext(TweedDataTokens.getMapStart(), false);
contextStack.push(Context.MAP);
return token;
}
case ByteBufSerdeConstants.COMPLEX_VARIANT_END: {
Context context = contextStack.pop();
return wrapTokenForContext(
context == Context.MAP
? TweedDataTokens.getMapEnd()
: TweedDataTokens.getListEnd()
);
}
default:
int specialEmbedType = b & ByteBufSerdeConstants.SPECIAL_EMBED_TYPE_MASK;
if (specialEmbedType == ByteBufSerdeConstants.UINT6_TYPE) {
return wrapTokenForContext(new ByteToken((byte) (b & ByteBufSerdeConstants.SPECIAL_EMBED_VALUE_MASK)));
} else if (specialEmbedType == ByteBufSerdeConstants.SMALL_STRING_TYPE) {
int length = (b & ByteBufSerdeConstants.SPECIAL_EMBED_VALUE_MASK) + 1;
ByteBuf byteBuf = buf.readBytes(length);
return wrapTokenForContext(new StringToken(byteBuf.toString(StandardCharsets.UTF_8)));
}
throw TweedDataReadException.builder()
.message("Unknown type byte value " + Integer.toBinaryString(b))
.build();
}
}
private TweedDataToken wrapTokenForContext(TweedDataToken token) {
return wrapTokenForContext(token, true);
}
private TweedDataToken wrapTokenForContext(TweedDataToken token, boolean isValueEnd) {
Context context = contextStack.peek();
if (context == null) {
return token;
} else if (context == Context.LIST) {
return TweedDataTokens.asListValue(token);
} else if (context == Context.MAP) {
contextStack.push(Context.MAP_VALUE);
return TweedDataTokens.asMapEntryKey(token);
} else {
if (isValueEnd) {
contextStack.pop();
}
return TweedDataTokens.asMapEntryValue(token);
}
}
@Override
public void close() {
}
private enum Context {
LIST, MAP, MAP_VALUE
}
@RequiredArgsConstructor
private static class BooleanToken implements TweedDataToken {
public static final BooleanToken FALSE = new BooleanToken(false);
public static final BooleanToken TRUE = new BooleanToken(true);
private final boolean value;
@Override
public boolean canReadAsBoolean() {
return true;
}
@Override
public boolean readAsBoolean() {
return value;
}
}
@RequiredArgsConstructor
private static class ByteToken implements TweedDataToken {
private final byte value;
@Override
public boolean canReadAsByte() {
return true;
}
@Override
public byte readAsByte() {
return value;
}
@Override
public boolean canReadAsShort() {
return true;
}
@Override
public short readAsShort() {
return value;
}
@Override
public boolean canReadAsInt() {
return true;
}
@Override
public int readAsInt() {
return value;
}
@Override
public boolean canReadAsLong() {
return true;
}
@Override
public long readAsLong() {
return value;
}
}
@RequiredArgsConstructor
public static class IntToken implements TweedDataToken {
private final int value;
@Override
public boolean canReadAsByte() {
return value >= Byte.MIN_VALUE && value <= Byte.MAX_VALUE;
}
@Override
public byte readAsByte() {
return (byte) value;
}
@Override
public boolean canReadAsShort() {
return value >= Short.MIN_VALUE && value <= Short.MAX_VALUE;
}
@Override
public short readAsShort() {
return (short) value;
}
@Override
public boolean canReadAsInt() {
return true;
}
@Override
public int readAsInt() {
return value;
}
@Override
public boolean canReadAsLong() {
return true;
}
@Override
public long readAsLong() {
return value;
}
}
@RequiredArgsConstructor
public static class StringToken implements TweedDataToken {
private final String value;
@Override
public boolean canReadAsString() {
return true;
}
@Override
public String readAsString() {
return value;
}
}
}

View File

@@ -0,0 +1,109 @@
package de.siphalor.tweed5.minecraft.networking.api;
import de.siphalor.tweed5.serde_api.api.TweedDataWriter;
import de.siphalor.tweed5.serde_api.api.decoration.TweedDataDecoration;
import de.siphalor.tweed5.minecraft.networking.impl.ByteBufSerdeConstants;
import io.netty.buffer.ByteBuf;
import lombok.RequiredArgsConstructor;
import java.nio.charset.StandardCharsets;
@RequiredArgsConstructor
public class RawByteBufWriter implements TweedDataWriter {
protected final ByteBuf buf;
@Override
public void visitNull() {
buf.writeByte(ByteBufSerdeConstants.NULL_VALUE);
}
@Override
public void visitBoolean(boolean value) {
if (value) {
buf.writeByte(ByteBufSerdeConstants.TRUE_VALUE);
} else {
buf.writeByte(ByteBufSerdeConstants.FALSE_VALUE);
}
}
@Override
public void visitByte(byte value) {
buf.writeByte(ByteBufSerdeConstants.VARNUM_VARIANT_INT8);
buf.writeByte(Byte.toUnsignedInt(value));
}
@Override
public void visitShort(short value) {
buf.writeByte(ByteBufSerdeConstants.VARNUM_VARIANT_INT16);
buf.writeShort(Short.toUnsignedInt(value));
}
@Override
public void visitInt(int value) {
buf.writeByte(ByteBufSerdeConstants.VARNUM_VARIANT_INT32);
buf.writeInt(value);
}
@Override
public void visitLong(long value) {
buf.writeByte(ByteBufSerdeConstants.VARNUM_VARIANT_INT64);
buf.writeLong(value);
}
@Override
public void visitFloat(float value) {
buf.writeByte(ByteBufSerdeConstants.VARNUM_VARIANT_FLOAT);
buf.writeFloat(value);
}
@Override
public void visitDouble(double value) {
buf.writeByte(ByteBufSerdeConstants.VARNUM_VARIANT_DOUBLE);
buf.writeDouble(value);
}
@Override
public void visitString(String value) {
writeStringBytes(value.getBytes(StandardCharsets.UTF_8));
}
protected void writeStringBytes(byte[] bytes) {
buf.writeByte(ByteBufSerdeConstants.COMPLEX_VARIANT_STRING);
buf.writeInt(bytes.length);
buf.writeBytes(bytes);
}
@Override
public void visitListStart() {
buf.writeByte(ByteBufSerdeConstants.COMPLEX_VARIANT_LIST);
}
@Override
public void visitListEnd() {
buf.writeByte(ByteBufSerdeConstants.COMPLEX_VARIANT_END);
}
@Override
public void visitMapStart() {
buf.writeByte(ByteBufSerdeConstants.COMPLEX_VARIANT_MAP);
}
@Override
public void visitMapEntryKey(String key) {
visitString(key);
}
@Override
public void visitMapEnd() {
buf.writeByte(ByteBufSerdeConstants.COMPLEX_VARIANT_END);
}
@Override
public void visitDecoration(TweedDataDecoration decoration) {
// ignored
}
@Override
public void close() {
}
}

View File

@@ -0,0 +1,87 @@
package de.siphalor.tweed5.minecraft.networking.api;
import de.siphalor.tweed5.minecraft.networking.impl.ByteBufSerdeConstants;
import io.netty.buffer.ByteBuf;
import java.nio.charset.StandardCharsets;
public class SlightlyCompressedByteBufWriter extends RawByteBufWriter {
public SlightlyCompressedByteBufWriter(ByteBuf buf) {
super(buf);
}
@Override
public void visitByte(byte value) {
int v = Byte.toUnsignedInt(value);
if (v <= 0b11_1111) {
buf.writeByte(ByteBufSerdeConstants.UINT6_TYPE + (v & 0b11_1111));
} else {
buf.writeByte(ByteBufSerdeConstants.VARNUM_VARIANT_INT8);
buf.writeByte(v);
}
}
@Override
public void visitShort(short value) {
int v = Short.toUnsignedInt(value);
if (v <= 0b11_1111) {
writeUint6(v);
} else if (v <= 0b0111_1111) {
buf.writeByte(ByteBufSerdeConstants.VARNUM_VARIANT_INT8);
buf.writeByte(v & 0b0111_1111);
} else if (v >= 0b1000_0000_0000_0000 && v <= 0b1000_0000_0111_1111) {
buf.writeByte(ByteBufSerdeConstants.VARNUM_VARIANT_INT8);
buf.writeByte(v & 0b0111_1111 | 0b1000_0000);
} else {
buf.writeByte(ByteBufSerdeConstants.VARNUM_VARIANT_INT16);
buf.writeShort(v);
}
}
@Override
public void visitInt(int value) {
if (value >= 0 && value <= 0b11_1111) {
writeUint6(value);
} else if (value >= Byte.MIN_VALUE && value <= Byte.MAX_VALUE) {
buf.writeByte(ByteBufSerdeConstants.VARNUM_VARIANT_INT8);
if (value < 0) value |= 0b1000_0000;
buf.writeByte(value);
} else if (value >= Short.MIN_VALUE && value <= Short.MAX_VALUE) {
buf.writeByte(ByteBufSerdeConstants.VARNUM_VARIANT_INT16);
if (value < 0) value |= 0b1000_0000_0000_0000;
buf.writeShort(value);
} else {
buf.writeByte(ByteBufSerdeConstants.VARNUM_VARIANT_INT32);
buf.writeInt(value);
}
}
@Override
public void visitLong(long value) {
if (value >= Integer.MIN_VALUE && value <= Integer.MAX_VALUE) {
visitInt((int) value);
} else {
buf.writeByte(ByteBufSerdeConstants.VARNUM_VARIANT_INT64);
buf.writeLong(value);
}
}
private void writeUint6(int value) {
buf.writeByte(ByteBufSerdeConstants.UINT6_TYPE | (value & 0b11_1111));
}
@Override
public void visitString(String value) {
if (value.isEmpty()) {
buf.writeByte(ByteBufSerdeConstants.EMPTY_STRING_VALUE);
return;
}
byte[] bytes = value.getBytes(StandardCharsets.UTF_8);
if (bytes.length <= 0b100_0000) {
buf.writeByte(ByteBufSerdeConstants.SMALL_STRING_TYPE | (bytes.length - 1));
buf.writeBytes(bytes);
} else {
writeStringBytes(bytes);
}
}
}

View File

@@ -1,4 +1,4 @@
@NullMarked
package de.siphalor.tweed5.dataapi.api.decoration;
package de.siphalor.tweed5.minecraft.networking.api;
import org.jspecify.annotations.NullMarked;

View File

@@ -0,0 +1,36 @@
package de.siphalor.tweed5.minecraft.networking.impl;
import lombok.NoArgsConstructor;
@NoArgsConstructor(access = lombok.AccessLevel.PRIVATE)
public class ByteBufSerdeConstants {
public static final int TYPE_MASK = 0b1111_0000;
public static final int VALUE_MASK = 0b0000_1111;
public static final int CONST_TYPE = 0;
public static final int NULL_VALUE = 0;
public static final int FALSE_VALUE = 1;
public static final int TRUE_VALUE = 0b10;
public static final int EMPTY_STRING_VALUE = 0b11;
public static final int VARNUM_TYPE = 0b0001_0000;
public static final int VARNUM_VARIANT_INT8 = VARNUM_TYPE;
public static final int VARNUM_VARIANT_INT16 = VARNUM_TYPE | 0b0001;
public static final int VARNUM_VARIANT_INT32 = VARNUM_TYPE | 0b0010;
public static final int VARNUM_VARIANT_INT64 = VARNUM_TYPE | 0b0011;
public static final int VARNUM_VARIANT_FLOAT = VARNUM_TYPE | 0b1000;
public static final int VARNUM_VARIANT_DOUBLE = VARNUM_TYPE | 0b1001;
public static final int COMPLEX_TYPE = 0b0010_0000;
public static final int COMPLEX_VARIANT_STRING = COMPLEX_TYPE;
public static final int COMPLEX_VARIANT_LIST = COMPLEX_TYPE | 0b0001;
public static final int COMPLEX_VARIANT_MAP = COMPLEX_TYPE | 0b0010;
public static final int COMPLEX_VARIANT_END = COMPLEX_TYPE | 0b1111;
// 0b01xx_xxxx is reserved for future use
public static final int SPECIAL_EMBED_TYPE_MASK = 0b1100_0000;
public static final int SPECIAL_EMBED_VALUE_MASK = 0b0011_1111;
public static final int UINT6_TYPE = 0b1000_0000;
public static final int SMALL_STRING_TYPE = 0b1100_0000;
}

View File

@@ -0,0 +1,4 @@
@NullMarked
package de.siphalor.tweed5.minecraft.networking.impl;
import org.jspecify.annotations.NullMarked;

View File

@@ -0,0 +1,157 @@
package de.siphalor.tweed5.minecraft.network.api;
import de.siphalor.tweed5.serde_api.api.TweedDataReadException;
import de.siphalor.tweed5.serde_api.api.TweedDataToken;
import de.siphalor.tweed5.serde_api.api.TweedDataWriter;
import de.siphalor.tweed5.minecraft.networking.api.ByteBufReader;
import de.siphalor.tweed5.minecraft.networking.api.RawByteBufWriter;
import de.siphalor.tweed5.minecraft.networking.api.SlightlyCompressedByteBufWriter;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import lombok.SneakyThrows;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
import java.util.function.Function;
import java.util.stream.Stream;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.junit.jupiter.params.provider.Arguments.argumentSet;
public class ByteBufReaderWriterTest {
@ParameterizedTest
@MethodSource("testParams")
@SneakyThrows
void test(Function<ByteBuf, TweedDataWriter> writerConstructor) {
ByteBuf buffer = Unpooled.buffer();
try (TweedDataWriter writer = writerConstructor.apply(buffer)) {
writer.visitMapStart();
writer.visitMapEntryKey("first");
writer.visitNull();
writer.visitMapEntryKey("kind-of a_weird/key!");
writer.visitListStart();
writer.visitByte((byte) 12);
writer.visitByte((byte) -12);
writer.visitByte((byte) 123);
writer.visitListEnd();
writer.visitMapEntryKey("nums");
writer.visitListStart();
writer.visitShort((short) 1234);
writer.visitInt(4321);
writer.visitInt(Integer.MAX_VALUE);
writer.visitLong(Long.MAX_VALUE);
writer.visitFloat(1234.5678f);
writer.visitDouble(1234.5678);
writer.visitListEnd();
writer.visitMapEntryKey("other");
writer.visitString("Hello World!");
writer.visitMapEnd();
}
System.out.println("Buffer size is: " + buffer.writerIndex());
assertThat(buffer.readerIndex()).isZero();
try (ByteBufReader reader = new ByteBufReader(buffer)) {
assertThat(reader.readToken()).extracting(TweedDataToken::isMapStart).isEqualTo(true);
assertNextMapKey(reader.readToken(), "first");
assertThat(reader.readToken()).extracting(TweedDataToken::isNull).isEqualTo(true);
assertNextMapKey(reader.readToken(), "kind-of a_weird/key!");
assertThat(reader.readToken()).extracting(TweedDataToken::isListStart).isEqualTo(true);
assertByteToken(reader.readToken(), (byte) 12);
assertByteToken(reader.readToken(), (byte) -12);
assertByteToken(reader.readToken(), (byte) 123);
assertThat(reader.readToken()).extracting(TweedDataToken::isListEnd).isEqualTo(true);
assertNextMapKey(reader.readToken(), "nums");
assertThat(reader.readToken()).extracting(TweedDataToken::isListStart).isEqualTo(true);
assertThat(reader.readToken()).satisfies(
token -> assertThat(token.canReadAsByte()).isFalse(),
token -> assertThat(token.canReadAsShort()).isTrue(),
token -> assertThat(token.readAsShort()).isEqualTo((short) 1234),
token -> assertThat(token.canReadAsInt()).isTrue(),
token -> assertThat(token.readAsInt()).isEqualTo(1234),
token -> assertThat(token.canReadAsLong()).isTrue(),
token -> assertThat(token.readAsLong()).isEqualTo(1234L)
);
assertThat(reader.readToken()).satisfies(
token -> assertThat(token.canReadAsByte()).isFalse(),
token -> assertThat(token.canReadAsShort()).isTrue(),
token -> assertThat(token.readAsShort()).isEqualTo((short) 4321),
token -> assertThat(token.canReadAsInt()).isTrue(),
token -> assertThat(token.readAsInt()).isEqualTo(4321),
token -> assertThat(token.canReadAsLong()).isTrue(),
token -> assertThat(token.readAsLong()).isEqualTo(4321L)
);
assertThat(reader.readToken()).satisfies(
token -> assertThat(token.canReadAsByte()).isFalse(),
token -> assertThat(token.canReadAsShort()).isFalse(),
token -> assertThat(token.canReadAsInt()).isTrue(),
token -> assertThat(token.readAsInt()).isEqualTo(Integer.MAX_VALUE),
token -> assertThat(token.canReadAsLong()).isTrue(),
token -> assertThat(token.readAsLong()).isEqualTo(Integer.MAX_VALUE)
);
assertThat(reader.readToken()).satisfies(
token -> assertThat(token.canReadAsByte()).isFalse(),
token -> assertThat(token.canReadAsShort()).isFalse(),
token -> assertThat(token.canReadAsInt()).isFalse(),
token -> assertThat(token.canReadAsLong()).isTrue(),
token -> assertThat(token.readAsLong()).isEqualTo(Long.MAX_VALUE)
);
assertThat(reader.readToken()).satisfies(
token -> assertThat(token.canReadAsFloat()).isTrue(),
token -> assertThat(token.readAsFloat()).isEqualTo(1234.5678f)
);
assertThat(reader.readToken()).satisfies(
token -> assertThat(token.canReadAsDouble()).isTrue(),
token -> assertThat(token.readAsDouble()).isEqualTo(1234.5678)
);
assertThat(reader.readToken()).extracting(TweedDataToken::isListEnd).isEqualTo(true);
assertNextMapKey(reader.readToken(), "other");
assertThat(reader.readToken()).satisfies(
token -> assertThat(token.canReadAsString()).isTrue(),
token -> assertThat(token.readAsString()).isEqualTo("Hello World!"),
token -> assertThat(token.isMapEntryValue()).isTrue()
);
assertThat(reader.readToken()).extracting(TweedDataToken::isMapEnd).isEqualTo(true);
assertThatThrownBy(reader::readToken).isInstanceOf(TweedDataReadException.class);
}
buffer.release();
}
private void assertNextMapKey(TweedDataToken dataToken, String key) {
assertThat(dataToken).satisfies(
token -> assertThat(token.isMapEntryKey()).isTrue(),
token -> assertThat(token.canReadAsString()).isTrue(),
token -> assertThat(token.readAsString()).isEqualTo(key)
);
}
private void assertByteToken(TweedDataToken dataToken, byte value) {
assertThat(dataToken).satisfies(
token -> assertThat(token.canReadAsByte()).isTrue(),
token -> assertThat(token.readAsByte()).isEqualTo(value),
token -> assertThat(token.canReadAsShort()).isTrue(),
token -> assertThat(token.readAsShort()).isEqualTo(value),
token -> assertThat(token.canReadAsInt()).isTrue(),
token -> assertThat(token.readAsInt()).isEqualTo(value),
token -> assertThat(token.canReadAsLong()).isTrue(),
token -> assertThat(token.readAsLong()).isEqualTo(value)
);
}
static Stream<Arguments> testParams() {
return Stream.of(
argumentSet(
RawByteBufWriter.class.getSimpleName(),
((Function<ByteBuf, ?>) RawByteBufWriter::new)
),
argumentSet(
SlightlyCompressedByteBufWriter.class.getSimpleName(),
((Function<ByteBuf, ?>) SlightlyCompressedByteBufWriter::new)
)
);
}
}

View File

@@ -44,7 +44,7 @@ dependencyResolutionManagement {
}
create("mcLibs") {
val mcVersionDescriptor = providers.gradleProperty("minecraft.version.descriptor").get()
from(files("gradle/mc-$mcVersionDescriptor/mc.versions.toml"))
from(files("gradle/mc-$mcVersionDescriptor/mcLibs.versions.toml"))
}
}
}
@@ -56,6 +56,7 @@ includeNormalModule("bundle-pojo-weaving")
includeNormalModule("coat-bridge")
includeNormalModule("fabric-helper")
includeNormalModule("logging")
includeNormalModule("networking")
fun includeNormalModule(name: String) {
includeAs("tweed5-$name", name)

View File

@@ -6,6 +6,7 @@ plugins {
dependencies {
implementation(project(":tweed5-core"))
compileOnly(project(":tweed5-serde-extension"))
testImplementation(project(":tweed5-default-extensions"))
testImplementation(project(":tweed5-serde-extension"))
testImplementation(project(":tweed5-serde-hjson"))
}

View File

@@ -9,17 +9,20 @@ import de.siphalor.tweed5.core.api.entry.ConfigEntry;
import de.siphalor.tweed5.core.api.entry.ConfigEntryVisitor;
import de.siphalor.tweed5.core.api.extension.TweedExtensionSetupContext;
import de.siphalor.tweed5.core.api.middleware.Middleware;
import de.siphalor.tweed5.data.extension.api.TweedEntryReadException;
import de.siphalor.tweed5.data.extension.api.TweedEntryReader;
import de.siphalor.tweed5.data.extension.api.TweedEntryWriter;
import de.siphalor.tweed5.data.extension.api.TweedReadContext;
import de.siphalor.tweed5.data.extension.api.extension.ReadWriteExtensionSetupContext;
import de.siphalor.tweed5.data.extension.api.extension.ReadWriteRelatedExtension;
import de.siphalor.tweed5.data.extension.impl.TweedEntryReaderWriterImpls;
import de.siphalor.tweed5.dataapi.api.*;
import de.siphalor.tweed5.dataapi.api.decoration.TweedDataDecoration;
import de.siphalor.tweed5.patchwork.api.Patchwork;
import de.siphalor.tweed5.patchwork.api.PatchworkPartAccess;
import de.siphalor.tweed5.serde.extension.api.TweedEntryReader;
import de.siphalor.tweed5.serde.extension.api.TweedEntryWriter;
import de.siphalor.tweed5.serde.extension.api.extension.ReadWriteExtensionSetupContext;
import de.siphalor.tweed5.serde.extension.api.extension.ReadWriteRelatedExtension;
import de.siphalor.tweed5.serde.extension.api.extension.ReaderMiddlewareContext;
import de.siphalor.tweed5.serde.extension.api.extension.WriterMiddlewareContext;
import de.siphalor.tweed5.serde.extension.api.read.result.TweedReadResult;
import de.siphalor.tweed5.serde.extension.impl.TweedEntryReaderWriterImpls;
import de.siphalor.tweed5.serde_api.api.DelegatingTweedDataWriter;
import de.siphalor.tweed5.serde_api.api.TweedDataUnsupportedValueException;
import de.siphalor.tweed5.serde_api.api.TweedDataVisitor;
import de.siphalor.tweed5.serde_api.api.decoration.TweedDataDecoration;
import de.siphalor.tweed5.utils.api.UniqueSymbol;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
@@ -38,6 +41,8 @@ public class AttributesReadWriteFilterExtensionImpl
private static final Set<String> MIDDLEWARES_MUST_COME_AFTER = Collections.emptySet();
private static final UniqueSymbol TWEED_DATA_NOTHING_VALUE = new UniqueSymbol("nothing (skip value)");
public static final Object NOOP_MARKER = new Object();
private final ConfigContainer<?> configContainer;
private @Nullable AttributesExtension attributesExtension;
private final Set<String> filterableAttributes = new HashSet<>();
@@ -155,7 +160,7 @@ public class AttributesReadWriteFilterExtensionImpl
}
}
private class ReaderMiddleware implements Middleware<TweedEntryReader<?, ?>> {
private class ReaderMiddleware implements Middleware<TweedEntryReader<?, ?>, ReaderMiddlewareContext> {
@Override
public String id() {
return EXTENSION_ID;
@@ -172,32 +177,28 @@ public class AttributesReadWriteFilterExtensionImpl
}
@Override
public TweedEntryReader<?, ?> process(TweedEntryReader<?, ?> inner) {
public TweedEntryReader<?, ?> process(TweedEntryReader<?, ?> inner, ReaderMiddlewareContext context) {
assert readWriteContextDataAccess != null;
//noinspection unchecked
TweedEntryReader<Object, ConfigEntry<Object>> innerCasted
= (TweedEntryReader<Object, @NonNull ConfigEntry<Object>>) inner;
return new TweedEntryReader<@Nullable Object, ConfigEntry<Object>>() {
@Override
public @Nullable Object read(
TweedDataReader reader,
ConfigEntry<Object> entry,
TweedReadContext context
) throws TweedEntryReadException {
ReadWriteContextCustomData contextData = context.extensionsData().get(readWriteContextDataAccess);
if (contextData == null || doFiltersMatch(entry, contextData)) {
return innerCasted.read(reader, entry, context);
}
TweedEntryReaderWriterImpls.NOOP_READER_WRITER.read(reader, entry, context);
// TODO: this should result in a noop instead of a null value
return null;
return (TweedEntryReader<Object, ConfigEntry<Object>>) (reader, entry, readContext) -> {
ReadWriteContextCustomData contextData = readContext.extensionsData().get(readWriteContextDataAccess);
if (contextData == null) {
contextData = new ReadWriteContextCustomData();
readContext.extensionsData().set(readWriteContextDataAccess, contextData);
}
if (!doFiltersMatch(entry, contextData)) {
TweedEntryReaderWriterImpls.NOOP_READER_WRITER.read(reader, entry, readContext);
return TweedReadResult.empty();
}
return innerCasted.read(reader, entry, readContext);
};
}
}
private class WriterMiddleware implements Middleware<TweedEntryWriter<?, ?>> {
private class WriterMiddleware implements Middleware<TweedEntryWriter<?, ?>, WriterMiddlewareContext> {
@Override
public String id() {
return EXTENSION_ID;
@@ -214,18 +215,18 @@ public class AttributesReadWriteFilterExtensionImpl
}
@Override
public TweedEntryWriter<?, ?> process(TweedEntryWriter<?, ?> inner) {
public TweedEntryWriter<?, ?> process(TweedEntryWriter<?, ?> inner, WriterMiddlewareContext context) {
assert readWriteContextDataAccess != null;
//noinspection unchecked
TweedEntryWriter<Object, ConfigEntry<Object>> innerCasted
= (TweedEntryWriter<Object, @NonNull ConfigEntry<Object>>) inner;
return (TweedEntryWriter<@Nullable Object, @NonNull ConfigEntry<@Nullable Object>>)
(writer, value, entry, context) -> {
ReadWriteContextCustomData contextData = context.extensionsData()
(writer, value, entry, writeContext) -> {
ReadWriteContextCustomData contextData = writeContext.extensionsData()
.get(readWriteContextDataAccess);
if (contextData == null || contextData.attributeFilters().isEmpty()) {
innerCasted.write(writer, value, entry, context);
innerCasted.write(writer, value, entry, writeContext);
return;
}
@@ -235,7 +236,7 @@ public class AttributesReadWriteFilterExtensionImpl
}
if (doFiltersMatch(entry, contextData)) {
innerCasted.write(writer, value, entry, context);
innerCasted.write(writer, value, entry, writeContext);
} else {
try {
writer.visitValue(TWEED_DATA_NOTHING_VALUE);
@@ -380,5 +381,6 @@ public class AttributesReadWriteFilterExtensionImpl
private static class ReadWriteContextCustomData {
private final Map<String, Set<String>> attributeFilters = new HashMap<>();
private boolean writerInstalled;
private boolean noopHandlerInstalled;
}
}

View File

@@ -8,10 +8,11 @@ import de.siphalor.tweed5.core.api.entry.SimpleConfigEntry;
import de.siphalor.tweed5.core.impl.DefaultConfigContainer;
import de.siphalor.tweed5.core.impl.entry.SimpleConfigEntryImpl;
import de.siphalor.tweed5.core.impl.entry.StaticMapCompoundConfigEntryImpl;
import de.siphalor.tweed5.data.extension.api.ReadWriteExtension;
import de.siphalor.tweed5.data.hjson.HjsonLexer;
import de.siphalor.tweed5.data.hjson.HjsonReader;
import de.siphalor.tweed5.data.hjson.HjsonWriter;
import de.siphalor.tweed5.serde.extension.api.ReadWriteExtension;
import de.siphalor.tweed5.serde.extension.api.read.result.TweedReadResult;
import de.siphalor.tweed5.serde.hjson.HjsonLexer;
import de.siphalor.tweed5.serde.hjson.HjsonReader;
import de.siphalor.tweed5.serde.hjson.HjsonWriter;
import org.jspecify.annotations.NullUnmarked;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
@@ -26,9 +27,9 @@ import java.util.Map;
import static de.siphalor.tweed5.attributesextension.api.AttributesExtension.attribute;
import static de.siphalor.tweed5.attributesextension.api.AttributesExtension.attributeDefault;
import static de.siphalor.tweed5.data.extension.api.ReadWriteExtension.*;
import static de.siphalor.tweed5.data.extension.api.readwrite.TweedEntryReaderWriters.compoundReaderWriter;
import static de.siphalor.tweed5.data.extension.api.readwrite.TweedEntryReaderWriters.stringReaderWriter;
import static de.siphalor.tweed5.serde.extension.api.ReadWriteExtension.*;
import static de.siphalor.tweed5.serde.extension.api.readwrite.TweedEntryReaderWriters.compoundReaderWriter;
import static de.siphalor.tweed5.serde.extension.api.readwrite.TweedEntryReaderWriters.stringReaderWriter;
import static de.siphalor.tweed5.testutils.generic.MapTestUtils.sequencedMap;
import static java.util.Map.entry;
import static org.assertj.core.api.Assertions.assertThat;
@@ -164,19 +165,21 @@ class AttributesReadWriteFilterExtensionImplTest {
}
}
""")));
Map<String, Object> readValue = configContainer.rootEntry().call(read(
TweedReadResult<Map<String, Object>> readResult = configContainer.rootEntry().call(read(
reader,
patchwork -> attributesReadWriteFilterExtension.addFilter(patchwork, "type", "a")
));
assertThat(readValue)
assertThat(readResult)
.extracting(TweedReadResult::value)
.asInstanceOf(map(String.class, Object.class))
.containsEntry("first", "1st")
.containsEntry("second", null)
.doesNotContainKey("second")
.hasEntrySatisfying(
"nested", nested -> assertThat(nested)
.asInstanceOf(map(String.class, Object.class))
.containsEntry("first", "n 1st")
.containsEntry("second", null)
.doesNotContainKey("second")
);
}
}

View File

@@ -0,0 +1,88 @@
package de.siphalor.tweed5.attributesextension.impl.serde.filter;
import de.siphalor.tweed5.attributesextension.api.serde.filter.AttributesReadWriteFilterExtension;
import de.siphalor.tweed5.attributesextension.impl.AttributesExtensionImpl;
import de.siphalor.tweed5.core.api.container.ConfigContainer;
import de.siphalor.tweed5.core.impl.DefaultConfigContainer;
import de.siphalor.tweed5.core.impl.entry.SimpleConfigEntryImpl;
import de.siphalor.tweed5.core.impl.entry.StaticMapCompoundConfigEntryImpl;
import de.siphalor.tweed5.serde.extension.api.ReadWriteExtension;
import de.siphalor.tweed5.serde.extension.api.read.result.TweedReadResult;
import de.siphalor.tweed5.serde.hjson.HjsonLexer;
import de.siphalor.tweed5.serde.hjson.HjsonReader;
import de.siphalor.tweed5.defaultextensions.patch.api.PatchExtension;
import lombok.SneakyThrows;
import org.junit.jupiter.api.Test;
import java.io.StringReader;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import static de.siphalor.tweed5.attributesextension.api.AttributesExtension.attribute;
import static de.siphalor.tweed5.serde.extension.api.ReadWriteExtension.entryReaderWriter;
import static de.siphalor.tweed5.serde.extension.api.readwrite.TweedEntryReaderWriters.compoundReaderWriter;
import static de.siphalor.tweed5.serde.extension.api.readwrite.TweedEntryReaderWriters.stringReaderWriter;
import static de.siphalor.tweed5.testutils.generic.MapTestUtils.sequencedMap;
import static java.util.Map.entry;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.InstanceOfAssertFactories.map;
public class AttributesReadWriteFilterExtensionImplWithPatchInfoTest {
@Test
@SneakyThrows
void testOrder() {
ConfigContainer<Map<String, Object>> configContainer = new DefaultConfigContainer<>();
configContainer.registerExtension(AttributesReadWriteFilterExtensionImpl.class);
configContainer.registerExtension(ReadWriteExtension.class);
configContainer.registerExtension(AttributesExtensionImpl.class);
configContainer.registerExtension(PatchExtension.class);
configContainer.finishExtensionSetup();
var firstEntry = new SimpleConfigEntryImpl<>(configContainer, String.class)
.apply(entryReaderWriter(stringReaderWriter()))
.apply(attribute("type", "a"));
var secondEntry = new SimpleConfigEntryImpl<>(configContainer, String.class)
.apply(entryReaderWriter(stringReaderWriter()));
var rootEntry = new StaticMapCompoundConfigEntryImpl<>(
configContainer,
(Class<Map<String, Object>>) (Class<?>) Map.class,
HashMap::new,
sequencedMap(List.of(entry("first", firstEntry), entry("second", secondEntry)))
)
.apply(entryReaderWriter(compoundReaderWriter()));
configContainer.attachTree(rootEntry);
var readWriteFilterExtension = configContainer.extension(AttributesReadWriteFilterExtension.class).orElseThrow();
readWriteFilterExtension.markAttributeForFiltering("type");
configContainer.initialize();
var readWriteExtension = configContainer.extension(ReadWriteExtension.class).orElseThrow();
var patchExtension = configContainer.extension(PatchExtension.class).orElseThrow();
var filterExtension = configContainer.extension(AttributesReadWriteFilterExtension.class).orElseThrow();
var readExtData = readWriteExtension.createReadWriteContextExtensionsData();
var patchInfo = patchExtension.collectPatchInfo(readExtData);
filterExtension.addFilter(readExtData, "type", "a");
var readResult = readWriteExtension.read(
new HjsonReader(new HjsonLexer(new StringReader("""
{
"first": "FIRST",
"second": "SECOND"
}
"""))),
configContainer.rootEntry(),
readExtData
);
assertThat(readResult)
.extracting(TweedReadResult::value)
.asInstanceOf(map(String.class, Object.class))
.isEqualTo(Map.of("first", "FIRST"));
assertThat(patchInfo.containsEntry(firstEntry)).isTrue();
assertThat(patchInfo.containsEntry(secondEntry)).isFalse();
}
}

View File

@@ -2,7 +2,7 @@ package de.siphalor.tweed5.commentloaderextension.api;
import de.siphalor.tweed5.commentloaderextension.impl.CommentLoaderExtensionImpl;
import de.siphalor.tweed5.core.api.extension.TweedExtension;
import de.siphalor.tweed5.dataapi.api.TweedDataReader;
import de.siphalor.tweed5.serde_api.api.TweedDataReader;
public interface CommentLoaderExtension extends TweedExtension {
Class<? extends CommentLoaderExtension> DEFAULT = CommentLoaderExtensionImpl.class;

View File

@@ -6,11 +6,12 @@ import de.siphalor.tweed5.core.api.container.ConfigContainer;
import de.siphalor.tweed5.core.api.container.ConfigContainerSetupPhase;
import de.siphalor.tweed5.core.api.extension.TweedExtensionSetupContext;
import de.siphalor.tweed5.core.api.middleware.Middleware;
import de.siphalor.tweed5.dataapi.api.IntuitiveVisitingTweedDataReader;
import de.siphalor.tweed5.dataapi.api.TweedDataReadException;
import de.siphalor.tweed5.dataapi.api.TweedDataReader;
import de.siphalor.tweed5.dataapi.api.TweedDataVisitor;
import de.siphalor.tweed5.dataapi.api.decoration.TweedDataDecoration;
import de.siphalor.tweed5.defaultextensions.comment.api.CommentProducerMiddlewareContext;
import de.siphalor.tweed5.serde_api.api.IntuitiveVisitingTweedDataReader;
import de.siphalor.tweed5.serde_api.api.TweedDataReadException;
import de.siphalor.tweed5.serde_api.api.TweedDataReader;
import de.siphalor.tweed5.serde_api.api.TweedDataVisitor;
import de.siphalor.tweed5.serde_api.api.decoration.TweedDataDecoration;
import de.siphalor.tweed5.defaultextensions.comment.api.CommentExtension;
import de.siphalor.tweed5.defaultextensions.comment.api.CommentModifyingExtension;
import de.siphalor.tweed5.defaultextensions.comment.api.CommentProducer;
@@ -44,8 +45,8 @@ public class CommentLoaderExtensionImpl implements CommentLoaderExtension, Comme
}
@Override
public Middleware<CommentProducer> commentMiddleware() {
return new Middleware<CommentProducer>() {
public Middleware<CommentProducer, CommentProducerMiddlewareContext> commentMiddleware() {
return new Middleware<CommentProducer, CommentProducerMiddlewareContext>() {
@Override
public String id() {
return EXTENSION_ID;
@@ -62,7 +63,10 @@ public class CommentLoaderExtensionImpl implements CommentLoaderExtension, Comme
}
@Override
public CommentProducer process(CommentProducer inner) {
public CommentProducer process(CommentProducer inner, CommentProducerMiddlewareContext context) {
if (context.entry().extensionsData().get(loadedCommentAccess) == null) {
return inner;
}
return entry -> {
String loadedComment = entry.extensionsData().get(loadedCommentAccess);
String innerComment = inner.createComment(entry);

View File

@@ -7,7 +7,7 @@ import de.siphalor.tweed5.core.impl.DefaultConfigContainer;
import de.siphalor.tweed5.core.impl.entry.SimpleConfigEntryImpl;
import de.siphalor.tweed5.core.impl.entry.StaticMapCompoundConfigEntryImpl;
import de.siphalor.tweed5.defaultextensions.comment.api.CommentExtension;
import de.siphalor.tweed5.data.gson.GsonReader;
import de.siphalor.tweed5.serde.gson.GsonReader;
import lombok.SneakyThrows;
import org.junit.jupiter.api.Test;

View File

@@ -0,0 +1,13 @@
package de.siphalor.tweed5.core.api.entry;
import java.util.function.Consumer;
public interface AddressableStructuredConfigEntry<T> extends StructuredConfigEntry<T> {
@Override
default AddressableStructuredConfigEntry<T> apply(Consumer<ConfigEntry<T>> function) {
StructuredConfigEntry.super.apply(function);
return this;
}
Object get(T value, String dataKey);
}

View File

@@ -37,10 +37,10 @@ public interface CollectionConfigEntry<E, T extends Collection<E>> extends Struc
if (visitor.enterStructuredEntry(this, value)) {
int index = 0;
for (E item : value) {
String indexString = Integer.toString(index);
if (visitor.enterStructuredSubEntry("element", indexString)) {
SubEntryKey subEntryKey = SubEntryKey.structured("element", Integer.toString(index));
if (visitor.enterSubEntry(subEntryKey)) {
elementEntry().visitInOrder(visitor, item);
visitor.leaveStructuredSubEntry("element", indexString);
visitor.leaveSubEntry(subEntryKey);
}
index++;
}

View File

@@ -4,18 +4,13 @@ import de.siphalor.tweed5.core.api.Arity;
import java.util.function.Consumer;
public interface CompoundConfigEntry<T> extends StructuredConfigEntry<T> {
public interface CompoundConfigEntry<T> extends MutableStructuredConfigEntry<T> {
@Override
default CompoundConfigEntry<T> apply(Consumer<ConfigEntry<T>> function) {
StructuredConfigEntry.super.apply(function);
MutableStructuredConfigEntry.super.apply(function);
return this;
}
<V> void set(T compoundValue, String key, V value);
<V> V get(T compoundValue, String key);
T instantiateCompoundValue();
@Override
default void visitInOrder(ConfigEntryVisitor visitor) {
if (visitor.enterStructuredEntry(this)) {

View File

@@ -3,18 +3,18 @@ package de.siphalor.tweed5.core.api.entry;
public interface ConfigEntryValueVisitor {
<T> void visitEntry(ConfigEntry<T> entry, T value);
default <T> boolean enterStructuredEntry(ConfigEntry<T> entry, T value) {
default <T> boolean enterStructuredEntry(StructuredConfigEntry<T> entry, T value) {
visitEntry(entry, value);
return true;
}
default boolean enterStructuredSubEntry(String entryKey, String valueKey) {
default boolean enterSubEntry(SubEntryKey subEntryKey) {
return true;
}
default void leaveStructuredSubEntry(String entryKey, String valueKey) {
default void leaveSubEntry(SubEntryKey subEntryKey) {
}
default <T> void leaveStructuredEntry(ConfigEntry<T> entry, T value) {
default <T> void leaveStructuredEntry(StructuredConfigEntry<T> entry, T value) {
}
}

View File

@@ -0,0 +1,16 @@
package de.siphalor.tweed5.core.api.entry;
import org.jspecify.annotations.NonNull;
import java.util.function.Consumer;
public interface MutableStructuredConfigEntry<T> extends AddressableStructuredConfigEntry<T> {
@Override
default AddressableStructuredConfigEntry<T> apply(Consumer<ConfigEntry<T>> function) {
AddressableStructuredConfigEntry.super.apply(function);
return this;
}
@NonNull T instantiateValue();
void set(T value, String dataKey, Object subValue);
}

View File

@@ -5,12 +5,12 @@ import org.jetbrains.annotations.Nullable;
import java.util.function.Consumer;
public interface NullableConfigEntry<T extends @Nullable Object> extends StructuredConfigEntry<T> {
public interface NullableConfigEntry<T extends @Nullable Object> extends AddressableStructuredConfigEntry<T> {
String NON_NULL_KEY = ":nonNull";
@Override
default NullableConfigEntry<T> apply(Consumer<ConfigEntry<T>> function) {
StructuredConfigEntry.super.apply(function);
AddressableStructuredConfigEntry.super.apply(function);
return this;
}

View File

@@ -0,0 +1,53 @@
package de.siphalor.tweed5.core.api.entry;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Value;
import org.jspecify.annotations.Nullable;
@Value
@AllArgsConstructor(access = AccessLevel.PRIVATE)
public class SubEntryKey {
String entry;
@Nullable String value;
@Nullable String data;
/**
* Models the key of a transparent entry. Transparent entries are sub entries that only exist in the entry tree
* but are not actually present in the data.
* @param entryKey the key of the entry in the entry tree
* @apiNote The resulting key is not addressable.
*/
public static SubEntryKey transparent(String entryKey) {
return new SubEntryKey(entryKey, null, null);
}
/**
* Models the key of a transparent entry with a data key for use with {@link AddressableStructuredConfigEntry}s.
* @param entryKey the key of the entry in the entry tree
* @param dataKey the "address" of the data in the {@link AddressableStructuredConfigEntry}
*/
public static SubEntryKey transparentAddressable(String entryKey, String dataKey) {
return new SubEntryKey(entryKey, null, dataKey);
}
/**
* A "normal" sub entry key.
* @param entryKey the key of the entry in the entry tree
* @param valueKey a potentially differing key for the user-facing data tree
*/
public static SubEntryKey structured(String entryKey, String valueKey) {
return new SubEntryKey(entryKey, valueKey, null);
}
/**
* A sub entry key for {@link AddressableStructuredConfigEntry}s.
* @param entryKey the key of the entry in the entry tree
* @param valueKey a potentially differing key for the user-facing data tree
* @param dataKey the "address" of the data in the {@link AddressableStructuredConfigEntry}
* @apiNote {@code valueKey} and {@code dataKey} are usually a 1:1 mapping.
*/
public static SubEntryKey addressable(String entryKey, String valueKey, String dataKey) {
return new SubEntryKey(entryKey, valueKey, dataKey);
}
}

View File

@@ -8,11 +8,11 @@ import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public class DefaultMiddlewareContainer<M> implements MiddlewareContainer<M> {
public class DefaultMiddlewareContainer<M, C> implements MiddlewareContainer<M, C> {
private static final String CONTAINER_ID = "";
@Getter
private List<Middleware<M>> middlewares = new ArrayList<>();
private List<Middleware<M, C>> middlewares = new ArrayList<>();
private final Set<String> middlewareIds = new HashSet<>();
private boolean sealed = false;
@@ -22,10 +22,10 @@ public class DefaultMiddlewareContainer<M> implements MiddlewareContainer<M> {
}
@Override
public void registerAll(Collection<Middleware<M>> middlewares) {
public void registerAll(Collection<Middleware<M, C>> middlewares) {
requireUnsealed();
for (Middleware<M> middleware : middlewares) {
for (Middleware<M, C> middleware : middlewares) {
if (middleware.id().isEmpty()) {
throw new IllegalArgumentException("Middleware id cannot be empty");
}
@@ -37,7 +37,7 @@ public class DefaultMiddlewareContainer<M> implements MiddlewareContainer<M> {
}
@Override
public void register(Middleware<M> middleware) {
public void register(Middleware<M, C> middleware) {
requireUnsealed();
if (middleware.id().isEmpty()) {
@@ -76,7 +76,7 @@ public class DefaultMiddlewareContainer<M> implements MiddlewareContainer<M> {
AcyclicGraphSorter sorter = new AcyclicGraphSorter(allMentionedMiddlewareIds.length);
for (Middleware<M> middleware : middlewares) {
for (Middleware<M, C> middleware : middlewares) {
Integer currentIndex = indecesByMiddlewareId.get(middleware.id());
middleware.mustComeAfter().stream()
@@ -87,7 +87,8 @@ public class DefaultMiddlewareContainer<M> implements MiddlewareContainer<M> {
.forEach(afterIndex -> sorter.addEdge(currentIndex, afterIndex));
}
Map<String, Middleware<M>> middlewaresById = middlewares.stream().collect(Collectors.toMap(Middleware::id, Function.identity()));
Map<String, Middleware<M, C>> middlewaresById = middlewares.stream()
.collect(Collectors.toMap(Middleware::id, Function.identity()));
try {
int[] sortedIndeces = sorter.sort();
@@ -107,13 +108,27 @@ public class DefaultMiddlewareContainer<M> implements MiddlewareContainer<M> {
}
@Override
public M process(M inner, C context) {
if (!sealed) {
throw new IllegalStateException("Middleware container has not been sealed");
}
M combined = inner;
for (int i = middlewares.size() - 1; i >= 0; i--) {
Middleware<M, C> middleware = middlewares.get(i);
combined = middleware.process(combined, context);
}
return combined;
}
@Override
@Deprecated
public M process(M inner) {
if (!sealed) {
throw new IllegalStateException("Middleware container has not been sealed");
}
M combined = inner;
for (int i = middlewares.size() - 1; i >= 0; i--) {
Middleware<M> middleware = middlewares.get(i);
Middleware<M, C> middleware = middlewares.get(i);
combined = middleware.process(combined);
}
return combined;

View File

@@ -1,9 +1,11 @@
package de.siphalor.tweed5.core.api.middleware;
import org.jetbrains.annotations.ApiStatus;
import java.util.Collections;
import java.util.Set;
public interface Middleware<M> {
public interface Middleware<M, C> {
String DEFAULT_START = "$default.start";
String DEFAULT_END = "$default.end";
@@ -16,5 +18,13 @@ public interface Middleware<M> {
return Collections.singleton(DEFAULT_START);
}
M process(M inner);
default M process(M inner, C context) {
return process(inner);
}
@Deprecated
@ApiStatus.OverrideOnly
default M process(M inner) {
return inner;
}
}

View File

@@ -2,11 +2,11 @@ package de.siphalor.tweed5.core.api.middleware;
import java.util.Collection;
public interface MiddlewareContainer<M> extends Middleware<M> {
default void registerAll(Collection<Middleware<M>> middlewares) {
public interface MiddlewareContainer<M, C> extends Middleware<M, C> {
default void registerAll(Collection<Middleware<M, C>> middlewares) {
middlewares.forEach(this::register);
}
void register(Middleware<M> middleware);
void register(Middleware<M, C> middleware);
void seal();
Collection<Middleware<M>> middlewares();
Collection<Middleware<M, C>> middlewares();
}

View File

@@ -2,11 +2,12 @@ package de.siphalor.tweed5.core.impl.entry;
import de.siphalor.tweed5.core.api.container.ConfigContainer;
import de.siphalor.tweed5.core.api.entry.*;
import org.jspecify.annotations.NonNull;
import java.util.Collection;
import java.util.function.IntFunction;
public class CollectionConfigEntryImpl<E, T extends Collection<E>> extends BaseConfigEntry<T> implements CollectionConfigEntry<E, T> {
public class CollectionConfigEntryImpl<E, T extends @NonNull Collection<E>> extends BaseConfigEntry<T> implements CollectionConfigEntry<E, T> {
private final IntFunction<T> collectionConstructor;
private final ConfigEntry<E> elementEntry;

View File

@@ -5,6 +5,7 @@ import de.siphalor.tweed5.core.api.entry.BaseConfigEntry;
import de.siphalor.tweed5.core.api.entry.ConfigEntry;
import de.siphalor.tweed5.core.api.entry.ConfigEntryValueVisitor;
import de.siphalor.tweed5.core.api.entry.NullableConfigEntry;
import de.siphalor.tweed5.core.api.entry.SubEntryKey;
import lombok.Getter;
import org.jetbrains.annotations.Nullable;
@@ -24,6 +25,11 @@ public class NullableConfigEntryImpl<T extends @Nullable Object> extends BaseCon
this.nonNullEntry = nonNullEntry;
}
@Override
public T get(T value, String dataKey) {
return value;
}
@Override
public Map<String, ConfigEntry<?>> subEntries() {
return Collections.singletonMap(NON_NULL_KEY, nonNullEntry);
@@ -33,9 +39,10 @@ public class NullableConfigEntryImpl<T extends @Nullable Object> extends BaseCon
public void visitInOrder(ConfigEntryValueVisitor visitor, T value) {
if (value != null) {
if (visitor.enterStructuredEntry(this, value)) {
if (visitor.enterStructuredSubEntry(NON_NULL_KEY, NON_NULL_KEY)) {
SubEntryKey subEntryKey = SubEntryKey.transparentAddressable(NON_NULL_KEY, NON_NULL_KEY);
if (visitor.enterSubEntry(subEntryKey)) {
nonNullEntry.visitInOrder(visitor, value);
visitor.leaveStructuredSubEntry(NON_NULL_KEY, NON_NULL_KEY);
visitor.leaveSubEntry(subEntryKey);
}
visitor.leaveStructuredEntry(this, value);
}

View File

@@ -2,12 +2,13 @@ package de.siphalor.tweed5.core.impl.entry;
import de.siphalor.tweed5.core.api.container.ConfigContainer;
import de.siphalor.tweed5.core.api.entry.*;
import org.jspecify.annotations.NonNull;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.function.IntFunction;
public class StaticMapCompoundConfigEntryImpl<T extends Map<String, Object>> extends BaseConfigEntry<T> implements CompoundConfigEntry<T> {
public class StaticMapCompoundConfigEntryImpl<T extends @NonNull Map<String, Object>> extends BaseConfigEntry<T> implements CompoundConfigEntry<T> {
private final IntFunction<T> mapConstructor;
private final Map<String, ConfigEntry<?>> compoundEntries;
@@ -28,16 +29,15 @@ public class StaticMapCompoundConfigEntryImpl<T extends Map<String, Object>> ext
}
@Override
public <V> void set(T compoundValue, String key, V value) {
requireKey(key);
compoundValue.put(key, value);
public void set(T compoundValue, String dataKey, Object value) {
requireKey(dataKey);
compoundValue.put(dataKey, value);
}
@Override
public <V> V get(T compoundValue, String key) {
requireKey(key);
//noinspection unchecked
return (V) compoundValue.get(key);
public Object get(T compoundValue, String dataKey) {
requireKey(dataKey);
return compoundValue.get(dataKey);
}
private void requireKey(String key) {
@@ -47,29 +47,28 @@ public class StaticMapCompoundConfigEntryImpl<T extends Map<String, Object>> ext
}
@Override
public T instantiateCompoundValue() {
public T instantiateValue() {
return mapConstructor.apply(compoundEntries.size());
}
@Override
public void visitInOrder(ConfigEntryValueVisitor visitor, T value) {
if (visitor.enterStructuredEntry(this, value)) {
if (value != null) {
compoundEntries.forEach((key, entry) -> {
if (visitor.enterStructuredSubEntry(key, key)) {
//noinspection unchecked
((ConfigEntry<Object>) entry).visitInOrder(visitor, value.get(key));
visitor.leaveStructuredSubEntry(key, key);
}
});
}
compoundEntries.forEach((key, entry) -> {
SubEntryKey subEntryKey = SubEntryKey.addressable(key, key, key);
if (visitor.enterSubEntry(subEntryKey)) {
//noinspection unchecked
((ConfigEntry<Object>) entry).visitInOrder(visitor, value.get(key));
visitor.leaveSubEntry(subEntryKey);
}
});
visitor.leaveStructuredEntry(this, value);
}
}
@Override
public T deepCopy(T value) {
T copy = instantiateCompoundValue();
T copy = instantiateValue();
value.forEach((String key, Object element) -> {
//noinspection unchecked
ConfigEntry<Object> elementEntry = (ConfigEntry<Object>) compoundEntries.get(key);

View File

@@ -3,5 +3,5 @@ package de.siphalor.tweed5.defaultextensions.comment.api;
import de.siphalor.tweed5.core.api.middleware.Middleware;
public interface CommentModifyingExtension {
Middleware<CommentProducer> commentMiddleware();
Middleware<CommentProducer, CommentProducerMiddlewareContext> commentMiddleware();
}

View File

@@ -0,0 +1,14 @@
package de.siphalor.tweed5.defaultextensions.comment.api;
import de.siphalor.tweed5.core.api.entry.ConfigEntry;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Value;
@Value
@AllArgsConstructor(access = AccessLevel.PRIVATE)
@Builder
public class CommentProducerMiddlewareContext {
ConfigEntry<?> entry;
}

View File

@@ -6,8 +6,9 @@ import de.siphalor.tweed5.core.api.entry.ConfigEntry;
import de.siphalor.tweed5.core.api.extension.TweedExtension;
import de.siphalor.tweed5.core.api.extension.TweedExtensionSetupContext;
import de.siphalor.tweed5.core.api.middleware.DefaultMiddlewareContainer;
import de.siphalor.tweed5.data.extension.api.extension.ReadWriteExtensionSetupContext;
import de.siphalor.tweed5.data.extension.api.extension.ReadWriteRelatedExtension;
import de.siphalor.tweed5.defaultextensions.comment.api.CommentProducerMiddlewareContext;
import de.siphalor.tweed5.serde.extension.api.extension.ReadWriteExtensionSetupContext;
import de.siphalor.tweed5.serde.extension.api.extension.ReadWriteRelatedExtension;
import de.siphalor.tweed5.defaultextensions.comment.api.CommentExtension;
import de.siphalor.tweed5.defaultextensions.comment.api.CommentModifyingExtension;
import de.siphalor.tweed5.defaultextensions.comment.api.CommentProducer;
@@ -20,7 +21,7 @@ public class CommentExtensionImpl implements ReadWriteRelatedExtension, CommentE
private final ConfigContainer<?> configContainer;
@Getter
private final PatchworkPartAccess<CustomEntryData> customEntryDataAccess;
private final DefaultMiddlewareContainer<CommentProducer> middlewareContainer;
private final DefaultMiddlewareContainer<CommentProducer, CommentProducerMiddlewareContext> middlewareContainer;
@Getter
private @Nullable PatchworkPartAccess<Boolean> writerInstalledReadWriteContextAccess;
@@ -71,7 +72,10 @@ public class CommentExtensionImpl implements ReadWriteRelatedExtension, CommentE
public void recomputeFullComments() {
configContainer.rootEntry().visitInOrder(entry -> {
CustomEntryData entryData = getOrCreateCustomEntryData(entry);
entryData.commentProducer(middlewareContainer.process(_entry -> entryData.baseComment()));
entryData.commentProducer(middlewareContainer.process(
_entry -> entryData.baseComment(),
CommentProducerMiddlewareContext.builder().entry(entry).build()
));
});
}

View File

@@ -2,12 +2,12 @@ package de.siphalor.tweed5.defaultextensions.comment.impl;
import de.siphalor.tweed5.core.api.entry.ConfigEntry;
import de.siphalor.tweed5.core.api.middleware.Middleware;
import de.siphalor.tweed5.data.extension.api.TweedEntryWriter;
import de.siphalor.tweed5.dataapi.api.DelegatingTweedDataWriter;
import de.siphalor.tweed5.dataapi.api.TweedDataVisitor;
import de.siphalor.tweed5.dataapi.api.TweedDataWriter;
import de.siphalor.tweed5.dataapi.api.decoration.TweedDataCommentDecoration;
import de.siphalor.tweed5.dataapi.api.decoration.TweedDataDecoration;
import de.siphalor.tweed5.serde.extension.api.TweedEntryWriter;
import de.siphalor.tweed5.serde.extension.api.extension.WriterMiddlewareContext;
import de.siphalor.tweed5.serde_api.api.DelegatingTweedDataWriter;
import de.siphalor.tweed5.serde_api.api.TweedDataVisitor;
import de.siphalor.tweed5.serde_api.api.decoration.TweedDataCommentDecoration;
import de.siphalor.tweed5.serde_api.api.decoration.TweedDataDecoration;
import de.siphalor.tweed5.patchwork.api.PatchworkPartAccess;
import lombok.RequiredArgsConstructor;
import lombok.Value;
@@ -18,7 +18,7 @@ import java.util.ArrayDeque;
import java.util.Deque;
@RequiredArgsConstructor
class TweedEntryWriterCommentMiddleware implements Middleware<TweedEntryWriter<?, ?>> {
class TweedEntryWriterCommentMiddleware implements Middleware<TweedEntryWriter<?, ?>, WriterMiddlewareContext> {
private final CommentExtensionImpl commentExtension;
@Override
@@ -27,15 +27,15 @@ class TweedEntryWriterCommentMiddleware implements Middleware<TweedEntryWriter<?
}
@Override
public TweedEntryWriter<?, ?> process(TweedEntryWriter<?, ?> inner) {
public TweedEntryWriter<?, ?> process(TweedEntryWriter<?, ?> inner, WriterMiddlewareContext context) {
PatchworkPartAccess<Boolean> writerInstalledAccess = commentExtension.writerInstalledReadWriteContextAccess();
assert writerInstalledAccess != null;
//noinspection unchecked
TweedEntryWriter<Object, ConfigEntry<Object>> innerCasted = (TweedEntryWriter<Object, @NonNull ConfigEntry<Object>>) inner;
return (TweedEntryWriter<Object, @NonNull ConfigEntry<Object>>) (writer, value, entry, context) -> {
if (!Boolean.TRUE.equals(context.extensionsData().get(writerInstalledAccess))) {
context.extensionsData().set(writerInstalledAccess, Boolean.TRUE);
return (TweedEntryWriter<Object, @NonNull ConfigEntry<Object>>) (writer, value, entry, writeContext) -> {
if (!Boolean.TRUE.equals(writeContext.extensionsData().get(writerInstalledAccess))) {
writeContext.extensionsData().set(writerInstalledAccess, Boolean.TRUE);
writer = new MapEntryKeyDeferringWriter(writer);
}
@@ -45,7 +45,7 @@ class TweedEntryWriterCommentMiddleware implements Middleware<TweedEntryWriter<?
writer.visitDecoration(new PiercingCommentDecoration(() -> comment));
}
innerCasted.write(writer, value, entry, context);
innerCasted.write(writer, value, entry, writeContext);
};
}

View File

@@ -3,12 +3,11 @@ package de.siphalor.tweed5.defaultextensions.patch.impl;
import de.siphalor.tweed5.core.api.entry.CompoundConfigEntry;
import de.siphalor.tweed5.core.api.entry.ConfigEntry;
import de.siphalor.tweed5.core.api.middleware.Middleware;
import de.siphalor.tweed5.data.extension.api.TweedEntryReadException;
import de.siphalor.tweed5.data.extension.api.TweedEntryReader;
import de.siphalor.tweed5.data.extension.api.TweedReadContext;
import de.siphalor.tweed5.data.extension.api.extension.ReadWriteExtensionSetupContext;
import de.siphalor.tweed5.data.extension.api.extension.ReadWriteRelatedExtension;
import de.siphalor.tweed5.dataapi.api.TweedDataReader;
import de.siphalor.tweed5.serde.extension.api.TweedEntryReader;
import de.siphalor.tweed5.serde.extension.api.extension.ReadWriteExtensionSetupContext;
import de.siphalor.tweed5.serde.extension.api.extension.ReadWriteRelatedExtension;
import de.siphalor.tweed5.serde.extension.api.extension.ReaderMiddlewareContext;
import de.siphalor.tweed5.serde.extension.api.read.result.TweedReadResult;
import de.siphalor.tweed5.defaultextensions.patch.api.PatchExtension;
import de.siphalor.tweed5.defaultextensions.patch.api.PatchInfo;
import de.siphalor.tweed5.patchwork.api.Patchwork;
@@ -62,7 +61,7 @@ public class PatchExtensionImpl implements PatchExtension, ReadWriteRelatedExten
if (targetValue != null) {
targetCompoundValue = targetValue;
} else {
targetCompoundValue = compoundEntry.instantiateCompoundValue();
targetCompoundValue = compoundEntry.instantiateValue();
}
compoundEntry.subEntries().forEach((key, subEntry) -> {
if (!patchInfo.containsEntry(subEntry)) {
@@ -70,7 +69,7 @@ public class PatchExtensionImpl implements PatchExtension, ReadWriteRelatedExten
}
compoundEntry.set(
targetCompoundValue, key, patch(
subEntry,
(ConfigEntry<Object>) subEntry,
compoundEntry.get(targetCompoundValue, key),
compoundEntry.get(patchValue, key),
patchInfo
@@ -83,33 +82,26 @@ public class PatchExtensionImpl implements PatchExtension, ReadWriteRelatedExten
}
}
private class ReaderMiddleware implements Middleware<TweedEntryReader<?, ?>> {
private class ReaderMiddleware implements Middleware<TweedEntryReader<?, ?>, ReaderMiddlewareContext> {
@Override
public String id() {
return "patch-info-collector";
}
@Override
public TweedEntryReader<?, ?> process(TweedEntryReader<?, ?> inner) {
public TweedEntryReader<?, ?> process(TweedEntryReader<?, ?> inner, ReaderMiddlewareContext context) {
assert readWriteContextDataAccess != null;
//noinspection unchecked
TweedEntryReader<Object, ConfigEntry<Object>> innerCasted =
(TweedEntryReader<Object, @NonNull ConfigEntry<Object>>) inner;
return new TweedEntryReader<@Nullable Object, ConfigEntry<Object>>() {
@Override
public @Nullable Object read(
TweedDataReader reader,
ConfigEntry<Object> entry,
TweedReadContext context
) throws TweedEntryReadException {
Object readValue = innerCasted.read(reader, entry, context);
ReadWriteContextCustomData customData = context.extensionsData().get(readWriteContextDataAccess);
if (customData != null && customData.patchInfo() != null) {
customData.patchInfo().addEntry(entry);
}
return readValue;
return (TweedEntryReader<Object, ConfigEntry<Object>>) (reader, entry, readContext) -> {
TweedReadResult<Object> readResult = innerCasted.read(reader, entry, readContext);
ReadWriteContextCustomData customData = readContext.extensionsData().get(readWriteContextDataAccess);
if (customData != null && customData.patchInfo() != null) {
customData.patchInfo().addEntry(entry);
}
return readResult;
};
}
}

View File

@@ -9,7 +9,11 @@ public interface PathTracking {
void pushPathPart(String pathPart);
void pushEmptyPathPart();
void popPathPart();
String currentPath();
String[] currentPathParts();
}

View File

@@ -2,6 +2,8 @@ package de.siphalor.tweed5.defaultextensions.pather.api;
import de.siphalor.tweed5.core.api.entry.ConfigEntry;
import de.siphalor.tweed5.core.api.entry.ConfigEntryValueVisitor;
import de.siphalor.tweed5.core.api.entry.StructuredConfigEntry;
import de.siphalor.tweed5.core.api.entry.SubEntryKey;
import lombok.RequiredArgsConstructor;
import org.jspecify.annotations.Nullable;
@@ -16,27 +18,31 @@ public class PathTrackingConfigEntryValueVisitor implements ConfigEntryValueVisi
}
@Override
public <T> boolean enterStructuredEntry(ConfigEntry<T> entry, T value) {
public <T> boolean enterStructuredEntry(StructuredConfigEntry<T> entry, T value) {
return delegate.enterStructuredEntry(entry, value);
}
@Override
public boolean enterStructuredSubEntry(String entryKey, String valueKey) {
boolean enter = delegate.enterStructuredSubEntry(entryKey, valueKey);
public boolean enterSubEntry(SubEntryKey subEntryKey) {
boolean enter = delegate.enterSubEntry(subEntryKey);
if (enter) {
pathTracking.pushPathPart(entryKey, valueKey);
if (subEntryKey.value() != null) {
pathTracking.pushPathPart(subEntryKey.entry(), subEntryKey.value());
} else {
pathTracking.pushEmptyValuePathPart(subEntryKey.entry());
}
}
return enter;
}
@Override
public void leaveStructuredSubEntry(String entryKey, String valueKey) {
delegate.leaveStructuredSubEntry(entryKey, valueKey);
public void leaveSubEntry(SubEntryKey subEntryKey) {
delegate.leaveSubEntry(subEntryKey);
pathTracking.popPathPart();
}
@Override
public <T> void leaveStructuredEntry(ConfigEntry<T> entry, T value) {
public <T> void leaveStructuredEntry(StructuredConfigEntry<T> entry, T value) {
delegate.leaveStructuredEntry(entry, value);
}
}

View File

@@ -1,8 +1,8 @@
package de.siphalor.tweed5.defaultextensions.pather.api;
import de.siphalor.tweed5.dataapi.api.TweedDataReadException;
import de.siphalor.tweed5.dataapi.api.TweedDataReader;
import de.siphalor.tweed5.dataapi.api.TweedDataToken;
import de.siphalor.tweed5.serde_api.api.TweedDataReadException;
import de.siphalor.tweed5.serde_api.api.TweedDataReader;
import de.siphalor.tweed5.serde_api.api.TweedDataToken;
import lombok.RequiredArgsConstructor;
import java.util.ArrayDeque;

View File

@@ -1,8 +1,8 @@
package de.siphalor.tweed5.defaultextensions.pather.api;
import de.siphalor.tweed5.dataapi.api.TweedDataUnsupportedValueException;
import de.siphalor.tweed5.dataapi.api.TweedDataVisitor;
import de.siphalor.tweed5.dataapi.api.decoration.TweedDataDecoration;
import de.siphalor.tweed5.serde_api.api.TweedDataUnsupportedValueException;
import de.siphalor.tweed5.serde_api.api.TweedDataVisitor;
import de.siphalor.tweed5.serde_api.api.decoration.TweedDataDecoration;
import lombok.RequiredArgsConstructor;
import org.jspecify.annotations.Nullable;

View File

@@ -1,8 +1,8 @@
package de.siphalor.tweed5.defaultextensions.pather.api;
import de.siphalor.tweed5.core.api.extension.TweedExtension;
import de.siphalor.tweed5.data.extension.api.TweedReadContext;
import de.siphalor.tweed5.data.extension.api.TweedWriteContext;
import de.siphalor.tweed5.serde.extension.api.TweedReadContext;
import de.siphalor.tweed5.serde.extension.api.TweedWriteContext;
import de.siphalor.tweed5.defaultextensions.pather.impl.PatherExtensionImpl;
public interface PatherExtension extends TweedExtension {

View File

@@ -17,6 +17,17 @@ public final class ValuePathTracking implements PathTracking {
valuePathTracking.pushPathPart(valuePathPart);
}
@Override
public void pushEmptyPathPart() {
entryPathTracking.pushEmptyPathPart();
valuePathTracking.pushEmptyPathPart();
}
public void pushEmptyValuePathPart(String entryPathPart) {
entryPathTracking.pushPathPart(entryPathPart);
valuePathTracking.pushEmptyPathPart();
}
@Override
public void popPathPart() {
valuePathTracking.popPathPart();
@@ -31,4 +42,13 @@ public final class ValuePathTracking implements PathTracking {
public String currentEntryPath() {
return entryPathTracking.currentPath();
}
@Override
public String[] currentPathParts() {
return valuePathTracking.currentPathParts();
}
public String[] currentEntryPathParts() {
return entryPathTracking.currentPathParts();
}
}

View File

@@ -1,13 +1,14 @@
package de.siphalor.tweed5.defaultextensions.pather.impl;
import de.siphalor.tweed5.defaultextensions.pather.api.PathTracking;
import org.jspecify.annotations.Nullable;
import java.util.ArrayDeque;
import java.util.Deque;
public class PathTrackingImpl implements PathTracking {
private final StringBuilder pathBuilder = new StringBuilder(256);
private final Deque<String> pathParts = new ArrayDeque<>(50);
private final Deque<@Nullable String> pathParts = new ArrayDeque<>(50);
@Override
public void pushPathPart(String entryPathPart) {
@@ -15,11 +16,18 @@ public class PathTrackingImpl implements PathTracking {
pathBuilder.append(".").append(entryPathPart);
}
@Override
public void pushEmptyPathPart() {
pathParts.push(null);
}
@Override
public void popPathPart() {
if (!pathParts.isEmpty()) {
String poppedPart = pathParts.pop();
pathBuilder.setLength(pathBuilder.length() - poppedPart.length() - 1);
if (poppedPart != null) {
pathBuilder.setLength(pathBuilder.length() - poppedPart.length() - 1);
}
}
}
@@ -27,4 +35,9 @@ public class PathTrackingImpl implements PathTracking {
public String currentPath() {
return pathBuilder.toString();
}
@Override
public String[] currentPathParts() {
return pathParts.toArray(new String[0]);
}
}

View File

@@ -2,11 +2,13 @@ package de.siphalor.tweed5.defaultextensions.pather.impl;
import de.siphalor.tweed5.core.api.entry.ConfigEntry;
import de.siphalor.tweed5.core.api.middleware.Middleware;
import de.siphalor.tweed5.data.extension.api.*;
import de.siphalor.tweed5.data.extension.api.extension.ReadWriteExtensionSetupContext;
import de.siphalor.tweed5.data.extension.api.extension.ReadWriteRelatedExtension;
import de.siphalor.tweed5.dataapi.api.TweedDataReader;
import de.siphalor.tweed5.dataapi.api.TweedDataVisitor;
import de.siphalor.tweed5.serde.extension.api.*;
import de.siphalor.tweed5.serde.extension.api.extension.ReadWriteExtensionSetupContext;
import de.siphalor.tweed5.serde.extension.api.extension.ReadWriteRelatedExtension;
import de.siphalor.tweed5.serde.extension.api.extension.ReaderMiddlewareContext;
import de.siphalor.tweed5.serde.extension.api.extension.WriterMiddlewareContext;
import de.siphalor.tweed5.serde_api.api.TweedDataReader;
import de.siphalor.tweed5.serde_api.api.TweedDataVisitor;
import de.siphalor.tweed5.defaultextensions.pather.api.PathTracking;
import de.siphalor.tweed5.defaultextensions.pather.api.PathTrackingDataReader;
import de.siphalor.tweed5.defaultextensions.pather.api.PathTrackingDataVisitor;
@@ -48,75 +50,62 @@ public class PatherExtensionImpl implements PatherExtension, ReadWriteRelatedExt
return pathTracking.currentPath();
}
private Middleware<TweedEntryReader<?, ?>> createEntryReaderMiddleware() {
return new Middleware<TweedEntryReader<?, ?>>() {
private Middleware<TweedEntryReader<?, ?>, ReaderMiddlewareContext> createEntryReaderMiddleware() {
return new Middleware<TweedEntryReader<?, ?>, ReaderMiddlewareContext>() {
@Override
public String id() {
return EXTENSION_ID;
}
@Override
public TweedEntryReader<?, ?> process(TweedEntryReader<?, ?> inner) {
public TweedEntryReader<?, ?> process(TweedEntryReader<?, ?> inner, ReaderMiddlewareContext context) {
assert rwContextPathTrackingAccess != null;
//noinspection unchecked
val castedInner = (TweedEntryReader<Object, @NonNull ConfigEntry<Object>>) inner;
return (TweedDataReader reader, ConfigEntry<Object> entry, TweedReadContext context) -> {
PathTracking pathTracking = context.extensionsData().get(rwContextPathTrackingAccess);
return (TweedDataReader reader, ConfigEntry<Object> entry, TweedReadContext readContext) -> {
PathTracking pathTracking = readContext.extensionsData().get(rwContextPathTrackingAccess);
if (pathTracking != null) {
return castedInner.read(reader, entry, context);
return castedInner.read(reader, entry, readContext);
}
pathTracking = PathTracking.create();
context.extensionsData().set(rwContextPathTrackingAccess, pathTracking);
try {
return castedInner.read(new PathTrackingDataReader(reader, pathTracking), entry, context);
} catch (TweedEntryReadException e) {
val exceptionPathTracking = e.context().extensionsData().get(rwContextPathTrackingAccess);
if (exceptionPathTracking != null) {
throw new TweedEntryReadException(
"Exception while reading entry at "
+ exceptionPathTracking.currentPath()
+ ": " + e.getMessage(),
e
);
} else {
throw e;
}
}
readContext.extensionsData().set(rwContextPathTrackingAccess, pathTracking);
return castedInner.read(new PathTrackingDataReader(reader, pathTracking), entry, readContext);
};
}
};
}
private Middleware<TweedEntryWriter<?, ?>> createEntryWriterMiddleware() {
return new Middleware<TweedEntryWriter<?, ?>>() {
private Middleware<TweedEntryWriter<?, ?>, WriterMiddlewareContext> createEntryWriterMiddleware() {
return new Middleware<TweedEntryWriter<?, ?>, WriterMiddlewareContext>() {
@Override
public String id() {
return EXTENSION_ID;
}
@Override
public TweedEntryWriter<?, ?> process(TweedEntryWriter<?, ?> inner) {
public TweedEntryWriter<?, ?> process(TweedEntryWriter<?, ?> inner, WriterMiddlewareContext context) {
assert rwContextPathTrackingAccess != null;
//noinspection unchecked
val castedInner = (TweedEntryWriter<Object, @NonNull ConfigEntry<Object>>) inner;
return (TweedDataVisitor writer, Object value, ConfigEntry<Object> entry, TweedWriteContext context) -> {
PathTracking pathTracking = context.extensionsData().get(rwContextPathTrackingAccess);
return (TweedDataVisitor writer, Object value, ConfigEntry<Object> entry, TweedWriteContext writeContext) -> {
PathTracking pathTracking = writeContext.extensionsData().get(rwContextPathTrackingAccess);
if (pathTracking != null) {
castedInner.write(writer, value, entry, context);
castedInner.write(writer, value, entry, writeContext);
return;
}
pathTracking = PathTracking.create();
context.extensionsData().set(rwContextPathTrackingAccess, pathTracking);
writeContext.extensionsData().set(rwContextPathTrackingAccess, pathTracking);
try {
castedInner.write(new PathTrackingDataVisitor(writer, pathTracking), value, entry, context);
castedInner.write(new PathTrackingDataVisitor(writer, pathTracking), value, entry, writeContext);
} catch (TweedEntryWriteException e) {
val exceptionPathTracking = e.context().extensionsData().get(rwContextPathTrackingAccess);
PathTracking exceptionPathTracking =
e.context().extensionsData().get(rwContextPathTrackingAccess);
if (exceptionPathTracking != null) {
throw new TweedEntryWriteException(
"Exception while writing entry at "

View File

@@ -3,14 +3,15 @@ package de.siphalor.tweed5.defaultextensions.readfallback.impl;
import de.siphalor.tweed5.core.api.container.ConfigContainer;
import de.siphalor.tweed5.core.api.entry.ConfigEntry;
import de.siphalor.tweed5.core.api.middleware.Middleware;
import de.siphalor.tweed5.data.extension.api.TweedEntryReadException;
import de.siphalor.tweed5.data.extension.api.TweedEntryReader;
import de.siphalor.tweed5.data.extension.api.extension.ReadWriteExtensionSetupContext;
import de.siphalor.tweed5.data.extension.api.extension.ReadWriteRelatedExtension;
import de.siphalor.tweed5.serde.extension.api.TweedEntryReader;
import de.siphalor.tweed5.serde.extension.api.extension.ReadWriteExtensionSetupContext;
import de.siphalor.tweed5.serde.extension.api.extension.ReadWriteRelatedExtension;
import de.siphalor.tweed5.defaultextensions.pather.api.PatherExtension;
import de.siphalor.tweed5.defaultextensions.presets.api.PresetsExtension;
import de.siphalor.tweed5.defaultextensions.readfallback.api.ReadFallbackExtension;
import de.siphalor.tweed5.defaultextensions.validation.api.ValidationExtension;
import de.siphalor.tweed5.serde.extension.api.extension.ReaderMiddlewareContext;
import de.siphalor.tweed5.serde.extension.api.read.result.TweedReadResult;
import lombok.extern.apachecommons.CommonsLog;
import org.jspecify.annotations.NonNull;
@@ -30,9 +31,7 @@ public class ReadFallbackExtensionImpl implements ReadFallbackExtension, ReadWri
PresetsExtension presetsExtension = configContainer.extension(PresetsExtension.class)
.orElseThrow(() -> new IllegalStateException(getClass().getSimpleName()
+ " requires " + ReadFallbackExtension.class.getSimpleName()));
PatherExtension patherExtension = configContainer.extension(PatherExtension.class).orElse(null);
context.registerReaderMiddleware(new Middleware<TweedEntryReader<?, ?>>() {
context.registerReaderMiddleware(new Middleware<TweedEntryReader<?, ?>, ReaderMiddlewareContext>() {
@Override
public String id() {
return EXTENSION_ID;
@@ -49,26 +48,19 @@ public class ReadFallbackExtensionImpl implements ReadFallbackExtension, ReadWri
}
@Override
public TweedEntryReader<?, ?> process(TweedEntryReader<?, ?> inner) {
public TweedEntryReader<?, ?> process(TweedEntryReader<?, ?> inner, ReaderMiddlewareContext context) {
//noinspection unchecked
TweedEntryReader<Object, ConfigEntry<Object>> castedInner =
(TweedEntryReader<Object, @NonNull ConfigEntry<Object>>) inner;
return (TweedEntryReader<Object, @NonNull ConfigEntry<Object>>) (reader, entry, context) -> {
try {
return castedInner.read(reader, entry, context);
} catch (TweedEntryReadException e) {
if (patherExtension == null) {
log.error("Failed to read entry: " + e.getMessage(), e);
} else {
log.error(
"Failed to read entry: " + e.getMessage()
+ " at " + patherExtension.getPath(context),
e
);
}
return presetsExtension.presetValue(entry, PresetsExtension.DEFAULT_PRESET_NAME);
}
};
return (TweedEntryReader<Object, @NonNull ConfigEntry<Object>>) (reader, entry, readContext) ->
castedInner.read(reader, entry, readContext).catchError(
issues -> {
Object fallback =
presetsExtension.presetValue(entry, PresetsExtension.DEFAULT_PRESET_NAME);
return TweedReadResult.withIssues(fallback, issues);
},
readContext
);
}
});
}

View File

@@ -8,6 +8,7 @@ import de.siphalor.tweed5.defaultextensions.validation.api.result.ValidationResu
import de.siphalor.tweed5.defaultextensions.validation.api.validators.SimpleValidatorMiddleware;
import de.siphalor.tweed5.defaultextensions.validation.impl.ValidationExtensionImpl;
import de.siphalor.tweed5.patchwork.api.Patchwork;
import org.jetbrains.annotations.UnknownNullability;
import org.jspecify.annotations.Nullable;
import java.util.*;
@@ -55,7 +56,10 @@ public interface ValidationExtension extends TweedExtension {
lastId = id;
}
}
<T> void addValidatorMiddleware(ConfigEntry<T> entry, Middleware<ConfigEntryValidator> validator);
<T> void addValidatorMiddleware(
ConfigEntry<T> entry,
Middleware<ConfigEntryValidator, ValidatorMiddlewareContext> validator
);
ValidationIssues captureValidationIssues(Patchwork readContextExtensionsData);

View File

@@ -3,5 +3,5 @@ package de.siphalor.tweed5.defaultextensions.validation.api;
import de.siphalor.tweed5.core.api.middleware.Middleware;
public interface ValidationProvidingExtension {
Middleware<ConfigEntryValidator> validationMiddleware();
Middleware<ConfigEntryValidator, ValidatorMiddlewareContext> validationMiddleware();
}

View File

@@ -0,0 +1,13 @@
package de.siphalor.tweed5.defaultextensions.validation.api;
import de.siphalor.tweed5.core.api.entry.ConfigEntry;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Value;
@Value
@AllArgsConstructor(access = lombok.AccessLevel.PRIVATE)
@Builder
public class ValidatorMiddlewareContext {
ConfigEntry<?> entry;
}

View File

@@ -3,6 +3,7 @@ package de.siphalor.tweed5.defaultextensions.validation.api.validators;
import de.siphalor.tweed5.core.api.entry.ConfigEntry;
import de.siphalor.tweed5.core.api.middleware.Middleware;
import de.siphalor.tweed5.defaultextensions.validation.api.ConfigEntryValidator;
import de.siphalor.tweed5.defaultextensions.validation.api.ValidatorMiddlewareContext;
import de.siphalor.tweed5.defaultextensions.validation.api.result.ValidationResult;
import lombok.AllArgsConstructor;
import lombok.Getter;
@@ -10,12 +11,12 @@ import org.jspecify.annotations.Nullable;
@Getter
@AllArgsConstructor
public class SimpleValidatorMiddleware implements Middleware<ConfigEntryValidator> {
public class SimpleValidatorMiddleware implements Middleware<ConfigEntryValidator, ValidatorMiddlewareContext> {
String id;
ConfigEntryValidator validator;
@Override
public ConfigEntryValidator process(ConfigEntryValidator inner) {
public ConfigEntryValidator process(ConfigEntryValidator inner, ValidatorMiddlewareContext context) {
return new ConfigEntryValidator() {
@Override
public <T extends @Nullable Object> ValidationResult<T> validate(ConfigEntry<T> configEntry, T value) {

View File

@@ -3,19 +3,15 @@ package de.siphalor.tweed5.defaultextensions.validation.impl;
import de.siphalor.tweed5.core.api.container.ConfigContainer;
import de.siphalor.tweed5.core.api.entry.ConfigEntry;
import de.siphalor.tweed5.core.api.entry.ConfigEntryValueVisitor;
import de.siphalor.tweed5.core.api.entry.StructuredConfigEntry;
import de.siphalor.tweed5.core.api.extension.TweedExtension;
import de.siphalor.tweed5.core.api.extension.TweedExtensionSetupContext;
import de.siphalor.tweed5.core.api.middleware.DefaultMiddlewareContainer;
import de.siphalor.tweed5.core.api.middleware.Middleware;
import de.siphalor.tweed5.core.api.middleware.MiddlewareContainer;
import de.siphalor.tweed5.data.extension.api.TweedEntryReadException;
import de.siphalor.tweed5.data.extension.api.TweedEntryReader;
import de.siphalor.tweed5.data.extension.api.TweedReadContext;
import de.siphalor.tweed5.data.extension.api.extension.ReadWriteExtensionSetupContext;
import de.siphalor.tweed5.data.extension.api.extension.ReadWriteRelatedExtension;
import de.siphalor.tweed5.dataapi.api.TweedDataReader;
import de.siphalor.tweed5.defaultextensions.comment.api.CommentModifyingExtension;
import de.siphalor.tweed5.defaultextensions.comment.api.CommentProducer;
import de.siphalor.tweed5.defaultextensions.comment.api.CommentProducerMiddlewareContext;
import de.siphalor.tweed5.defaultextensions.pather.api.PathTracking;
import de.siphalor.tweed5.defaultextensions.pather.api.PathTrackingConfigEntryValueVisitor;
import de.siphalor.tweed5.defaultextensions.pather.api.PatherExtension;
@@ -23,13 +19,23 @@ import de.siphalor.tweed5.defaultextensions.pather.api.ValuePathTracking;
import de.siphalor.tweed5.defaultextensions.validation.api.ConfigEntryValidator;
import de.siphalor.tweed5.defaultextensions.validation.api.ValidationExtension;
import de.siphalor.tweed5.defaultextensions.validation.api.ValidationProvidingExtension;
import de.siphalor.tweed5.defaultextensions.validation.api.ValidatorMiddlewareContext;
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.ValidationIssues;
import de.siphalor.tweed5.defaultextensions.validation.api.result.ValidationResult;
import de.siphalor.tweed5.patchwork.api.Patchwork;
import de.siphalor.tweed5.patchwork.api.PatchworkPartAccess;
import de.siphalor.tweed5.serde.extension.api.TweedEntryReader;
import de.siphalor.tweed5.serde.extension.api.TweedReadContext;
import de.siphalor.tweed5.serde.extension.api.extension.ReadWriteExtensionSetupContext;
import de.siphalor.tweed5.serde.extension.api.extension.ReadWriteRelatedExtension;
import de.siphalor.tweed5.serde.extension.api.extension.ReaderMiddlewareContext;
import de.siphalor.tweed5.serde.extension.api.read.result.TweedReadIssue;
import de.siphalor.tweed5.serde.extension.api.read.result.TweedReadResult;
import de.siphalor.tweed5.serde_api.api.TweedDataReader;
import lombok.*;
import org.jetbrains.annotations.UnknownNullability;
import org.jspecify.annotations.Nullable;
import java.util.*;
@@ -68,8 +74,8 @@ public class ValidationExtensionImpl implements ReadWriteRelatedExtension, Valid
private final ConfigContainer<?> configContainer;
private final PatchworkPartAccess<CustomEntryData> customEntryDataAccess;
private final MiddlewareContainer<ConfigEntryValidator> entryValidatorMiddlewareContainer
= new DefaultMiddlewareContainer<>();
private final MiddlewareContainer<ConfigEntryValidator, ValidatorMiddlewareContext>
entryValidatorMiddlewareContainer = new DefaultMiddlewareContainer<>();
private @Nullable PatchworkPartAccess<ValidationIssues> readContextValidationIssuesAccess;
private @Nullable PatherExtension patherExtension;
@@ -95,15 +101,15 @@ public class ValidationExtensionImpl implements ReadWriteRelatedExtension, Valid
}
@Override
public Middleware<CommentProducer> commentMiddleware() {
return new Middleware<CommentProducer>() {
public Middleware<CommentProducer, CommentProducerMiddlewareContext> commentMiddleware() {
return new Middleware<CommentProducer, CommentProducerMiddlewareContext>() {
@Override
public String id() {
return EXTENSION_ID;
}
@Override
public CommentProducer process(CommentProducer inner) {
public CommentProducer process(CommentProducer inner, CommentProducerMiddlewareContext context) {
return entry -> {
String baseComment = inner.createComment(entry);
CustomEntryData entryData = entry.extensionsData().get(customEntryDataAccess);
@@ -133,7 +139,10 @@ public class ValidationExtensionImpl implements ReadWriteRelatedExtension, Valid
}
@Override
public <T> void addValidatorMiddleware(ConfigEntry<T> entry, Middleware<ConfigEntryValidator> validator) {
public <T> void addValidatorMiddleware(
ConfigEntry<T> entry,
Middleware<ConfigEntryValidator, ValidatorMiddlewareContext> validator
) {
CustomEntryData entryData = getOrCreateCustomEntryData(entry);
entryData.addValidator(validator);
}
@@ -150,13 +159,20 @@ public class ValidationExtensionImpl implements ReadWriteRelatedExtension, Valid
CustomEntryData entryData = getOrCreateCustomEntryData(configEntry);
if (entryData.validators().isEmpty()) {
entryData.completeValidator(entryValidatorMiddlewareContainer.process(baseValidator));
entryData.completeValidator(entryValidatorMiddlewareContainer.process(
baseValidator,
ValidatorMiddlewareContext.builder().entry(configEntry).build()
));
} else {
DefaultMiddlewareContainer<ConfigEntryValidator> entrySpecificValidatorContainer = new DefaultMiddlewareContainer<>();
DefaultMiddlewareContainer<ConfigEntryValidator, ValidatorMiddlewareContext> entrySpecificValidatorContainer
= new DefaultMiddlewareContainer<>();
entrySpecificValidatorContainer.registerAll(entryValidatorMiddlewareContainer.middlewares());
entrySpecificValidatorContainer.registerAll(entryData.validators());
entrySpecificValidatorContainer.seal();
entryData.completeValidator(entrySpecificValidatorContainer.process(baseValidator));
entryData.completeValidator(entrySpecificValidatorContainer.process(
baseValidator,
ValidatorMiddlewareContext.builder().entry(configEntry).build()
));
}
}
@@ -197,14 +213,14 @@ public class ValidationExtensionImpl implements ReadWriteRelatedExtension, Valid
@Data
private static class CustomEntryData {
@Setter(AccessLevel.NONE)
private @Nullable List<Middleware<ConfigEntryValidator>> validators;
private @Nullable List<Middleware<ConfigEntryValidator, ValidatorMiddlewareContext>> validators;
private @Nullable ConfigEntryValidator completeValidator;
public List<Middleware<ConfigEntryValidator>> validators() {
public List<Middleware<ConfigEntryValidator, ValidatorMiddlewareContext>> validators() {
return validators == null ? Collections.emptyList() : validators;
}
public void addValidator(Middleware<ConfigEntryValidator> validator) {
public void addValidator(Middleware<ConfigEntryValidator, ValidatorMiddlewareContext> validator) {
if (validators == null) {
validators = new ArrayList<>();
}
@@ -212,7 +228,8 @@ public class ValidationExtensionImpl implements ReadWriteRelatedExtension, Valid
}
}
private class EntryValidationReaderMiddleware implements Middleware<TweedEntryReader<?, ?>> {
private class EntryValidationReaderMiddleware
implements Middleware<TweedEntryReader<?, ?>, ReaderMiddlewareContext> {
@Override
public String id() {
return EXTENSION_ID;
@@ -224,43 +241,55 @@ public class ValidationExtensionImpl implements ReadWriteRelatedExtension, Valid
}
@Override
public TweedEntryReader<?, ?> process(TweedEntryReader<?, ?> inner) {
public TweedEntryReader<?, ?> process(TweedEntryReader<?, ?> inner, ReaderMiddlewareContext context) {
assert readContextValidationIssuesAccess != null && patherExtension != null;
//noinspection unchecked
TweedEntryReader<Object, ConfigEntry<Object>> castedInner = (TweedEntryReader<Object, ConfigEntry<Object>>) inner;
return (TweedDataReader reader, ConfigEntry<Object> entry, TweedReadContext context) -> {
ValidationIssues validationIssues = getOrCreateValidationIssues(context.extensionsData());
return (TweedDataReader reader, ConfigEntry<Object> entry, TweedReadContext readContext) -> {
ValidationIssues validationIssues = getOrCreateValidationIssues(readContext.extensionsData());
Object value = castedInner.read(reader, entry, context);
return castedInner.read(reader, entry, readContext).andThen(value -> {
ConfigEntryValidator entryValidator = entry.extensionsData()
.get(customEntryDataAccess)
.completeValidator();
assert entryValidator != null;
ConfigEntryValidator entryValidator = entry.extensionsData()
.get(customEntryDataAccess)
.completeValidator();
assert entryValidator != null;
ValidationResult<Object> validationResult = entryValidator.validate(entry, value);
ValidationResult<Object> validationResult = entryValidator.validate(entry, value);
if (validationResult.issues().isEmpty()) {
return TweedReadResult.ok(validationResult.value());
}
if (!validationResult.issues().isEmpty()) {
String path = patherExtension.getPath(context);
String path = patherExtension.getPath(readContext);
validationIssues.issuesByPath().put(path, new ValidationIssues.EntryIssues(
entry,
validationResult.issues()
));
}
if (validationResult.hasError()) {
throw new TweedEntryReadException(
"Failed to validate entry: " + validationResult.issues(),
context
);
}
return validationResult.value();
if (validationResult.hasError()) {
return TweedReadResult.failed(
mapValidationIssuesToReadIssues(validationResult.issues(), readContext)
);
} else {
return TweedReadResult.withIssues(
validationResult.value(),
mapValidationIssuesToReadIssues(validationResult.issues(), readContext)
);
}
}, readContext);
};
}
}
private static TweedReadIssue[] mapValidationIssuesToReadIssues(
Collection<ValidationIssue> issues,
TweedReadContext context
) {
return issues.stream().map(
validationIssue -> TweedReadIssue.error(validationIssue.message(), context)
).toArray(TweedReadIssue[]::new);
}
private ValidationIssues getOrCreateValidationIssues(Patchwork readContextExtensionsData) {
assert readContextValidationIssuesAccess != null;
ValidationIssues validationIssues = readContextExtensionsData.get(readContextValidationIssuesAccess);
@@ -290,12 +319,12 @@ public class ValidationExtensionImpl implements ReadWriteRelatedExtension, Valid
}
@Override
public <T> boolean enterStructuredEntry(ConfigEntry<T> entry, T value) {
public <T> boolean enterStructuredEntry(StructuredConfigEntry<T> entry, T value) {
return true;
}
@Override
public <T> void leaveStructuredEntry(ConfigEntry<T> entry, T value) {
public <T> void leaveStructuredEntry(StructuredConfigEntry<T> entry, T value) {
visitEntry(entry, value);
}
}

View File

@@ -7,6 +7,7 @@ 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.ValidatorMiddlewareContext;
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;
@@ -62,11 +63,11 @@ public class ValidationFallbackExtensionImpl implements ValidationFallbackExtens
}
@Override
public Middleware<ConfigEntryValidator> validationMiddleware() {
public Middleware<ConfigEntryValidator, ValidatorMiddlewareContext> validationMiddleware() {
return new ValidationFallbackMiddleware();
}
private class ValidationFallbackMiddleware implements Middleware<ConfigEntryValidator> {
private class ValidationFallbackMiddleware implements Middleware<ConfigEntryValidator, ValidatorMiddlewareContext> {
@Override
public String id() {
return EXTENSION_ID;
@@ -83,7 +84,7 @@ public class ValidationFallbackExtensionImpl implements ValidationFallbackExtens
}
@Override
public ConfigEntryValidator process(ConfigEntryValidator inner) {
public ConfigEntryValidator process(ConfigEntryValidator inner, ValidatorMiddlewareContext context) {
return new ConfigEntryValidator() {
@Override
public <T extends @Nullable Object> ValidationResult<T> validate(ConfigEntry<T> configEntry, T value) {

View File

@@ -9,9 +9,10 @@ import de.siphalor.tweed5.core.impl.DefaultConfigContainer;
import de.siphalor.tweed5.core.impl.entry.NullableConfigEntryImpl;
import de.siphalor.tweed5.core.impl.entry.SimpleConfigEntryImpl;
import de.siphalor.tweed5.core.impl.entry.StaticMapCompoundConfigEntryImpl;
import de.siphalor.tweed5.data.extension.api.ReadWriteExtension;
import de.siphalor.tweed5.data.hjson.HjsonCommentType;
import de.siphalor.tweed5.data.hjson.HjsonWriter;
import de.siphalor.tweed5.defaultextensions.comment.api.CommentProducerMiddlewareContext;
import de.siphalor.tweed5.serde.extension.api.ReadWriteExtension;
import de.siphalor.tweed5.serde.hjson.HjsonCommentType;
import de.siphalor.tweed5.serde.hjson.HjsonWriter;
import de.siphalor.tweed5.defaultextensions.comment.api.CommentExtension;
import de.siphalor.tweed5.defaultextensions.comment.api.CommentModifyingExtension;
import de.siphalor.tweed5.defaultextensions.comment.api.CommentProducer;
@@ -26,9 +27,9 @@ import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import static de.siphalor.tweed5.data.extension.api.ReadWriteExtension.entryReaderWriter;
import static de.siphalor.tweed5.data.extension.api.readwrite.TweedEntryReaderWriters.*;
import static de.siphalor.tweed5.serde.extension.api.ReadWriteExtension.entryReaderWriter;
import static de.siphalor.tweed5.defaultextensions.comment.api.CommentExtension.baseComment;
import static de.siphalor.tweed5.serde.extension.api.readwrite.TweedEntryReaderWriters.*;
import static de.siphalor.tweed5.testutils.generic.MapTestUtils.sequencedMap;
import static java.util.Map.entry;
import static org.junit.jupiter.api.Assertions.*;
@@ -144,7 +145,7 @@ class CommentExtensionImplTest {
}
@Override
public Middleware<CommentProducer> commentMiddleware() {
public Middleware<CommentProducer, CommentProducerMiddlewareContext> commentMiddleware() {
return new Middleware<>() {
@Override
public String id() {
@@ -152,7 +153,7 @@ class CommentExtensionImplTest {
}
@Override
public CommentProducer process(CommentProducer inner) {
public CommentProducer process(CommentProducer inner, CommentProducerMiddlewareContext context) {
return entry -> "The comment is:\n" + inner.createComment(entry) + "\nEND";
}
};

View File

@@ -8,9 +8,10 @@ import de.siphalor.tweed5.core.impl.entry.CollectionConfigEntryImpl;
import de.siphalor.tweed5.core.impl.entry.NullableConfigEntryImpl;
import de.siphalor.tweed5.core.impl.entry.SimpleConfigEntryImpl;
import de.siphalor.tweed5.core.impl.entry.StaticMapCompoundConfigEntryImpl;
import de.siphalor.tweed5.data.extension.api.ReadWriteExtension;
import de.siphalor.tweed5.data.hjson.HjsonLexer;
import de.siphalor.tweed5.data.hjson.HjsonReader;
import de.siphalor.tweed5.serde.extension.api.ReadWriteExtension;
import de.siphalor.tweed5.serde.extension.api.read.result.TweedReadResult;
import de.siphalor.tweed5.serde.hjson.HjsonLexer;
import de.siphalor.tweed5.serde.hjson.HjsonReader;
import de.siphalor.tweed5.defaultextensions.patch.api.PatchExtension;
import de.siphalor.tweed5.defaultextensions.patch.api.PatchInfo;
import lombok.SneakyThrows;
@@ -26,9 +27,9 @@ import java.util.*;
import java.util.concurrent.atomic.AtomicReference;
import java.util.stream.Stream;
import static de.siphalor.tweed5.data.extension.api.ReadWriteExtension.entryReaderWriter;
import static de.siphalor.tweed5.data.extension.api.ReadWriteExtension.read;
import static de.siphalor.tweed5.data.extension.api.readwrite.TweedEntryReaderWriters.*;
import static de.siphalor.tweed5.serde.extension.api.ReadWriteExtension.entryReaderWriter;
import static de.siphalor.tweed5.serde.extension.api.ReadWriteExtension.read;
import static de.siphalor.tweed5.serde.extension.api.readwrite.TweedEntryReaderWriters.*;
import static de.siphalor.tweed5.testutils.generic.MapTestUtils.sequencedMap;
import static java.util.Map.entry;
import static org.assertj.core.api.Assertions.assertThat;
@@ -150,7 +151,7 @@ class PatchExtensionImplTest {
PatchExtension patchExtension = configContainer.extension(PatchExtension.class).orElseThrow();
var patchInfo = new AtomicReference<@Nullable PatchInfo>();
Map<String, Object> patchValue = rootEntry.call(read(
TweedReadResult<Map<String, Object>> patchResult = rootEntry.call(read(
reader, extensionsData ->
patchInfo.set(patchExtension.collectPatchInfo(extensionsData))
));
@@ -158,7 +159,7 @@ class PatchExtensionImplTest {
Map<String, Object> resultValue = patchExtension.patch(
rootEntry,
baseValue,
patchValue,
patchResult.value(),
Objects.requireNonNull(patchInfo.get())
);

View File

@@ -1,8 +1,8 @@
package de.siphalor.tweed5.defaultextensions.pather.api;
import de.siphalor.tweed5.dataapi.api.TweedDataReader;
import de.siphalor.tweed5.dataapi.api.TweedDataToken;
import de.siphalor.tweed5.dataapi.api.TweedDataTokens;
import de.siphalor.tweed5.serde_api.api.TweedDataReader;
import de.siphalor.tweed5.serde_api.api.TweedDataToken;
import de.siphalor.tweed5.serde_api.api.TweedDataTokens;
import lombok.EqualsAndHashCode;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;

View File

@@ -1,27 +1,27 @@
package de.siphalor.tweed5.defaultextensions.readfallback.impl;
import ch.qos.logback.classic.Level;
import ch.qos.logback.classic.spi.ILoggingEvent;
import de.siphalor.tweed5.core.api.entry.CompoundConfigEntry;
import de.siphalor.tweed5.core.api.entry.ConfigEntry;
import de.siphalor.tweed5.core.impl.DefaultConfigContainer;
import de.siphalor.tweed5.core.impl.entry.SimpleConfigEntryImpl;
import de.siphalor.tweed5.core.impl.entry.StaticMapCompoundConfigEntryImpl;
import de.siphalor.tweed5.data.extension.api.ReadWriteExtension;
import de.siphalor.tweed5.data.extension.api.TweedEntryReadException;
import de.siphalor.tweed5.data.extension.api.TweedReadContext;
import de.siphalor.tweed5.data.extension.api.TweedWriteContext;
import de.siphalor.tweed5.data.extension.api.readwrite.TweedEntryReaderWriter;
import de.siphalor.tweed5.data.extension.api.readwrite.TweedEntryReaderWriters;
import de.siphalor.tweed5.data.hjson.HjsonLexer;
import de.siphalor.tweed5.data.hjson.HjsonReader;
import de.siphalor.tweed5.dataapi.api.TweedDataReadException;
import de.siphalor.tweed5.dataapi.api.TweedDataReader;
import de.siphalor.tweed5.dataapi.api.TweedDataVisitor;
import de.siphalor.tweed5.dataapi.api.TweedDataWriteException;
import de.siphalor.tweed5.defaultextensions.pather.api.PatherExtension;
import de.siphalor.tweed5.defaultextensions.presets.api.PresetsExtension;
import de.siphalor.tweed5.defaultextensions.readfallback.api.ReadFallbackExtension;
import de.siphalor.tweed5.serde.extension.api.ReadWriteExtension;
import de.siphalor.tweed5.serde.extension.api.TweedReadContext;
import de.siphalor.tweed5.serde.extension.api.TweedWriteContext;
import de.siphalor.tweed5.serde.extension.api.read.result.TweedReadIssue;
import de.siphalor.tweed5.serde.extension.api.read.result.TweedReadResult;
import de.siphalor.tweed5.serde.extension.api.readwrite.TweedEntryReaderWriter;
import de.siphalor.tweed5.serde.extension.api.readwrite.TweedEntryReaderWriters;
import de.siphalor.tweed5.serde.hjson.HjsonLexer;
import de.siphalor.tweed5.serde.hjson.HjsonReader;
import de.siphalor.tweed5.serde_api.api.TweedDataReadException;
import de.siphalor.tweed5.serde_api.api.TweedDataReader;
import de.siphalor.tweed5.serde_api.api.TweedDataVisitor;
import de.siphalor.tweed5.serde_api.api.TweedDataWriteException;
import de.siphalor.tweed5.testutils.generic.log.LogCaptureMockitoExtension;
import de.siphalor.tweed5.testutils.generic.log.LogsCaptor;
import org.jspecify.annotations.NullMarked;
@@ -34,12 +34,11 @@ import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import static de.siphalor.tweed5.data.extension.api.ReadWriteExtension.entryReaderWriter;
import static de.siphalor.tweed5.data.extension.api.ReadWriteExtension.read;
import static de.siphalor.tweed5.data.extension.api.readwrite.TweedEntryReaderWriters.compoundReaderWriter;
import static de.siphalor.tweed5.defaultextensions.presets.api.PresetsExtension.presetValue;
import static de.siphalor.tweed5.serde.extension.api.ReadWriteExtension.entryReaderWriter;
import static de.siphalor.tweed5.serde.extension.api.ReadWriteExtension.read;
import static de.siphalor.tweed5.serde.extension.api.readwrite.TweedEntryReaderWriters.compoundReaderWriter;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.InstanceOfAssertFactories.STRING;
@ExtendWith(LogCaptureMockitoExtension.class)
@NullMarked
@@ -59,12 +58,18 @@ class ReadFallbackExtensionImplTest {
configContainer.attachTree(entry);
configContainer.initialize();
assertThat(entry.call(read(new HjsonReader(new HjsonLexer(new StringReader("12")))))).isEqualTo(12);
assertThat(entry.call(read(new HjsonReader(new HjsonLexer(new StringReader("12"))))))
.isEqualTo(TweedReadResult.ok(12));
assertThat(logsCaptor.getLogsForLevel(Level.ERROR)).isEmpty();
logsCaptor.clear();
assertThat(entry.call(read(new HjsonReader(new HjsonLexer(new StringReader("13")))))).isEqualTo(-1);
assertThat(logsCaptor.getLogsForLevel(Level.ERROR)).hasSize(1);
assertThat(entry.call(read(new HjsonReader(new HjsonLexer(new StringReader("13")))))).satisfies(
r -> assertThat(r).extracting(TweedReadResult::value).isEqualTo(-1),
r -> assertThat(r.issues()).singleElement()
.extracting(TweedReadIssue::exception)
.extracting(Throwable::getMessage)
.isEqualTo("Value is not even")
);
}
@SuppressWarnings({"unchecked", "rawtypes"})
@@ -105,6 +110,7 @@ class ReadFallbackExtensionImplTest {
assertThat(root.call(read(new HjsonReader(new HjsonLexer(new StringReader(
"{first: {second: 12}}"
))))))
.extracting(TweedReadResult::value)
.extracting(map -> (Map<String, Object>) map.get("first"))
.extracting(map -> (Integer) map.get("second"))
.isEqualTo(12);
@@ -114,22 +120,20 @@ class ReadFallbackExtensionImplTest {
assertThat(root.call(read(new HjsonReader(new HjsonLexer(new StringReader(
"{first: {second: 13}}"
))))))
.extracting(TweedReadResult::value)
.extracting(map -> (Map<String, Object>) map.get("first"))
.extracting(map -> (Integer) map.get("second"))
.isEqualTo(-1);
assertThat(logsCaptor.getLogsForLevel(Level.ERROR)).singleElement()
.extracting(ILoggingEvent::getMessage)
.asInstanceOf(STRING)
.contains("first.second");
assertThat(logsCaptor.getLogsForLevel(Level.ERROR)).isEmpty();
}
private static class EvenIntReader implements TweedEntryReaderWriter<Integer, ConfigEntry<Integer>> {
@Override
public Integer read(
public TweedReadResult<Integer> read(
TweedDataReader reader,
ConfigEntry<Integer> entry,
TweedReadContext context
) throws TweedEntryReadException {
) {
int value;
try {
value = reader.readToken().readAsInt();
@@ -137,9 +141,9 @@ class ReadFallbackExtensionImplTest {
throw new IllegalStateException("Should not be called", e);
}
if (value % 2 == 0) {
return value;
return TweedReadResult.ok(value);
} else {
throw new TweedEntryReadException("Value is not even", context);
return TweedReadResult.error(TweedReadIssue.error("Value is not even", context));
}
}

View File

@@ -6,11 +6,12 @@ import de.siphalor.tweed5.core.api.entry.SimpleConfigEntry;
import de.siphalor.tweed5.core.impl.DefaultConfigContainer;
import de.siphalor.tweed5.core.impl.entry.SimpleConfigEntryImpl;
import de.siphalor.tweed5.core.impl.entry.StaticMapCompoundConfigEntryImpl;
import de.siphalor.tweed5.data.extension.api.ReadWriteExtension;
import de.siphalor.tweed5.data.hjson.HjsonCommentType;
import de.siphalor.tweed5.data.hjson.HjsonLexer;
import de.siphalor.tweed5.data.hjson.HjsonReader;
import de.siphalor.tweed5.data.hjson.HjsonWriter;
import de.siphalor.tweed5.serde.extension.api.ReadWriteExtension;
import de.siphalor.tweed5.serde.extension.api.read.result.TweedReadResult;
import de.siphalor.tweed5.serde.hjson.HjsonCommentType;
import de.siphalor.tweed5.serde.hjson.HjsonLexer;
import de.siphalor.tweed5.serde.hjson.HjsonReader;
import de.siphalor.tweed5.serde.hjson.HjsonWriter;
import de.siphalor.tweed5.defaultextensions.comment.api.CommentExtension;
import de.siphalor.tweed5.defaultextensions.validation.api.ValidationExtension;
import de.siphalor.tweed5.defaultextensions.validation.api.result.ValidationIssueLevel;
@@ -31,10 +32,10 @@ import java.util.Map;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Consumer;
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.comment.api.CommentExtension.baseComment;
import static de.siphalor.tweed5.defaultextensions.validation.api.ValidationExtension.validators;
import static de.siphalor.tweed5.serde.extension.api.ReadWriteExtension.*;
import static de.siphalor.tweed5.serde.extension.api.readwrite.TweedEntryReaderWriters.*;
import static de.siphalor.tweed5.testutils.generic.MapTestUtils.sequencedMap;
import static java.util.Map.entry;
import static org.assertj.core.api.Assertions.assertThat;
@@ -156,12 +157,14 @@ class ValidationExtensionImplTest {
}
""")));
var validationIssues = new AtomicReference<@Nullable ValidationIssues>();
Map<String, Object> value = configContainer.rootEntry().call(read(
TweedReadResult<Map<String, Object>> value = configContainer.rootEntry().call(read(
reader,
extensionsData -> validationIssues.set(validationExtension.captureValidationIssues(extensionsData))
));
assertThat(value).isEqualTo(Map.of("byte", (byte) 11, "int", 123, "double", 0.5));
assertThat(value)
.extracting(TweedReadResult::value)
.isEqualTo(Map.of("byte", (byte) 11, "int", 123, "double", 0.5));
//noinspection DataFlowIssue
assertThat(validationIssues.get()).isNotNull().satisfies(
vi -> assertValidationIssue(

View File

@@ -4,11 +4,12 @@ import de.siphalor.tweed5.core.api.entry.ConfigEntry;
import de.siphalor.tweed5.core.api.entry.SimpleConfigEntry;
import de.siphalor.tweed5.core.impl.DefaultConfigContainer;
import de.siphalor.tweed5.core.impl.entry.SimpleConfigEntryImpl;
import de.siphalor.tweed5.data.extension.api.ReadWriteExtension;
import de.siphalor.tweed5.data.hjson.HjsonCommentType;
import de.siphalor.tweed5.data.hjson.HjsonLexer;
import de.siphalor.tweed5.data.hjson.HjsonReader;
import de.siphalor.tweed5.data.hjson.HjsonWriter;
import de.siphalor.tweed5.serde.extension.api.ReadWriteExtension;
import de.siphalor.tweed5.serde.extension.api.read.result.TweedReadResult;
import de.siphalor.tweed5.serde.hjson.HjsonCommentType;
import de.siphalor.tweed5.serde.hjson.HjsonLexer;
import de.siphalor.tweed5.serde.hjson.HjsonReader;
import de.siphalor.tweed5.serde.hjson.HjsonWriter;
import de.siphalor.tweed5.defaultextensions.comment.api.CommentExtension;
import de.siphalor.tweed5.defaultextensions.validation.api.ConfigEntryValidator;
import de.siphalor.tweed5.defaultextensions.validation.api.ValidationExtension;
@@ -26,10 +27,11 @@ import java.io.StringReader;
import java.io.StringWriter;
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.presets.api.PresetsExtension.presetValue;
import static de.siphalor.tweed5.defaultextensions.validation.api.ValidationExtension.validators;
import static de.siphalor.tweed5.serde.extension.api.ReadWriteExtension.*;
import static de.siphalor.tweed5.serde.extension.api.readwrite.TweedEntryReaderWriters.*;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertEquals;
class ValidationFallbackExtensionImplTest {
@@ -114,8 +116,9 @@ class ValidationFallbackExtensionImplTest {
@ParameterizedTest
@ValueSource(strings = {"0", "7", "123", "null"})
void fallbackTriggers(String input) {
Integer result = intEntry.call(read(new HjsonReader(new HjsonLexer(new StringReader(input)))));
assertEquals(3, result);
TweedReadResult<Integer> result = intEntry.call(read(new HjsonReader(new HjsonLexer(new StringReader(input)))));
assertThat(result).extracting(TweedReadResult::value).isEqualTo(3);
assertThat(result.issues()).hasSize(1);
}
@Test

2
tweed5/gradle.properties Normal file
View File

@@ -0,0 +1,2 @@
org.gradle.jvmargs = -Xmx2G
org.gradle.configuration-cache = true

2
tweed5/lombok.config Normal file
View File

@@ -0,0 +1,2 @@
lombok.accessors.fluent = true
lombok.addLombokGeneratedAnnotation = true

Some files were not shown because too many files have changed in this diff Show More