From a16c29a04fb04157a94e93705bbc835250a42dc0 Mon Sep 17 00:00:00 2001 From: zvyap Date: Wed, 28 Jan 2026 22:06:05 +0800 Subject: [PATCH 01/18] Implemented adventure library support as additional module --- .../src/main/kotlin/libs-config.gradle.kts | 3 - configlib-adventure/build.gradle.kts | 22 +++ .../exlll/configlib/AdventureConfigLib.java | 28 ++++ .../de/exlll/configlib/ComponentFormat.java | 36 +++++ .../exlll/configlib/ComponentSerializer.java | 88 ++++++++++++ .../de/exlll/configlib/KeySerializer.java | 36 +++++ .../de/exlll/configlib/SoundSerializer.java | 110 +++++++++++++++ .../configlib/AdventureComponentTests.java | 125 ++++++++++++++++++ .../AdventureConfigurationTests.java | 91 +++++++++++++ .../configlib/AdventureTestConfiguration.java | 59 +++++++++ .../de/exlll/configlib/KeySerializerTest.java | 47 +++++++ .../exlll/configlib/SoundSerializerTest.java | 93 +++++++++++++ configlib-paper/build.gradle.kts | 1 + .../java/de/exlll/configlib/ConfigLib.java | 33 +++-- configlib-velocity/build.gradle.kts | 1 + .../java/de/exlll/configlib/ConfigLib.java | 17 +-- settings.gradle.kts | 1 + 17 files changed, 768 insertions(+), 23 deletions(-) create mode 100644 configlib-adventure/build.gradle.kts create mode 100644 configlib-adventure/src/main/java/de/exlll/configlib/AdventureConfigLib.java create mode 100644 configlib-adventure/src/main/java/de/exlll/configlib/ComponentFormat.java create mode 100644 configlib-adventure/src/main/java/de/exlll/configlib/ComponentSerializer.java create mode 100644 configlib-adventure/src/main/java/de/exlll/configlib/KeySerializer.java create mode 100644 configlib-adventure/src/main/java/de/exlll/configlib/SoundSerializer.java create mode 100644 configlib-adventure/src/test/java/de/exlll/configlib/AdventureComponentTests.java create mode 100644 configlib-adventure/src/test/java/de/exlll/configlib/AdventureConfigurationTests.java create mode 100644 configlib-adventure/src/test/java/de/exlll/configlib/AdventureTestConfiguration.java create mode 100644 configlib-adventure/src/test/java/de/exlll/configlib/KeySerializerTest.java create mode 100644 configlib-adventure/src/test/java/de/exlll/configlib/SoundSerializerTest.java diff --git a/buildSrc/src/main/kotlin/libs-config.gradle.kts b/buildSrc/src/main/kotlin/libs-config.gradle.kts index b77efb5..074f26b 100644 --- a/buildSrc/src/main/kotlin/libs-config.gradle.kts +++ b/buildSrc/src/main/kotlin/libs-config.gradle.kts @@ -7,6 +7,3 @@ dependencies { testImplementation(testFixtures(project(":configlib-core"))) } -tasks.compileJava { - dependsOn(project(":configlib-core").tasks.check) -} \ No newline at end of file diff --git a/configlib-adventure/build.gradle.kts b/configlib-adventure/build.gradle.kts new file mode 100644 index 0000000..6c21fef --- /dev/null +++ b/configlib-adventure/build.gradle.kts @@ -0,0 +1,22 @@ +plugins { + `core-config` + `libs-config` +} + +val adventureVersion = "4.26.1" + +dependencies { + api(project(":configlib-core")) + compileOnly("net.kyori:adventure-api:$adventureVersion") + compileOnly("net.kyori:adventure-text-minimessage:$adventureVersion") + compileOnly("net.kyori:adventure-text-serializer-legacy:$adventureVersion") + compileOnly("net.kyori:adventure-text-serializer-gson:$adventureVersion") + compileOnly("net.kyori:adventure-text-serializer-plain:${adventureVersion}") + + testImplementation(project(":configlib-yaml")) + testImplementation("net.kyori:adventure-api:$adventureVersion") + testImplementation("net.kyori:adventure-text-minimessage:$adventureVersion") + testImplementation("net.kyori:adventure-text-serializer-legacy:$adventureVersion") + testImplementation("net.kyori:adventure-text-serializer-gson:$adventureVersion") + testImplementation("net.kyori:adventure-text-serializer-plain:${adventureVersion}") +} diff --git a/configlib-adventure/src/main/java/de/exlll/configlib/AdventureConfigLib.java b/configlib-adventure/src/main/java/de/exlll/configlib/AdventureConfigLib.java new file mode 100644 index 0000000..a1984a2 --- /dev/null +++ b/configlib-adventure/src/main/java/de/exlll/configlib/AdventureConfigLib.java @@ -0,0 +1,28 @@ +package de.exlll.configlib; + +import net.kyori.adventure.key.Key; +import net.kyori.adventure.sound.Sound; +import net.kyori.adventure.text.Component; + +import java.util.List; + +public final class AdventureConfigLib { + private static final List DEFAULT_FORMAT_ORDER = List.of( + ComponentFormat.MINI_MESSAGE // Use MiniMessage as the default format since MiniMessage covered all component type + ); + + private AdventureConfigLib() { + } + + public static > ConfigurationProperties.Builder addDefaults(ConfigurationProperties.Builder builder) { + return addDefaults(builder, DEFAULT_FORMAT_ORDER, DEFAULT_FORMAT_ORDER); + } + + public static > ConfigurationProperties.Builder addDefaults(ConfigurationProperties.Builder builder, + List serializeOrder, List deserializeOrder) { + builder.addSerializer(Component.class, new ComponentSerializer(serializeOrder, deserializeOrder)); + builder.addSerializer(Key.class, new KeySerializer()); + builder.addSerializer(Sound.class, new SoundSerializer()); + return builder; + } +} diff --git a/configlib-adventure/src/main/java/de/exlll/configlib/ComponentFormat.java b/configlib-adventure/src/main/java/de/exlll/configlib/ComponentFormat.java new file mode 100644 index 0000000..e0926cf --- /dev/null +++ b/configlib-adventure/src/main/java/de/exlll/configlib/ComponentFormat.java @@ -0,0 +1,36 @@ +package de.exlll.configlib; + +import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer; + +import java.util.function.Predicate; +import java.util.regex.Pattern; + +public enum ComponentFormat { + MINI_MESSAGE(input -> Patterns.MINI_MESSAGE_PATTERN.matcher(input).find()), + TRANSLATION_KEY(input -> true), // translation keys can be any format + LEGACY_AMPERSAND(input -> input.indexOf(LegacyComponentSerializer.AMPERSAND_CHAR) != -1), + LEGACY_SECTION(input -> input.indexOf(LegacyComponentSerializer.SECTION_CHAR) != -1), + MINECRAFT_JSON(input -> { + input = input.trim(); + return input.startsWith("{") && input.endsWith("}"); + }); + + // Hack to avoid compiler error while singleton pattern initialization + private static class Patterns { + // Pattern to detect any in a string + static final Pattern MINI_MESSAGE_PATTERN = Pattern.compile("<[^>]+>"); + } + + private Predicate inputPredicate; + + ComponentFormat(Predicate inputPredicate) { + this.inputPredicate = inputPredicate; + } + + public boolean matches(String input) { + if(input == null) { + return false; + } + return inputPredicate.test(input); + } +} diff --git a/configlib-adventure/src/main/java/de/exlll/configlib/ComponentSerializer.java b/configlib-adventure/src/main/java/de/exlll/configlib/ComponentSerializer.java new file mode 100644 index 0000000..b3a356b --- /dev/null +++ b/configlib-adventure/src/main/java/de/exlll/configlib/ComponentSerializer.java @@ -0,0 +1,88 @@ +package de.exlll.configlib; + +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.minimessage.MiniMessage; +import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer; +import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer; +import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer; + +import java.util.Arrays; +import java.util.List; + +public final class ComponentSerializer implements Serializer { + private final List serializeOrder; + private final List deserializeOrder; + + public ComponentSerializer(List serializeOrder, List deserializeOrder) { + this.serializeOrder = List.copyOf(serializeOrder); + this.deserializeOrder = List.copyOf(deserializeOrder); + } + + public ComponentSerializer(ComponentFormat... formats) { + this(Arrays.asList(formats), Arrays.asList(formats)); + } + + @Override + public String serialize(Component element) { + if (element == null){ + return null; + } + + for (ComponentFormat format : serializeOrder) { + try { + return serialize(element, format); + } catch (Exception ignored) { + // This should never happen, but in case of adventure library issue + ignored.printStackTrace(); + } + } + + // Fallback to MiniMessage + return MiniMessage.miniMessage().serialize(element); + } + + @Override + public Component deserialize(String element) { + if (element == null){ + return null; + } + + for (ComponentFormat format : deserializeOrder) { + if (!format.matches(element)) { + continue; + } + + try { + return deserialize(element, format); + } catch (Exception ignored) { + // This should never happen, but in case of adventure library issue + ignored.printStackTrace(); + } + } + + // Fallback to MiniMessage + return MiniMessage.miniMessage().deserialize(element); + } + + private String serialize(Component component, ComponentFormat format) { + return switch (format) { + case MINI_MESSAGE -> MiniMessage.miniMessage().serialize(component); + case LEGACY_AMPERSAND -> LegacyComponentSerializer.legacyAmpersand().serialize(component); + case LEGACY_SECTION -> LegacyComponentSerializer.legacySection().serialize(component); + case MINECRAFT_JSON -> GsonComponentSerializer.gson().serialize(component); + case TRANSLATION_KEY -> PlainTextComponentSerializer.plainText().serialize(component); + default -> throw new UnsupportedOperationException("Unsupported format for serialization: " + format); + }; + } + + private Component deserialize(String string, ComponentFormat format) { + return switch (format) { + case MINI_MESSAGE -> MiniMessage.miniMessage().deserialize(string); + case LEGACY_AMPERSAND -> LegacyComponentSerializer.legacyAmpersand().deserialize(string); + case LEGACY_SECTION -> LegacyComponentSerializer.legacySection().deserialize(string); + case MINECRAFT_JSON -> GsonComponentSerializer.gson().deserialize(string); + case TRANSLATION_KEY -> Component.translatable(string); + default -> throw new UnsupportedOperationException("Unsupported format for deserialization: " + format); + }; + } +} diff --git a/configlib-adventure/src/main/java/de/exlll/configlib/KeySerializer.java b/configlib-adventure/src/main/java/de/exlll/configlib/KeySerializer.java new file mode 100644 index 0000000..db87cfb --- /dev/null +++ b/configlib-adventure/src/main/java/de/exlll/configlib/KeySerializer.java @@ -0,0 +1,36 @@ +package de.exlll.configlib; + +import net.kyori.adventure.key.Key; + +import java.util.OptionalInt; + +public final class KeySerializer implements Serializer { + + private final String defaultNamespace; + + public KeySerializer(String defaultNamespace) { + this.defaultNamespace = defaultNamespace; + OptionalInt result = Key.checkNamespace(defaultNamespace); + if(result.isPresent()) { + throw new IllegalArgumentException("Invalid namespace at index " + result.getAsInt() + ": " + defaultNamespace); + } + } + + public KeySerializer() { + this.defaultNamespace = null; // Use Adventure's default namespace + } + + @Override + public String serialize(Key element) { + return element.asString(); + } + + @Override + public Key deserialize(String element) { + if(this.defaultNamespace == null) { + return Key.key(element); + } + + return Key.key(this.defaultNamespace, element); + } +} diff --git a/configlib-adventure/src/main/java/de/exlll/configlib/SoundSerializer.java b/configlib-adventure/src/main/java/de/exlll/configlib/SoundSerializer.java new file mode 100644 index 0000000..00430e6 --- /dev/null +++ b/configlib-adventure/src/main/java/de/exlll/configlib/SoundSerializer.java @@ -0,0 +1,110 @@ +package de.exlll.configlib; + +import net.kyori.adventure.key.Key; +import net.kyori.adventure.sound.Sound; + +/** + * Serializer for {@link Sound} objects. + * String format: :[pitch]:[volume]:[source] + * Example: "minecraft:entity.player.levelup:1.0:1.0:MASTER" + * + */ +public final class SoundSerializer implements Serializer { + public static final String DELIMINATOR = " "; + + private final Sound.Source defaultSource; + + public SoundSerializer() { + this.defaultSource = Sound.Source.MASTER; + } + + public SoundSerializer(Sound.Source defaultSource) { + this.defaultSource = defaultSource; + } + + @Override + public String serialize(Sound element) { + StringBuilder builder = new StringBuilder(element.name().asString()); + if (element.source() != null) { + builder.append(DELIMINATOR).append(formatFloatSimple(element.pitch())); + builder.append(DELIMINATOR).append(formatFloatSimple(element.volume())); + builder.append(DELIMINATOR).append(element.source().name()); + } else if (element.volume() != 1f) { + builder.append(DELIMINATOR).append(formatFloatSimple(element.pitch())); + builder.append(DELIMINATOR).append(formatFloatSimple(element.volume())); + } else if (element.pitch() != 1f) { + builder.append(DELIMINATOR).append(formatFloatSimple(element.pitch())); + } + + return builder.toString(); + } + + @Override + public Sound deserialize(String element) { + String[] parts = element.split(DELIMINATOR); + float pitch = 1.0f; + float volume = 1.0f; + Sound.Source source = defaultSource; + + int endIndex = parts.length - 1; + + // Try to parse source + if (endIndex >= 0) { + try { + source = Sound.Source.valueOf(parts[endIndex]); + endIndex--; + } catch (IllegalArgumentException ignored) { + } + } + + // Try to parse volume and pitch + if (endIndex >= 0) { + Float lastFloat = tryParseFloat(parts[endIndex]); + if (lastFloat != null) { + boolean hasSecondFloat = false; + if (endIndex > 0) { + Float secondLastFloat = tryParseFloat(parts[endIndex - 1]); + if (secondLastFloat != null) { + volume = lastFloat; + pitch = secondLastFloat; + endIndex -= 2; + hasSecondFloat = true; + } + } + + if (!hasSecondFloat) { + pitch = lastFloat; + endIndex--; + } + } + } + + Key key = buildKey(parts, endIndex); + return Sound.sound(key, source, volume, pitch); + } + + private Float tryParseFloat(String s) { + try { + return Float.parseFloat(s); + } catch (NumberFormatException e) { + return null; + } + } + + private Key buildKey(String[] parts, int endIndex) { + StringBuilder keyString = new StringBuilder(); + for (int i = 0; i <= endIndex; i++) { + keyString.append(parts[i]); + if (i < endIndex) { + keyString.append(DELIMINATOR); + } + } + return Key.key(keyString.toString()); + } + + // If the float is a whole number, remove the decimal part for simplicity + private static String formatFloatSimple(float value) { + String s = String.valueOf(value); + return s.endsWith(".0") ? s.substring(0, s.length() - 2) : s; + } +} diff --git a/configlib-adventure/src/test/java/de/exlll/configlib/AdventureComponentTests.java b/configlib-adventure/src/test/java/de/exlll/configlib/AdventureComponentTests.java new file mode 100644 index 0000000..b929bd8 --- /dev/null +++ b/configlib-adventure/src/test/java/de/exlll/configlib/AdventureComponentTests.java @@ -0,0 +1,125 @@ +package de.exlll.configlib; + +import com.google.common.jimfs.Jimfs; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.format.NamedTextColor; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.io.IOException; +import java.nio.file.FileSystem; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import static de.exlll.configlib.TestUtils.createPlatformSpecificFilePath; +import static org.junit.jupiter.api.Assertions.assertEquals; + +class AdventureComponentTests { + private FileSystem fs; + private Path yamlFile; + + @BeforeEach + void setUp() throws IOException { + fs = Jimfs.newFileSystem(); + yamlFile = fs.getPath(createPlatformSpecificFilePath("/tmp/config.yml")); + Files.createDirectories(yamlFile.getParent()); + } + + @AfterEach + void tearDown() throws IOException { + if (fs != null) { + fs.close(); + } + } + + @Test + void testMultipleFormats() { + List baseFormats = new ArrayList<>(List.of( + ComponentFormat.MINI_MESSAGE, + ComponentFormat.MINECRAFT_JSON, + ComponentFormat.LEGACY_AMPERSAND, + ComponentFormat.LEGACY_SECTION)); + + List> allPermutations = generatePermutations(baseFormats); + + for (List deserializeOrder : allPermutations) { + runTestWithOrder(deserializeOrder); + } + } + + private void runTestWithOrder(List deserializeOrder) { + YamlConfigurationProperties.Builder builder = YamlConfigurationProperties.newBuilder(); + // Set a fixed serialize order, we are testing deserialization. + AdventureConfigLib.addDefaults(builder, List.of(ComponentFormat.MINI_MESSAGE), deserializeOrder); + YamlConfigurationProperties properties = builder.build(); + + YamlConfigurationStore store = new YamlConfigurationStore<>( + MixedConfiguration.class, properties); + + // Manually create the YAML content to ensure specific formats are present + // We use double quotes to ensure characters like & and § are preserved and + // parsed as strings + String yamlContent = """ + json: '{"text":"strict_json","color":"yellow"}' + miniMessage: 'strict_mini' + legacyAmpersand: '&bstrict_ampersand' + legacySection: '§dstrict_section' + """; + + try { + Files.writeString(yamlFile, yamlContent); + } catch (IOException e) { + throw new RuntimeException(e); + } + + MixedConfiguration loaded = store.load(yamlFile); + + assertEquals( + Component.text("strict_json", NamedTextColor.YELLOW), + loaded.json, + "JSON mismatch with order: " + deserializeOrder); + assertEquals( + Component.text("strict_mini", NamedTextColor.GREEN), + loaded.miniMessage, + "MiniMessage mismatch with order: " + deserializeOrder); + assertEquals( + Component.text("strict_ampersand", NamedTextColor.AQUA), + loaded.legacyAmpersand, + "Legacy Ampersand mismatch with order: " + deserializeOrder); + assertEquals( + Component.text("strict_section", NamedTextColor.LIGHT_PURPLE), + loaded.legacySection, + "Legacy Section mismatch with order: " + deserializeOrder); + } + + @Configuration + static class MixedConfiguration { + Component json; + Component miniMessage; + Component legacyAmpersand; + Component legacySection; + } + + private List> generatePermutations(List original) { + if (original.isEmpty()) { + List> result = new ArrayList<>(); + result.add(new ArrayList<>()); + return result; + } + E firstElement = original.get(0); + List> returnValue = new ArrayList<>(); + List> permutations = generatePermutations(original.subList(1, original.size())); + for (List smallerPermutated : permutations) { + for (int index = 0; index <= smallerPermutated.size(); index++) { + List temp = new ArrayList<>(smallerPermutated); + temp.add(index, firstElement); + returnValue.add(temp); + } + } + return returnValue; + } +} diff --git a/configlib-adventure/src/test/java/de/exlll/configlib/AdventureConfigurationTests.java b/configlib-adventure/src/test/java/de/exlll/configlib/AdventureConfigurationTests.java new file mode 100644 index 0000000..67ae09a --- /dev/null +++ b/configlib-adventure/src/test/java/de/exlll/configlib/AdventureConfigurationTests.java @@ -0,0 +1,91 @@ +package de.exlll.configlib; + +import com.google.common.jimfs.Jimfs; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.format.NamedTextColor; +import net.kyori.adventure.text.minimessage.MiniMessage; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.io.IOException; +import java.nio.file.FileSystem; +import java.nio.file.Files; +import java.nio.file.Path; + +import static de.exlll.configlib.TestUtils.createPlatformSpecificFilePath; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +class AdventureConfigurationTests { + private FileSystem fs; + private Path yamlFile; + + @BeforeEach + void setUp() throws IOException { + fs = Jimfs.newFileSystem(); + yamlFile = fs.getPath(createPlatformSpecificFilePath("/tmp/config.yml")); + Files.createDirectories(yamlFile.getParent()); + } + + @AfterEach + void tearDown() throws IOException { + if (fs != null) { + fs.close(); + } + } + + // Test to make sure data is consistent after serialization and deserialization + @Test + void testSerializationAndDeserializationDefaults() { + YamlConfigurationProperties.Builder builder = YamlConfigurationProperties.newBuilder(); + AdventureConfigLib.addDefaults(builder); + YamlConfigurationProperties properties = builder.build(); + + YamlConfigurationStore store = new YamlConfigurationStore<>( + AdventureTestConfiguration.class, properties); + + AdventureTestConfiguration original = new AdventureTestConfiguration(); + store.save(original, yamlFile); + + AdventureTestConfiguration loaded = store.load(yamlFile); + + assertConfigsEqual(original, loaded); + } + + private void assertConfigsEqual(AdventureTestConfiguration expected, AdventureTestConfiguration actual) { + assertNotNull(actual); + assertEquals(expected.simpleText, actual.simpleText, "Simple Text mismatch"); + assertEquals(expected.coloredText, actual.coloredText, "Colored Text mismatch"); + assertEquals(expected.decoratedText, actual.decoratedText, "Decorated Text mismatch"); + assertEquals(expected.clickLink, actual.clickLink, "Click Link mismatch"); + assertEquals(expected.hoverText, actual.hoverText, "Hover Text mismatch"); + + assertEquals(serialize(expected.gradientText), serialize(actual.gradientText), "Gradient Text mismatch"); + assertEquals(serialize(expected.rainbowText), serialize(actual.rainbowText), "Rainbow Text mismatch"); + assertEquals(serialize(expected.formattedText), serialize(actual.formattedText), "Formatted Text mismatch"); + assertEquals(serialize(expected.clickText), serialize(actual.clickText), "Click Text mismatch"); + assertEquals(serialize(expected.hoverTextComplex), serialize(actual.hoverTextComplex), + "Hover Text Complex mismatch"); + assertEquals(serialize(expected.keybindText), serialize(actual.keybindText), "Keybind Text (MM) mismatch"); + assertEquals(serialize(expected.translatableText), serialize(actual.translatableText), + "Translatable Text (MM) mismatch"); + + assertEquals(expected.translatable, actual.translatable, "Translatable mismatch"); + assertEquals(expected.keybind, actual.keybind, "Keybind mismatch"); + + assertEquals(expected.simpleKey, actual.simpleKey, "Key mismatch"); + + assertEquals(expected.simpleSound.name(), actual.simpleSound.name(), "Sound Name mismatch"); + assertEquals(expected.simpleSound.source(), actual.simpleSound.source(), "Sound Source mismatch"); + assertEquals(expected.simpleSound.volume(), actual.simpleSound.volume(), "Sound Volume mismatch"); + assertEquals(expected.simpleSound.pitch(), actual.simpleSound.pitch(), "Sound Pitch mismatch"); + + assertEquals(expected.componentList, actual.componentList, "List mismatch"); + assertEquals(expected.componentMap, actual.componentMap, "Map mismatch"); + } + + private String serialize(Component component) { + return MiniMessage.miniMessage().serialize(component); + } +} diff --git a/configlib-adventure/src/test/java/de/exlll/configlib/AdventureTestConfiguration.java b/configlib-adventure/src/test/java/de/exlll/configlib/AdventureTestConfiguration.java new file mode 100644 index 0000000..ae1401c --- /dev/null +++ b/configlib-adventure/src/test/java/de/exlll/configlib/AdventureTestConfiguration.java @@ -0,0 +1,59 @@ +package de.exlll.configlib; + +import net.kyori.adventure.key.Key; +import net.kyori.adventure.sound.Sound; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.event.ClickEvent; +import net.kyori.adventure.text.event.HoverEvent; +import net.kyori.adventure.text.format.NamedTextColor; +import net.kyori.adventure.text.format.TextDecoration; +import net.kyori.adventure.text.minimessage.MiniMessage; + +import java.util.List; +import java.util.Map; +import java.util.UUID; + +@Configuration +public class AdventureTestConfiguration { + Component simpleText = Component.text("Hello World"); + + Component coloredText = Component.text("Red Text", NamedTextColor.RED); + + Component decoratedText = Component.text("Bold Italic", NamedTextColor.BLUE, TextDecoration.BOLD, + TextDecoration.ITALIC); + + Component clickLink = Component.text("Click Me") + .clickEvent(ClickEvent.openUrl("https://example.com")); + + Component hoverText = Component.text("Hover Me") + .hoverEvent(HoverEvent.showText(Component.text("Hover Content", NamedTextColor.GREEN))); + + Component gradientText = MiniMessage.miniMessage() + .deserialize("Gradient Text"); + Component rainbowText = MiniMessage.miniMessage().deserialize("Rainbow"); + Component formattedText = MiniMessage.miniMessage().deserialize( + "Bold Italic Underlined Strike Obfuscated"); + Component clickText = MiniMessage.miniMessage().deserialize("Click Command"); + // Note: Hover events with complex content inside might be sensitive to + // serialization round trips + Component hoverTextComplex = MiniMessage.miniMessage() + .deserialize("Red Hover'>Hover Text"); + Component keybindText = MiniMessage.miniMessage().deserialize(""); + Component translatableText = MiniMessage.miniMessage().deserialize(""); + + Key simpleKey = Key.key("namespace", "value"); + + Sound simpleSound = Sound.sound(Key.key("minecraft:entity.player.levelup"), Sound.Source.MASTER, 1f, 1f); + + List componentList = List.of( + Component.text("Item 1"), + Component.text("Item 2", NamedTextColor.GOLD)); + + Map componentMap = Map.of( + "welcome", Component.text("Welcome!"), + "goodbye", Component.text("Goodbye!", NamedTextColor.RED)); + + // Testing specific component types if possible via MiniMessage or construction + Component translatable = Component.translatable("item.minecraft.diamond_sword"); + Component keybind = Component.keybind("key.jump"); +} diff --git a/configlib-adventure/src/test/java/de/exlll/configlib/KeySerializerTest.java b/configlib-adventure/src/test/java/de/exlll/configlib/KeySerializerTest.java new file mode 100644 index 0000000..002e227 --- /dev/null +++ b/configlib-adventure/src/test/java/de/exlll/configlib/KeySerializerTest.java @@ -0,0 +1,47 @@ +package de.exlll.configlib; + +import net.kyori.adventure.key.Key; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +class KeySerializerTest { + + @Test + void testSerialize() { + KeySerializer serializer = new KeySerializer(); + Key key = Key.key("namespace", "value"); + assertEquals("namespace:value", serializer.serialize(key)); + } + + @Test + void testDeserializeWithColon() { + KeySerializer serializer = new KeySerializer(); + Key key = serializer.deserialize("namespace:value"); + assertEquals(Key.key("namespace", "value"), key); + } + + @Test + void testDeserializeWithoutColonAndNoDefaultNamespace() { + KeySerializer serializer = new KeySerializer(); + // Adventure default namespace is "minecraft" + Key key = serializer.deserialize("value"); + assertEquals(Key.key("minecraft", "value"), key); + } + + @Test + void testDeserializeWithoutColonAndDefaultNamespace() { + KeySerializer serializer = new KeySerializer("default"); + Key key = serializer.deserialize("value"); + assertEquals(Key.key("default", "value"), key); + } + + @Test + void testDeserializeWithColonAndDefaultNamespace() { + KeySerializer serializer = new KeySerializer("default"); + // Key.key("default", "namespace:value") will throw InvalidKeyException because + // ':' is not allowed in value + assertThrows(Exception.class, () -> serializer.deserialize("namespace:value")); + } +} diff --git a/configlib-adventure/src/test/java/de/exlll/configlib/SoundSerializerTest.java b/configlib-adventure/src/test/java/de/exlll/configlib/SoundSerializerTest.java new file mode 100644 index 0000000..d35a958 --- /dev/null +++ b/configlib-adventure/src/test/java/de/exlll/configlib/SoundSerializerTest.java @@ -0,0 +1,93 @@ +package de.exlll.configlib; + +import net.kyori.adventure.key.Key; +import net.kyori.adventure.sound.Sound; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +class SoundSerializerTest { + private final SoundSerializer serializer = new SoundSerializer(); + + @Test + void testDeserializeBasic() { + String input = "minecraft:entity.player.levelup"; + Sound sound = serializer.deserialize(input); + + assertEquals(Key.key("minecraft", "entity.player.levelup"), sound.name()); + assertEquals(1.0f, sound.pitch()); + assertEquals(1.0f, sound.volume()); + assertEquals(Sound.Source.MASTER, sound.source()); + } + + @Test + void testDeserializeWithPitch() { + String input = "minecraft:test" + SoundSerializer.DELIMINATOR + "1.5"; + Sound sound = serializer.deserialize(input); + + assertEquals(Key.key("minecraft", "test"), sound.name()); + assertEquals(1.5f, sound.pitch()); + assertEquals(1.0f, sound.volume()); + assertEquals(Sound.Source.MASTER, sound.source()); + } + + @Test + void testDeserializeWithPitchAndVolume() { + String input = "minecraft:test" + SoundSerializer.DELIMINATOR + "0.5" + SoundSerializer.DELIMINATOR + "2.0"; + Sound sound = serializer.deserialize(input); + + assertEquals(Key.key("minecraft", "test"), sound.name()); + assertEquals(0.5f, sound.pitch()); + assertEquals(2.0f, sound.volume()); + assertEquals(Sound.Source.MASTER, sound.source()); + } + + @Test + void testDeserializeFull() { + String input = "minecraft:test" + SoundSerializer.DELIMINATOR + "0.8" + SoundSerializer.DELIMINATOR + "0.5" + + SoundSerializer.DELIMINATOR + "MUSIC"; + Sound sound = serializer.deserialize(input); + + assertEquals(Key.key("minecraft", "test"), sound.name()); + assertEquals(0.8f, sound.pitch()); + assertEquals(0.5f, sound.volume()); + assertEquals(Sound.Source.MUSIC, sound.source()); + } + + @Test + void testDeserializeComplexKey() { + String input = "minecraft:complex.key.name" + SoundSerializer.DELIMINATOR + "1.2"; + Sound sound = serializer.deserialize(input); + + assertEquals(Key.key("minecraft", "complex.key.name"), sound.name()); + assertEquals(1.2f, sound.pitch()); + } + + @Test + void testDeserializeAmbiguousKeyLookingLikeFloat() { + String input = "custom" + SoundSerializer.DELIMINATOR + "1.5"; + Sound sound = serializer.deserialize(input); + assertEquals(Key.key("minecraft", "custom"), sound.name()); + assertEquals(1.5f, sound.pitch()); + } + + @Test + void testDeserializeAmbiguousKeyLookingLikeSource() { + String input = "custom" + SoundSerializer.DELIMINATOR + "MUSIC"; + Sound sound = serializer.deserialize(input); + assertEquals(Key.key("minecraft", "custom"), sound.name()); + assertEquals(Sound.Source.MUSIC, sound.source()); + } + + @Test + void testRoundTrip() { + Sound original = Sound.sound(Key.key("test:sound"), Sound.Source.AMBIENT, 0.5f, 1.2f); + String serialized = serializer.serialize(original); + Sound deserialized = serializer.deserialize(serialized); + + assertEquals(original.name(), deserialized.name()); + assertEquals(original.source(), deserialized.source()); + assertEquals(original.volume(), deserialized.volume()); + assertEquals(original.pitch(), deserialized.pitch()); + } +} diff --git a/configlib-paper/build.gradle.kts b/configlib-paper/build.gradle.kts index b7e75b7..d1ebdee 100644 --- a/configlib-paper/build.gradle.kts +++ b/configlib-paper/build.gradle.kts @@ -5,4 +5,5 @@ plugins { dependencies { compileOnly("io.papermc.paper:paper-api:1.20.2-R0.1-SNAPSHOT") + api(project(":configlib-adventure")) } diff --git a/configlib-paper/src/main/java/de/exlll/configlib/ConfigLib.java b/configlib-paper/src/main/java/de/exlll/configlib/ConfigLib.java index 41d110e..8526b2a 100644 --- a/configlib-paper/src/main/java/de/exlll/configlib/ConfigLib.java +++ b/configlib-paper/src/main/java/de/exlll/configlib/ConfigLib.java @@ -9,23 +9,36 @@ */ public final class ConfigLib extends JavaPlugin { /** - * A {@code YamlConfigurationProperties} object that provides serializers for several Bukkit - * classes like {@link ItemStack} and other {@link ConfigurationSerializable} types. + * A {@code YamlConfigurationProperties} object that provides serializers for + * several Bukkit + * classes like {@link ItemStack} and other {@link ConfigurationSerializable} + * types. *

- * You can configure these properties further by creating a new builder using the + * You can configure these properties further by creating a new builder using + * the * {@code toBuilder()} method of this object. */ - public static final YamlConfigurationProperties BUKKIT_DEFAULT_PROPERTIES = - initializeBukkitDefaultProperties(); + public static final YamlConfigurationProperties BUKKIT_DEFAULT_PROPERTIES = initializeBukkitDefaultProperties(); + public static final YamlConfigurationProperties PAPER_DEFAULT_PROPERTIES = initializePaperDefaultProperties(); private static YamlConfigurationProperties initializeBukkitDefaultProperties() { - return YamlConfigurationProperties - .newBuilder() + return builder().build(); + } + + private static YamlConfigurationProperties initializePaperDefaultProperties() { + return AdventureConfigLib.addDefaults(builder()) + .getThis() + .build(); + } + + public static YamlConfigurationProperties.Builder builder() { + var builder = YamlConfigurationProperties.newBuilder(); + return builder .addSerializerByCondition( type -> type instanceof Class cls && ConfigurationSerializable.class.isAssignableFrom(cls), - BukkitConfigurationSerializableSerializer.DEFAULT - ) - .build(); + BukkitConfigurationSerializableSerializer.DEFAULT); } + + } diff --git a/configlib-velocity/build.gradle.kts b/configlib-velocity/build.gradle.kts index 1a19522..646af96 100644 --- a/configlib-velocity/build.gradle.kts +++ b/configlib-velocity/build.gradle.kts @@ -6,4 +6,5 @@ plugins { dependencies { compileOnly("com.velocitypowered:velocity-api:3.2.0-SNAPSHOT") annotationProcessor("com.velocitypowered:velocity-api:3.2.0-SNAPSHOT") + api(project(":configlib-adventure")) } diff --git a/configlib-velocity/src/main/java/de/exlll/configlib/ConfigLib.java b/configlib-velocity/src/main/java/de/exlll/configlib/ConfigLib.java index b15a1c0..a533aeb 100644 --- a/configlib-velocity/src/main/java/de/exlll/configlib/ConfigLib.java +++ b/configlib-velocity/src/main/java/de/exlll/configlib/ConfigLib.java @@ -3,14 +3,11 @@ import com.velocitypowered.api.plugin.Plugin; /** - * An empty plugin class that loads this library and its dependencies. + * A velocity plugin that loads this library and support for Adventure types. */ -@Plugin( - id = "configlib", - name = "ConfigLib", - version = "4.8.0", - url = "https://github.com/Exlll/ConfigLib", - description = "A library for working with YAML configurations.", - authors = {"Exlll"} -) -public final class ConfigLib {} +@Plugin(id = "configlib", name = "ConfigLib", version = "4.8.0", url = "https://github.com/Exlll/ConfigLib", description = "A library for working with YAML configurations.", authors = { + "Exlll" }) +public final class ConfigLib { + public static final YamlConfigurationProperties VELOCITY_DEFAULT_PROPERTIES = AdventureConfigLib + .addDefaults(YamlConfigurationProperties.newBuilder()).getThis().build(); +} diff --git a/settings.gradle.kts b/settings.gradle.kts index bfb48b0..b77f1d2 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -4,3 +4,4 @@ include("configlib-yaml") include("configlib-paper") include("configlib-waterfall") include("configlib-velocity") +include("configlib-adventure") From 5943851706ea83abfe7e944c71b773433762289c Mon Sep 17 00:00:00 2001 From: zvyap Date: Wed, 28 Jan 2026 22:13:55 +0800 Subject: [PATCH 02/18] Optimize imports --- .../test/java/de/exlll/configlib/AdventureComponentTests.java | 1 - .../java/de/exlll/configlib/AdventureConfigurationTests.java | 1 - .../test/java/de/exlll/configlib/AdventureTestConfiguration.java | 1 - 3 files changed, 3 deletions(-) diff --git a/configlib-adventure/src/test/java/de/exlll/configlib/AdventureComponentTests.java b/configlib-adventure/src/test/java/de/exlll/configlib/AdventureComponentTests.java index b929bd8..fcc4be4 100644 --- a/configlib-adventure/src/test/java/de/exlll/configlib/AdventureComponentTests.java +++ b/configlib-adventure/src/test/java/de/exlll/configlib/AdventureComponentTests.java @@ -12,7 +12,6 @@ import java.nio.file.Files; import java.nio.file.Path; import java.util.ArrayList; -import java.util.Collections; import java.util.List; import static de.exlll.configlib.TestUtils.createPlatformSpecificFilePath; diff --git a/configlib-adventure/src/test/java/de/exlll/configlib/AdventureConfigurationTests.java b/configlib-adventure/src/test/java/de/exlll/configlib/AdventureConfigurationTests.java index 67ae09a..9a5aeca 100644 --- a/configlib-adventure/src/test/java/de/exlll/configlib/AdventureConfigurationTests.java +++ b/configlib-adventure/src/test/java/de/exlll/configlib/AdventureConfigurationTests.java @@ -2,7 +2,6 @@ import com.google.common.jimfs.Jimfs; import net.kyori.adventure.text.Component; -import net.kyori.adventure.text.format.NamedTextColor; import net.kyori.adventure.text.minimessage.MiniMessage; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; diff --git a/configlib-adventure/src/test/java/de/exlll/configlib/AdventureTestConfiguration.java b/configlib-adventure/src/test/java/de/exlll/configlib/AdventureTestConfiguration.java index ae1401c..49ca95c 100644 --- a/configlib-adventure/src/test/java/de/exlll/configlib/AdventureTestConfiguration.java +++ b/configlib-adventure/src/test/java/de/exlll/configlib/AdventureTestConfiguration.java @@ -11,7 +11,6 @@ import java.util.List; import java.util.Map; -import java.util.UUID; @Configuration public class AdventureTestConfiguration { From 857be59136c781524f792a9366ef2bb58116dbfe Mon Sep 17 00:00:00 2001 From: zvyap Date: Wed, 28 Jan 2026 22:15:42 +0800 Subject: [PATCH 03/18] Make paper & velocity module depend on adventure on compile --- configlib-paper/build.gradle.kts | 4 ++++ configlib-velocity/build.gradle.kts | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/configlib-paper/build.gradle.kts b/configlib-paper/build.gradle.kts index d1ebdee..77e3121 100644 --- a/configlib-paper/build.gradle.kts +++ b/configlib-paper/build.gradle.kts @@ -7,3 +7,7 @@ dependencies { compileOnly("io.papermc.paper:paper-api:1.20.2-R0.1-SNAPSHOT") api(project(":configlib-adventure")) } + +tasks.compileJava { + dependsOn(project(":configlib-adventure").tasks.check) +} diff --git a/configlib-velocity/build.gradle.kts b/configlib-velocity/build.gradle.kts index 646af96..0f4e6bc 100644 --- a/configlib-velocity/build.gradle.kts +++ b/configlib-velocity/build.gradle.kts @@ -8,3 +8,7 @@ dependencies { annotationProcessor("com.velocitypowered:velocity-api:3.2.0-SNAPSHOT") api(project(":configlib-adventure")) } + +tasks.compileJava { + dependsOn(project(":configlib-adventure").tasks.check) +} From dbb48072df0f292d04c92507cb6d88492148af06 Mon Sep 17 00:00:00 2001 From: zvyap Date: Wed, 28 Jan 2026 22:21:23 +0800 Subject: [PATCH 04/18] Fix comment formatted by IDE --- .../src/main/java/de/exlll/configlib/ConfigLib.java | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/configlib-paper/src/main/java/de/exlll/configlib/ConfigLib.java b/configlib-paper/src/main/java/de/exlll/configlib/ConfigLib.java index 8526b2a..f289834 100644 --- a/configlib-paper/src/main/java/de/exlll/configlib/ConfigLib.java +++ b/configlib-paper/src/main/java/de/exlll/configlib/ConfigLib.java @@ -9,13 +9,10 @@ */ public final class ConfigLib extends JavaPlugin { /** - * A {@code YamlConfigurationProperties} object that provides serializers for - * several Bukkit - * classes like {@link ItemStack} and other {@link ConfigurationSerializable} - * types. + * A {@code YamlConfigurationProperties} object that provides serializers for several Bukkit + * classes like {@link ItemStack} and other {@link ConfigurationSerializable} types. *

- * You can configure these properties further by creating a new builder using - * the + * You can configure these properties further by creating a new builder using the * {@code toBuilder()} method of this object. */ public static final YamlConfigurationProperties BUKKIT_DEFAULT_PROPERTIES = initializeBukkitDefaultProperties(); From 0b47e1ec822cd91863fb8c59cf4b12dadcf89b5d Mon Sep 17 00:00:00 2001 From: zvyap Date: Wed, 28 Jan 2026 22:38:33 +0800 Subject: [PATCH 05/18] Fix tests failed due to windows os --- .../src/test/java/de/exlll/configlib/SerializersTest.java | 6 ++++-- .../src/testFixtures/java/de/exlll/configlib/TestUtils.java | 5 +++++ 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/configlib-core/src/test/java/de/exlll/configlib/SerializersTest.java b/configlib-core/src/test/java/de/exlll/configlib/SerializersTest.java index ac5e284..d786b71 100644 --- a/configlib-core/src/test/java/de/exlll/configlib/SerializersTest.java +++ b/configlib-core/src/test/java/de/exlll/configlib/SerializersTest.java @@ -1216,8 +1216,10 @@ void deserializeThrowsForObjectsIfObjectToStringDeserializationTypeNotAdded() { assertThrowsConfigurationException( () -> serializer.deserialize(new File(TMP_CONFIG_PATH)), - buildExceptionMessage("File", "/tmp/config.yml", "OBJECT_TO_STRING") + buildExceptionMessage("File", TestUtils.isWindows() ? "C:\\tmp\\config.yml" : "/tmp/config.yml", + "OBJECT_TO_STRING") ); + assertThrowsConfigurationException( () -> serializer.deserialize(URI.create("https://example.com")), buildExceptionMessage("URI", "https://example.com", "OBJECT_TO_STRING") @@ -1275,7 +1277,7 @@ void deserializeObjectToStringIfDeserializationTypeAdded() { assertThat( serializer.deserialize(new File(TMP_CONFIG_PATH)), - is("/tmp/config.yml") + is(TestUtils.isWindows() ? "C:\\tmp\\config.yml" : "/tmp/config.yml") ); assertThat( serializer.deserialize(URI.create("https://example.com")), diff --git a/configlib-core/src/testFixtures/java/de/exlll/configlib/TestUtils.java b/configlib-core/src/testFixtures/java/de/exlll/configlib/TestUtils.java index edb1f6d..e4dc82d 100644 --- a/configlib-core/src/testFixtures/java/de/exlll/configlib/TestUtils.java +++ b/configlib-core/src/testFixtures/java/de/exlll/configlib/TestUtils.java @@ -357,6 +357,11 @@ public static String createPlatformSpecificFilePath(String path) { return String.format("C:%s", path.replace("/", File.separator)); } + public static boolean isWindows() { + final String platform = System.getProperty("os.name"); + return platform.contains("Windows"); + } + public static List createListOfPlatformSpecificFilePaths(String... paths) { return Stream.of(paths).map(TestUtils::createPlatformSpecificFilePath).toList(); } From 9683ceb48f3c26feeefec58e2056bbd7d75619f0 Mon Sep 17 00:00:00 2001 From: zvyap Date: Wed, 28 Jan 2026 22:48:45 +0800 Subject: [PATCH 06/18] Added javadocs --- .../exlll/configlib/AdventureConfigLib.java | 33 ++++++++++++++++--- .../de/exlll/configlib/ComponentFormat.java | 16 ++++++++- .../exlll/configlib/ComponentSerializer.java | 17 +++++++++- .../de/exlll/configlib/KeySerializer.java | 20 +++++++++-- .../de/exlll/configlib/SoundSerializer.java | 17 ++++++++-- .../java/de/exlll/configlib/ConfigLib.java | 10 +++++- .../java/de/exlll/configlib/ConfigLib.java | 6 ++++ 7 files changed, 106 insertions(+), 13 deletions(-) diff --git a/configlib-adventure/src/main/java/de/exlll/configlib/AdventureConfigLib.java b/configlib-adventure/src/main/java/de/exlll/configlib/AdventureConfigLib.java index a1984a2..d253efd 100644 --- a/configlib-adventure/src/main/java/de/exlll/configlib/AdventureConfigLib.java +++ b/configlib-adventure/src/main/java/de/exlll/configlib/AdventureConfigLib.java @@ -6,20 +6,45 @@ import java.util.List; +/** + * Utility class providing default serializers for Adventure library types. + */ public final class AdventureConfigLib { private static final List DEFAULT_FORMAT_ORDER = List.of( - ComponentFormat.MINI_MESSAGE // Use MiniMessage as the default format since MiniMessage covered all component type + ComponentFormat.MINI_MESSAGE // Use MiniMessage as the default format since MiniMessage covered all + // component type ); private AdventureConfigLib() { } - public static > ConfigurationProperties.Builder addDefaults(ConfigurationProperties.Builder builder) { + /** + * Adds default Adventure serializers to the configuration builder. + * + * @param builder the configuration properties builder + * @param the builder type + * @return the builder with default serializers added + */ + public static > ConfigurationProperties.Builder addDefaults( + ConfigurationProperties.Builder builder) { return addDefaults(builder, DEFAULT_FORMAT_ORDER, DEFAULT_FORMAT_ORDER); } - public static > ConfigurationProperties.Builder addDefaults(ConfigurationProperties.Builder builder, - List serializeOrder, List deserializeOrder) { + /** + * Adds default Adventure serializers to the configuration builder with custom + * format orders. + * + * @param builder the configuration properties builder + * @param serializeOrder the order of formats to try when serializing + * components + * @param deserializeOrder the order of formats to try when deserializing + * components + * @param the builder type + * @return the builder with default serializers added + */ + public static > ConfigurationProperties.Builder addDefaults( + ConfigurationProperties.Builder builder, + List serializeOrder, List deserializeOrder) { builder.addSerializer(Component.class, new ComponentSerializer(serializeOrder, deserializeOrder)); builder.addSerializer(Key.class, new KeySerializer()); builder.addSerializer(Sound.class, new SoundSerializer()); diff --git a/configlib-adventure/src/main/java/de/exlll/configlib/ComponentFormat.java b/configlib-adventure/src/main/java/de/exlll/configlib/ComponentFormat.java index e0926cf..6077988 100644 --- a/configlib-adventure/src/main/java/de/exlll/configlib/ComponentFormat.java +++ b/configlib-adventure/src/main/java/de/exlll/configlib/ComponentFormat.java @@ -5,11 +5,19 @@ import java.util.function.Predicate; import java.util.regex.Pattern; +/** + * Represents the different text formats supported for Adventure Component serialization. + */ public enum ComponentFormat { + /** MiniMessage format with tags like {@code } or {@code }. */ MINI_MESSAGE(input -> Patterns.MINI_MESSAGE_PATTERN.matcher(input).find()), + /** Translation key format for translatable components. */ TRANSLATION_KEY(input -> true), // translation keys can be any format + /** Legacy format using ampersand ({@code &}) as the color code prefix. */ LEGACY_AMPERSAND(input -> input.indexOf(LegacyComponentSerializer.AMPERSAND_CHAR) != -1), + /** Legacy format using section symbol ({@code §}) as the color code prefix. */ LEGACY_SECTION(input -> input.indexOf(LegacyComponentSerializer.SECTION_CHAR) != -1), + /** Minecraft JSON format for components. */ MINECRAFT_JSON(input -> { input = input.trim(); return input.startsWith("{") && input.endsWith("}"); @@ -20,13 +28,19 @@ private static class Patterns { // Pattern to detect any in a string static final Pattern MINI_MESSAGE_PATTERN = Pattern.compile("<[^>]+>"); } - + private Predicate inputPredicate; ComponentFormat(Predicate inputPredicate) { this.inputPredicate = inputPredicate; } + /** + * Checks if the given input string matches this format. + * + * @param input the input string to check + * @return true if the input matches this format, false otherwise + */ public boolean matches(String input) { if(input == null) { return false; diff --git a/configlib-adventure/src/main/java/de/exlll/configlib/ComponentSerializer.java b/configlib-adventure/src/main/java/de/exlll/configlib/ComponentSerializer.java index b3a356b..34ce2d0 100644 --- a/configlib-adventure/src/main/java/de/exlll/configlib/ComponentSerializer.java +++ b/configlib-adventure/src/main/java/de/exlll/configlib/ComponentSerializer.java @@ -9,15 +9,30 @@ import java.util.Arrays; import java.util.List; +/** + * Serializer for Adventure {@link Component} objects. + * Supports multiple formats including MiniMessage, legacy, and JSON. + */ public final class ComponentSerializer implements Serializer { private final List serializeOrder; private final List deserializeOrder; + /** + * Creates a new ComponentSerializer with separate format orders for serialization and deserialization. + * + * @param serializeOrder the order of formats to try when serializing + * @param deserializeOrder the order of formats to try when deserializing + */ public ComponentSerializer(List serializeOrder, List deserializeOrder) { - this.serializeOrder = List.copyOf(serializeOrder); + this.serializeOrder = List.copyOf(serializeOrder); this.deserializeOrder = List.copyOf(deserializeOrder); } + /** + * Creates a new ComponentSerializer using the same format order for both serialization and deserialization. + * + * @param formats the formats to use, in order of preference + */ public ComponentSerializer(ComponentFormat... formats) { this(Arrays.asList(formats), Arrays.asList(formats)); } diff --git a/configlib-adventure/src/main/java/de/exlll/configlib/KeySerializer.java b/configlib-adventure/src/main/java/de/exlll/configlib/KeySerializer.java index db87cfb..c0077c9 100644 --- a/configlib-adventure/src/main/java/de/exlll/configlib/KeySerializer.java +++ b/configlib-adventure/src/main/java/de/exlll/configlib/KeySerializer.java @@ -4,18 +4,32 @@ import java.util.OptionalInt; +/** + * Serializer for Adventure {@link Key} objects. + */ public final class KeySerializer implements Serializer { private final String defaultNamespace; + /** + * Creates a new KeySerializer with the specified default namespace. + * + * @param defaultNamespace the default namespace to use when deserializing keys + * without a namespace + * @throws IllegalArgumentException if the namespace is invalid + */ public KeySerializer(String defaultNamespace) { this.defaultNamespace = defaultNamespace; OptionalInt result = Key.checkNamespace(defaultNamespace); - if(result.isPresent()) { - throw new IllegalArgumentException("Invalid namespace at index " + result.getAsInt() + ": " + defaultNamespace); + if (result.isPresent()) { + throw new IllegalArgumentException( + "Invalid namespace at index " + result.getAsInt() + ": " + defaultNamespace); } } + /** + * Creates a new KeySerializer using Adventure's default namespace (minecraft). + */ public KeySerializer() { this.defaultNamespace = null; // Use Adventure's default namespace } @@ -27,7 +41,7 @@ public String serialize(Key element) { @Override public Key deserialize(String element) { - if(this.defaultNamespace == null) { + if (this.defaultNamespace == null) { return Key.key(element); } diff --git a/configlib-adventure/src/main/java/de/exlll/configlib/SoundSerializer.java b/configlib-adventure/src/main/java/de/exlll/configlib/SoundSerializer.java index 00430e6..37f39f7 100644 --- a/configlib-adventure/src/main/java/de/exlll/configlib/SoundSerializer.java +++ b/configlib-adventure/src/main/java/de/exlll/configlib/SoundSerializer.java @@ -5,19 +5,30 @@ /** * Serializer for {@link Sound} objects. - * String format: :[pitch]:[volume]:[source] - * Example: "minecraft:entity.player.levelup:1.0:1.0:MASTER" - * + *

+ * String format: {@code [pitch] [volume] [source]} + *

+ * Example: {@code "minecraft:entity.player.levelup 1.0 1.0 MASTER"} */ public final class SoundSerializer implements Serializer { + /** The delimiter used to separate sound components in the serialized string. */ public static final String DELIMINATOR = " "; private final Sound.Source defaultSource; + /** + * Creates a new SoundSerializer with the default source set to + * {@link Sound.Source#MASTER}. + */ public SoundSerializer() { this.defaultSource = Sound.Source.MASTER; } + /** + * Creates a new SoundSerializer with the specified default source. + * + * @param defaultSource the default sound source to use when deserializing + */ public SoundSerializer(Sound.Source defaultSource) { this.defaultSource = defaultSource; } diff --git a/configlib-paper/src/main/java/de/exlll/configlib/ConfigLib.java b/configlib-paper/src/main/java/de/exlll/configlib/ConfigLib.java index f289834..b00cc37 100644 --- a/configlib-paper/src/main/java/de/exlll/configlib/ConfigLib.java +++ b/configlib-paper/src/main/java/de/exlll/configlib/ConfigLib.java @@ -16,6 +16,14 @@ public final class ConfigLib extends JavaPlugin { * {@code toBuilder()} method of this object. */ public static final YamlConfigurationProperties BUKKIT_DEFAULT_PROPERTIES = initializeBukkitDefaultProperties(); + + /** + * A {@code YamlConfigurationProperties} object design for Paper software, it provides native support for Adventure library, + * serializers classes like {@link ItemStack} and other {@link ConfigurationSerializable} types. + *

+ * You can configure these properties further by creating a new builder using the + * {@code toBuilder()} method of this object. + */ public static final YamlConfigurationProperties PAPER_DEFAULT_PROPERTIES = initializePaperDefaultProperties(); private static YamlConfigurationProperties initializeBukkitDefaultProperties() { @@ -28,7 +36,7 @@ private static YamlConfigurationProperties initializePaperDefaultProperties() { .build(); } - public static YamlConfigurationProperties.Builder builder() { + private static YamlConfigurationProperties.Builder builder() { var builder = YamlConfigurationProperties.newBuilder(); return builder .addSerializerByCondition( diff --git a/configlib-velocity/src/main/java/de/exlll/configlib/ConfigLib.java b/configlib-velocity/src/main/java/de/exlll/configlib/ConfigLib.java index a533aeb..2ffe4e6 100644 --- a/configlib-velocity/src/main/java/de/exlll/configlib/ConfigLib.java +++ b/configlib-velocity/src/main/java/de/exlll/configlib/ConfigLib.java @@ -8,6 +8,12 @@ @Plugin(id = "configlib", name = "ConfigLib", version = "4.8.0", url = "https://github.com/Exlll/ConfigLib", description = "A library for working with YAML configurations.", authors = { "Exlll" }) public final class ConfigLib { + /** + * A {@code YamlConfigurationProperties} object design for Velocity software, it provides native support for Adventure library. + *

+ * You can configure these properties further by creating a new builder using the + * {@code toBuilder()} method of this object. + */ public static final YamlConfigurationProperties VELOCITY_DEFAULT_PROPERTIES = AdventureConfigLib .addDefaults(YamlConfigurationProperties.newBuilder()).getThis().build(); } From 4081dcff45286018f340326ccaaac1cfb77fd369 Mon Sep 17 00:00:00 2001 From: zvyap Date: Thu, 29 Jan 2026 09:50:45 +0800 Subject: [PATCH 07/18] Updated readme --- README.md | 66 ++++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 65 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 7a1de26..b6a0383 100644 --- a/README.md +++ b/README.md @@ -392,9 +392,69 @@ YamlConfigurationProperties properties = ConfigLib.BUKKIT_DEFAULT_PROPERTIES.toB .build(); ``` -To get access to this object, you have to import `configlib-paper` instead +If you are using **Paper** software, you can use `PAPER_DEFAULT_PROPERTIES` which includes both Bukkit `ConfigurationSerializable` support (e.g. `ItemStack`) and Adventure support pre-configured: + +```java +YamlConfigurationProperties properties = ConfigLib.PAPER_DEFAULT_PROPERTIES.toBuilder() + // ...further configure the builder... + .build(); +``` + +To get access to these objects, you have to import `configlib-paper` instead of `configlib-yaml` as described in the [Import](#import) section. +#### Support for Adventure library types + +The `configlib-adventure` module provides serializers for Adventure library types +commonly used in PaperMC and Velocity plugins. It supports: + +* `Component` - Text components with customizable format (MiniMessage, legacy, JSON) +* `Key` - Namespaced keys (e.g., `minecraft:stone`) +* `Sound` - Sound effects with pitch, volume, and source + +To use Adventure types in your configuration, use the helper method that registers all Adventure serializers: + +```java +YamlConfigurationProperties.Builder builder = YamlConfigurationProperties.newBuilder(); +AdventureConfigLib.addDefaults(builder); +YamlConfigurationProperties properties = builder.build(); +``` + +##### Component Formats + +The `ComponentSerializer` supports multiple text formats through the `ComponentFormat` enum: + +| Format | Description | Example | +|---------------------|----------------------------------------------------|------------------------------------| +| `MINI_MESSAGE` | MiniMessage tags | `Hello World` | +| `LEGACY_AMPERSAND` | Legacy colors with `&` | `&cHello &lWorld` | +| `LEGACY_SECTION` | Legacy colors with `§` | `§cHello §lWorld` | +| `MINECRAFT_JSON` | Minecraft JSON format | `{"text":"Hello","color":"red"}` | +| `TRANSLATION_KEY` | Translation keys | `block.minecraft.stone` | + +You can customize the serialization and deserialization format order: + +```java +List serializeOrder = List.of(ComponentFormat.MINI_MESSAGE); +List deserializeOrder = List.of( + ComponentFormat.MINI_MESSAGE, + ComponentFormat.LEGACY_AMPERSAND +); +AdventureConfigLib.addDefaults(builder, serializeOrder, deserializeOrder); +``` + +##### Sound Serialization + +Sounds are serialized in a compact string format: ` [pitch] volume source`. +Sound id can be anything even it is not in vanilla Minecraft to support custom sound from texture packs. +```yaml +# Full format +joinSound: "minecraft:entity.experience_orb.pickup 1.0 1.0 MASTER" + +# Minimal format (defaults: pitch=1.0, volume=1.0, source=MASTER) +leaveSound: "minecraft:entity.experience_orb.pickup" +``` + ### Comments The configuration elements of a configuration type can be annotated with @@ -1141,6 +1201,10 @@ This project contains three classes of modules: * The `configlib-yaml` module contains the classes that can save configuration instances as YAML files and instantiate new instances from such files. This module does not contain anything Minecraft related, either. +* The `configlib-adventure` module provides serializers for Adventure library types + like `Component`, `Key`, and `Sound`. This module is useful for PaperMC and + Velocity plugins that use the Adventure text API. See the + [Adventure support section](#support-for-adventure-library-types) for details. * The `configlib-paper`, `configlib-velocity`, and `configlib-waterfall` modules contain basic plugins that are used to conveniently load this library. These three modules shade the `-core` module, the `-yaml` module, and the YAML From 50a1adf042833db52fc31011885d0dd7d7ab558e Mon Sep 17 00:00:00 2001 From: zvyap Date: Tue, 3 Feb 2026 10:31:22 +0800 Subject: [PATCH 08/18] Fix PR requested changes Fix: https://github.com/Exlll/ConfigLib/pull/60#discussion_r2752091853 Fix: https://github.com/Exlll/ConfigLib/pull/60#discussion_r2752088690 Fix: https://github.com/Exlll/ConfigLib/pull/60#discussion_r2752079636 Fix: https://github.com/Exlll/ConfigLib/pull/60#discussion_r2752050124 Fix: https://github.com/Exlll/ConfigLib/pull/60#discussion_r2752050167 Fix: https://github.com/Exlll/ConfigLib/pull/60#discussion_r2752082673 Fix: https://github.com/Exlll/ConfigLib/pull/60#discussion_r2752049742 Fix: https://github.com/Exlll/ConfigLib/pull/60#discussion_r2752049819 Fix: https://github.com/Exlll/ConfigLib/pull/60#discussion_r2752043589 Fix: https://github.com/Exlll/ConfigLib/pull/60#discussion_r2752118319 --- .../de/exlll/configlib/ComponentFormat.java | 9 +- .../exlll/configlib/ComponentSerializer.java | 22 ++--- .../de/exlll/configlib/SoundSerializer.java | 84 +++++++------------ .../de/exlll/configlib/KeySerializerTest.java | 3 +- .../de/exlll/configlib/SerializersTest.java | 4 +- .../java/de/exlll/configlib/TestUtils.java | 4 +- .../java/de/exlll/configlib/ConfigLib.java | 10 ++- 7 files changed, 52 insertions(+), 84 deletions(-) diff --git a/configlib-adventure/src/main/java/de/exlll/configlib/ComponentFormat.java b/configlib-adventure/src/main/java/de/exlll/configlib/ComponentFormat.java index 6077988..7fab4a6 100644 --- a/configlib-adventure/src/main/java/de/exlll/configlib/ComponentFormat.java +++ b/configlib-adventure/src/main/java/de/exlll/configlib/ComponentFormat.java @@ -6,11 +6,12 @@ import java.util.regex.Pattern; /** - * Represents the different text formats supported for Adventure Component serialization. + * Represents the different text formats supported for Adventure Component + * serialization. */ public enum ComponentFormat { /** MiniMessage format with tags like {@code } or {@code }. */ - MINI_MESSAGE(input -> Patterns.MINI_MESSAGE_PATTERN.matcher(input).find()), + MINI_MESSAGE(Patterns.MINI_MESSAGE_PATTERN.asPredicate()), /** Translation key format for translatable components. */ TRANSLATION_KEY(input -> true), // translation keys can be any format /** Legacy format using ampersand ({@code &}) as the color code prefix. */ @@ -28,7 +29,7 @@ private static class Patterns { // Pattern to detect any in a string static final Pattern MINI_MESSAGE_PATTERN = Pattern.compile("<[^>]+>"); } - + private Predicate inputPredicate; ComponentFormat(Predicate inputPredicate) { @@ -42,7 +43,7 @@ private static class Patterns { * @return true if the input matches this format, false otherwise */ public boolean matches(String input) { - if(input == null) { + if (input == null) { return false; } return inputPredicate.test(input); diff --git a/configlib-adventure/src/main/java/de/exlll/configlib/ComponentSerializer.java b/configlib-adventure/src/main/java/de/exlll/configlib/ComponentSerializer.java index 34ce2d0..b8aae85 100644 --- a/configlib-adventure/src/main/java/de/exlll/configlib/ComponentSerializer.java +++ b/configlib-adventure/src/main/java/de/exlll/configlib/ComponentSerializer.java @@ -24,7 +24,7 @@ public final class ComponentSerializer implements Serializer * @param deserializeOrder the order of formats to try when deserializing */ public ComponentSerializer(List serializeOrder, List deserializeOrder) { - this.serializeOrder = List.copyOf(serializeOrder); + this.serializeOrder = List.copyOf(serializeOrder); this.deserializeOrder = List.copyOf(deserializeOrder); } @@ -39,17 +39,12 @@ public ComponentSerializer(ComponentFormat... formats) { @Override public String serialize(Component element) { - if (element == null){ + if (element == null) { return null; } for (ComponentFormat format : serializeOrder) { - try { - return serialize(element, format); - } catch (Exception ignored) { - // This should never happen, but in case of adventure library issue - ignored.printStackTrace(); - } + return serialize(element, format); } // Fallback to MiniMessage @@ -58,7 +53,7 @@ public String serialize(Component element) { @Override public Component deserialize(String element) { - if (element == null){ + if (element == null) { return null; } @@ -67,12 +62,7 @@ public Component deserialize(String element) { continue; } - try { - return deserialize(element, format); - } catch (Exception ignored) { - // This should never happen, but in case of adventure library issue - ignored.printStackTrace(); - } + return deserialize(element, format); } // Fallback to MiniMessage @@ -86,7 +76,6 @@ private String serialize(Component component, ComponentFormat format) { case LEGACY_SECTION -> LegacyComponentSerializer.legacySection().serialize(component); case MINECRAFT_JSON -> GsonComponentSerializer.gson().serialize(component); case TRANSLATION_KEY -> PlainTextComponentSerializer.plainText().serialize(component); - default -> throw new UnsupportedOperationException("Unsupported format for serialization: " + format); }; } @@ -97,7 +86,6 @@ private Component deserialize(String string, ComponentFormat format) { case LEGACY_SECTION -> LegacyComponentSerializer.legacySection().deserialize(string); case MINECRAFT_JSON -> GsonComponentSerializer.gson().deserialize(string); case TRANSLATION_KEY -> Component.translatable(string); - default -> throw new UnsupportedOperationException("Unsupported format for deserialization: " + format); }; } } diff --git a/configlib-adventure/src/main/java/de/exlll/configlib/SoundSerializer.java b/configlib-adventure/src/main/java/de/exlll/configlib/SoundSerializer.java index 37f39f7..6e3e1bf 100644 --- a/configlib-adventure/src/main/java/de/exlll/configlib/SoundSerializer.java +++ b/configlib-adventure/src/main/java/de/exlll/configlib/SoundSerializer.java @@ -3,6 +3,10 @@ import net.kyori.adventure.key.Key; import net.kyori.adventure.sound.Sound; +import java.util.Objects; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + /** * Serializer for {@link Sound} objects. *

@@ -30,13 +34,14 @@ public SoundSerializer() { * @param defaultSource the default sound source to use when deserializing */ public SoundSerializer(Sound.Source defaultSource) { + Objects.requireNonNull(defaultSource, "defaultSource must not be null"); this.defaultSource = defaultSource; } @Override public String serialize(Sound element) { StringBuilder builder = new StringBuilder(element.name().asString()); - if (element.source() != null) { + if (element.source() != defaultSource) { builder.append(DELIMINATOR).append(formatFloatSimple(element.pitch())); builder.append(DELIMINATOR).append(formatFloatSimple(element.volume())); builder.append(DELIMINATOR).append(element.source().name()); @@ -50,67 +55,36 @@ public String serialize(Sound element) { return builder.toString(); } + private static final Pattern SOUND_PATTERN = Pattern.compile( + "^(?[a-zA-Z0-9:._-]+)" + + "(?:\\s+(?\\d+(?:\\.\\d+)?))?" + + "(?:\\s+(?\\d+(?:\\.\\d+)?))?" + + "(?:\\s+(?MASTER|MUSIC|RECORD|WEATHER|BLOCK|HOSTILE|NEUTRAL|PLAYER|AMBIENT|VOICE))?" + + "\\s*$"); + @Override public Sound deserialize(String element) { - String[] parts = element.split(DELIMINATOR); - float pitch = 1.0f; - float volume = 1.0f; - Sound.Source source = defaultSource; - - int endIndex = parts.length - 1; - - // Try to parse source - if (endIndex >= 0) { - try { - source = Sound.Source.valueOf(parts[endIndex]); - endIndex--; - } catch (IllegalArgumentException ignored) { - } + Matcher matcher = SOUND_PATTERN.matcher(element); + if (!matcher.matches()) { + throw new IllegalArgumentException("Invalid sound format: " + element); } - // Try to parse volume and pitch - if (endIndex >= 0) { - Float lastFloat = tryParseFloat(parts[endIndex]); - if (lastFloat != null) { - boolean hasSecondFloat = false; - if (endIndex > 0) { - Float secondLastFloat = tryParseFloat(parts[endIndex - 1]); - if (secondLastFloat != null) { - volume = lastFloat; - pitch = secondLastFloat; - endIndex -= 2; - hasSecondFloat = true; - } - } - - if (!hasSecondFloat) { - pitch = lastFloat; - endIndex--; - } - } - } + String keyString = matcher.group("key"); + Key key = Key.key(keyString); - Key key = buildKey(parts, endIndex); - return Sound.sound(key, source, volume, pitch); - } + float pitch = matcher.group("pitch") != null + ? Float.parseFloat(matcher.group("pitch")) + : 1.0f; - private Float tryParseFloat(String s) { - try { - return Float.parseFloat(s); - } catch (NumberFormatException e) { - return null; - } - } + float volume = matcher.group("volume") != null + ? Float.parseFloat(matcher.group("volume")) + : 1.0f; - private Key buildKey(String[] parts, int endIndex) { - StringBuilder keyString = new StringBuilder(); - for (int i = 0; i <= endIndex; i++) { - keyString.append(parts[i]); - if (i < endIndex) { - keyString.append(DELIMINATOR); - } - } - return Key.key(keyString.toString()); + Sound.Source source = matcher.group("source") != null + ? Sound.Source.valueOf(matcher.group("source")) + : defaultSource; + + return Sound.sound(key, source, volume, pitch); } // If the float is a whole number, remove the decimal part for simplicity diff --git a/configlib-adventure/src/test/java/de/exlll/configlib/KeySerializerTest.java b/configlib-adventure/src/test/java/de/exlll/configlib/KeySerializerTest.java index 002e227..567bcea 100644 --- a/configlib-adventure/src/test/java/de/exlll/configlib/KeySerializerTest.java +++ b/configlib-adventure/src/test/java/de/exlll/configlib/KeySerializerTest.java @@ -1,5 +1,6 @@ package de.exlll.configlib; +import net.kyori.adventure.key.InvalidKeyException; import net.kyori.adventure.key.Key; import org.junit.jupiter.api.Test; @@ -42,6 +43,6 @@ void testDeserializeWithColonAndDefaultNamespace() { KeySerializer serializer = new KeySerializer("default"); // Key.key("default", "namespace:value") will throw InvalidKeyException because // ':' is not allowed in value - assertThrows(Exception.class, () -> serializer.deserialize("namespace:value")); + assertThrows(InvalidKeyException.class, () -> serializer.deserialize("namespace:value")); } } diff --git a/configlib-core/src/test/java/de/exlll/configlib/SerializersTest.java b/configlib-core/src/test/java/de/exlll/configlib/SerializersTest.java index d786b71..b225c87 100644 --- a/configlib-core/src/test/java/de/exlll/configlib/SerializersTest.java +++ b/configlib-core/src/test/java/de/exlll/configlib/SerializersTest.java @@ -1216,7 +1216,7 @@ void deserializeThrowsForObjectsIfObjectToStringDeserializationTypeNotAdded() { assertThrowsConfigurationException( () -> serializer.deserialize(new File(TMP_CONFIG_PATH)), - buildExceptionMessage("File", TestUtils.isWindows() ? "C:\\tmp\\config.yml" : "/tmp/config.yml", + buildExceptionMessage("File", TestUtils.createPlatformSpecificFilePath("/tmp/config.yml"), "OBJECT_TO_STRING") ); @@ -1277,7 +1277,7 @@ void deserializeObjectToStringIfDeserializationTypeAdded() { assertThat( serializer.deserialize(new File(TMP_CONFIG_PATH)), - is(TestUtils.isWindows() ? "C:\\tmp\\config.yml" : "/tmp/config.yml") + is(TestUtils.createPlatformSpecificFilePath("/tmp/config.yml")) ); assertThat( serializer.deserialize(URI.create("https://example.com")), diff --git a/configlib-core/src/testFixtures/java/de/exlll/configlib/TestUtils.java b/configlib-core/src/testFixtures/java/de/exlll/configlib/TestUtils.java index e4dc82d..d54a829 100644 --- a/configlib-core/src/testFixtures/java/de/exlll/configlib/TestUtils.java +++ b/configlib-core/src/testFixtures/java/de/exlll/configlib/TestUtils.java @@ -350,9 +350,7 @@ on different platforms like Windows. Currently, Jimfs(1.3.0) lacks support path declarations to fulfill the non-unix system's needs. */ public static String createPlatformSpecificFilePath(String path) { - final String platform = System.getProperty("os.name"); - - if (!platform.contains("Windows")) return path; + if (!isWindows()) return path; return String.format("C:%s", path.replace("/", File.separator)); } diff --git a/configlib-velocity/src/main/java/de/exlll/configlib/ConfigLib.java b/configlib-velocity/src/main/java/de/exlll/configlib/ConfigLib.java index 2ffe4e6..07adae8 100644 --- a/configlib-velocity/src/main/java/de/exlll/configlib/ConfigLib.java +++ b/configlib-velocity/src/main/java/de/exlll/configlib/ConfigLib.java @@ -5,8 +5,14 @@ /** * A velocity plugin that loads this library and support for Adventure types. */ -@Plugin(id = "configlib", name = "ConfigLib", version = "4.8.0", url = "https://github.com/Exlll/ConfigLib", description = "A library for working with YAML configurations.", authors = { - "Exlll" }) +@Plugin( + id = "configlib", + name = "ConfigLib", + version = "4.8.0", + url = "https://github.com/Exlll/ConfigLib", + description = "A library for working with YAML configurations.", + authors = {"Exlll"} +) public final class ConfigLib { /** * A {@code YamlConfigurationProperties} object design for Velocity software, it provides native support for Adventure library. From e6dd5bdaca61569ad0d5e008a07b3468b612fc93 Mon Sep 17 00:00:00 2001 From: zvyap Date: Tue, 3 Feb 2026 12:30:44 +0800 Subject: [PATCH 09/18] Change MiniMessage detection pattern to more strict --- .../src/main/java/de/exlll/configlib/ComponentFormat.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/configlib-adventure/src/main/java/de/exlll/configlib/ComponentFormat.java b/configlib-adventure/src/main/java/de/exlll/configlib/ComponentFormat.java index 7fab4a6..d61c741 100644 --- a/configlib-adventure/src/main/java/de/exlll/configlib/ComponentFormat.java +++ b/configlib-adventure/src/main/java/de/exlll/configlib/ComponentFormat.java @@ -27,10 +27,10 @@ public enum ComponentFormat { // Hack to avoid compiler error while singleton pattern initialization private static class Patterns { // Pattern to detect any in a string - static final Pattern MINI_MESSAGE_PATTERN = Pattern.compile("<[^>]+>"); + static final Pattern MINI_MESSAGE_PATTERN = Pattern.compile("<[a-zA-Z0-9_:-]+(?::[^<>]+)?>"); } - private Predicate inputPredicate; + private final Predicate inputPredicate; ComponentFormat(Predicate inputPredicate) { this.inputPredicate = inputPredicate; From 1f81d39111e19cba8615a0ef21a751d6484969be Mon Sep 17 00:00:00 2001 From: zvyap Date: Tue, 3 Feb 2026 14:07:38 +0800 Subject: [PATCH 10/18] Make sound source generate dynamically --- .../de/exlll/configlib/SoundSerializer.java | 25 +++++++++++++------ 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/configlib-adventure/src/main/java/de/exlll/configlib/SoundSerializer.java b/configlib-adventure/src/main/java/de/exlll/configlib/SoundSerializer.java index 6e3e1bf..0a997d6 100644 --- a/configlib-adventure/src/main/java/de/exlll/configlib/SoundSerializer.java +++ b/configlib-adventure/src/main/java/de/exlll/configlib/SoundSerializer.java @@ -2,10 +2,13 @@ import net.kyori.adventure.key.Key; import net.kyori.adventure.sound.Sound; +import net.kyori.adventure.text.minimessage.MiniMessage; +import java.util.Arrays; import java.util.Objects; import java.util.regex.Matcher; import java.util.regex.Pattern; +import java.util.stream.Collectors; /** * Serializer for {@link Sound} objects. @@ -18,6 +21,8 @@ public final class SoundSerializer implements Serializer { /** The delimiter used to separate sound components in the serialized string. */ public static final String DELIMINATOR = " "; + private static final Pattern SOUND_PATTERN = Pattern.compile(buildRegex()); + private final Sound.Source defaultSource; /** @@ -55,13 +60,6 @@ public String serialize(Sound element) { return builder.toString(); } - private static final Pattern SOUND_PATTERN = Pattern.compile( - "^(?[a-zA-Z0-9:._-]+)" + - "(?:\\s+(?\\d+(?:\\.\\d+)?))?" + - "(?:\\s+(?\\d+(?:\\.\\d+)?))?" + - "(?:\\s+(?MASTER|MUSIC|RECORD|WEATHER|BLOCK|HOSTILE|NEUTRAL|PLAYER|AMBIENT|VOICE))?" + - "\\s*$"); - @Override public Sound deserialize(String element) { Matcher matcher = SOUND_PATTERN.matcher(element); @@ -92,4 +90,17 @@ private static String formatFloatSimple(float value) { String s = String.valueOf(value); return s.endsWith(".0") ? s.substring(0, s.length() - 2) : s; } + + private static String buildRegex() { + // Dynamic generate source part to avoid any future Minecraft or Adventure update + String sourcePart = Arrays.stream(Sound.Source.values()) + .map(Enum::name) + .collect(Collectors.joining("|")); + + return "^(?[a-zA-Z0-9:._-]+)" + + "(?:\\s+(?\\d+(?:\\.\\d+)?))?" + + "(?:\\s+(?\\d+(?:\\.\\d+)?))?" + + "(?:\\s+(?" + sourcePart + "))?" + + "\\s*$"; + } } From 2e6490ab9b9a3bfd3ca8b2e3afd547abaf8b666d Mon Sep 17 00:00:00 2001 From: zvyap Date: Tue, 3 Feb 2026 14:21:30 +0800 Subject: [PATCH 11/18] Format code to 80 chars per line --- .../exlll/configlib/AdventureConfigLib.java | 19 ++-- .../de/exlll/configlib/ComponentFormat.java | 29 ++++-- .../exlll/configlib/ComponentSerializer.java | 24 +++-- .../de/exlll/configlib/KeySerializer.java | 3 +- .../de/exlll/configlib/SoundSerializer.java | 5 +- .../configlib/AdventureComponentTests.java | 9 +- .../AdventureConfigurationTests.java | 96 +++++++++++++------ .../configlib/AdventureTestConfiguration.java | 40 +++++--- .../de/exlll/configlib/KeySerializerTest.java | 3 +- .../exlll/configlib/SoundSerializerTest.java | 9 +- .../java/de/exlll/configlib/ConfigLib.java | 6 +- 11 files changed, 169 insertions(+), 74 deletions(-) diff --git a/configlib-adventure/src/main/java/de/exlll/configlib/AdventureConfigLib.java b/configlib-adventure/src/main/java/de/exlll/configlib/AdventureConfigLib.java index d253efd..ada468a 100644 --- a/configlib-adventure/src/main/java/de/exlll/configlib/AdventureConfigLib.java +++ b/configlib-adventure/src/main/java/de/exlll/configlib/AdventureConfigLib.java @@ -10,9 +10,10 @@ * Utility class providing default serializers for Adventure library types. */ public final class AdventureConfigLib { + // Use MiniMessage as the default format since MiniMessage covered all + // component type private static final List DEFAULT_FORMAT_ORDER = List.of( - ComponentFormat.MINI_MESSAGE // Use MiniMessage as the default format since MiniMessage covered all - // component type + ComponentFormat.MINI_MESSAGE ); private AdventureConfigLib() { @@ -25,8 +26,9 @@ private AdventureConfigLib() { * @param the builder type * @return the builder with default serializers added */ - public static > ConfigurationProperties.Builder addDefaults( - ConfigurationProperties.Builder builder) { + public static > + ConfigurationProperties.Builder addDefaults( + ConfigurationProperties.Builder builder) { return addDefaults(builder, DEFAULT_FORMAT_ORDER, DEFAULT_FORMAT_ORDER); } @@ -42,10 +44,13 @@ public static > ConfigurationProper * @param the builder type * @return the builder with default serializers added */ - public static > ConfigurationProperties.Builder addDefaults( + public static > + ConfigurationProperties.Builder addDefaults( ConfigurationProperties.Builder builder, - List serializeOrder, List deserializeOrder) { - builder.addSerializer(Component.class, new ComponentSerializer(serializeOrder, deserializeOrder)); + List serializeOrder, + List deserializeOrder) { + builder.addSerializer(Component.class, + new ComponentSerializer(serializeOrder, deserializeOrder)); builder.addSerializer(Key.class, new KeySerializer()); builder.addSerializer(Sound.class, new SoundSerializer()); return builder; diff --git a/configlib-adventure/src/main/java/de/exlll/configlib/ComponentFormat.java b/configlib-adventure/src/main/java/de/exlll/configlib/ComponentFormat.java index d61c741..59c765c 100644 --- a/configlib-adventure/src/main/java/de/exlll/configlib/ComponentFormat.java +++ b/configlib-adventure/src/main/java/de/exlll/configlib/ComponentFormat.java @@ -10,15 +10,27 @@ * serialization. */ public enum ComponentFormat { - /** MiniMessage format with tags like {@code } or {@code }. */ + /** + * MiniMessage format with tags like {@code } or {@code }. + */ MINI_MESSAGE(Patterns.MINI_MESSAGE_PATTERN.asPredicate()), - /** Translation key format for translatable components. */ + /** + * Translation key format for translatable components. + */ TRANSLATION_KEY(input -> true), // translation keys can be any format - /** Legacy format using ampersand ({@code &}) as the color code prefix. */ - LEGACY_AMPERSAND(input -> input.indexOf(LegacyComponentSerializer.AMPERSAND_CHAR) != -1), - /** Legacy format using section symbol ({@code §}) as the color code prefix. */ - LEGACY_SECTION(input -> input.indexOf(LegacyComponentSerializer.SECTION_CHAR) != -1), - /** Minecraft JSON format for components. */ + /** + * Legacy format using ampersand ({@code &}) as the color code prefix. + */ + LEGACY_AMPERSAND(input -> + input.indexOf(LegacyComponentSerializer.AMPERSAND_CHAR) != -1), + /** + * Legacy format using section symbol ({@code §}) as the color code prefix. + */ + LEGACY_SECTION(input -> + input.indexOf(LegacyComponentSerializer.SECTION_CHAR) != -1), + /** + * Minecraft JSON format for components. + */ MINECRAFT_JSON(input -> { input = input.trim(); return input.startsWith("{") && input.endsWith("}"); @@ -27,7 +39,8 @@ public enum ComponentFormat { // Hack to avoid compiler error while singleton pattern initialization private static class Patterns { // Pattern to detect any in a string - static final Pattern MINI_MESSAGE_PATTERN = Pattern.compile("<[a-zA-Z0-9_:-]+(?::[^<>]+)?>"); + static final Pattern MINI_MESSAGE_PATTERN = + Pattern.compile("<[a-zA-Z0-9_:-]+(?::[^<>]+)?>"); } private final Predicate inputPredicate; diff --git a/configlib-adventure/src/main/java/de/exlll/configlib/ComponentSerializer.java b/configlib-adventure/src/main/java/de/exlll/configlib/ComponentSerializer.java index b8aae85..690ac9b 100644 --- a/configlib-adventure/src/main/java/de/exlll/configlib/ComponentSerializer.java +++ b/configlib-adventure/src/main/java/de/exlll/configlib/ComponentSerializer.java @@ -18,18 +18,21 @@ public final class ComponentSerializer implements Serializer private final List deserializeOrder; /** - * Creates a new ComponentSerializer with separate format orders for serialization and deserialization. + * Creates a new ComponentSerializer with separate format orders for + * serialization and deserialization. * * @param serializeOrder the order of formats to try when serializing * @param deserializeOrder the order of formats to try when deserializing */ - public ComponentSerializer(List serializeOrder, List deserializeOrder) { + public ComponentSerializer(List serializeOrder, + List deserializeOrder) { this.serializeOrder = List.copyOf(serializeOrder); this.deserializeOrder = List.copyOf(deserializeOrder); } /** - * Creates a new ComponentSerializer using the same format order for both serialization and deserialization. + * Creates a new ComponentSerializer using the same format order for + * both serialization and deserialization. * * @param formats the formats to use, in order of preference */ @@ -72,18 +75,23 @@ public Component deserialize(String element) { private String serialize(Component component, ComponentFormat format) { return switch (format) { case MINI_MESSAGE -> MiniMessage.miniMessage().serialize(component); - case LEGACY_AMPERSAND -> LegacyComponentSerializer.legacyAmpersand().serialize(component); - case LEGACY_SECTION -> LegacyComponentSerializer.legacySection().serialize(component); + case LEGACY_AMPERSAND -> + LegacyComponentSerializer.legacyAmpersand().serialize(component); + case LEGACY_SECTION -> + LegacyComponentSerializer.legacySection().serialize(component); case MINECRAFT_JSON -> GsonComponentSerializer.gson().serialize(component); - case TRANSLATION_KEY -> PlainTextComponentSerializer.plainText().serialize(component); + case TRANSLATION_KEY -> + PlainTextComponentSerializer.plainText().serialize(component); }; } private Component deserialize(String string, ComponentFormat format) { return switch (format) { case MINI_MESSAGE -> MiniMessage.miniMessage().deserialize(string); - case LEGACY_AMPERSAND -> LegacyComponentSerializer.legacyAmpersand().deserialize(string); - case LEGACY_SECTION -> LegacyComponentSerializer.legacySection().deserialize(string); + case LEGACY_AMPERSAND -> + LegacyComponentSerializer.legacyAmpersand().deserialize(string); + case LEGACY_SECTION -> + LegacyComponentSerializer.legacySection().deserialize(string); case MINECRAFT_JSON -> GsonComponentSerializer.gson().deserialize(string); case TRANSLATION_KEY -> Component.translatable(string); }; diff --git a/configlib-adventure/src/main/java/de/exlll/configlib/KeySerializer.java b/configlib-adventure/src/main/java/de/exlll/configlib/KeySerializer.java index c0077c9..c692bd2 100644 --- a/configlib-adventure/src/main/java/de/exlll/configlib/KeySerializer.java +++ b/configlib-adventure/src/main/java/de/exlll/configlib/KeySerializer.java @@ -23,7 +23,8 @@ public KeySerializer(String defaultNamespace) { OptionalInt result = Key.checkNamespace(defaultNamespace); if (result.isPresent()) { throw new IllegalArgumentException( - "Invalid namespace at index " + result.getAsInt() + ": " + defaultNamespace); + "Invalid namespace at index " + result.getAsInt() + ": " + + defaultNamespace); } } diff --git a/configlib-adventure/src/main/java/de/exlll/configlib/SoundSerializer.java b/configlib-adventure/src/main/java/de/exlll/configlib/SoundSerializer.java index 0a997d6..d072b82 100644 --- a/configlib-adventure/src/main/java/de/exlll/configlib/SoundSerializer.java +++ b/configlib-adventure/src/main/java/de/exlll/configlib/SoundSerializer.java @@ -2,7 +2,6 @@ import net.kyori.adventure.key.Key; import net.kyori.adventure.sound.Sound; -import net.kyori.adventure.text.minimessage.MiniMessage; import java.util.Arrays; import java.util.Objects; @@ -18,7 +17,9 @@ * Example: {@code "minecraft:entity.player.levelup 1.0 1.0 MASTER"} */ public final class SoundSerializer implements Serializer { - /** The delimiter used to separate sound components in the serialized string. */ + /** + * The delimiter used to separate sound components in the serialized string. + */ public static final String DELIMINATOR = " "; private static final Pattern SOUND_PATTERN = Pattern.compile(buildRegex()); diff --git a/configlib-adventure/src/test/java/de/exlll/configlib/AdventureComponentTests.java b/configlib-adventure/src/test/java/de/exlll/configlib/AdventureComponentTests.java index fcc4be4..3a253bc 100644 --- a/configlib-adventure/src/test/java/de/exlll/configlib/AdventureComponentTests.java +++ b/configlib-adventure/src/test/java/de/exlll/configlib/AdventureComponentTests.java @@ -51,9 +51,11 @@ void testMultipleFormats() { } private void runTestWithOrder(List deserializeOrder) { - YamlConfigurationProperties.Builder builder = YamlConfigurationProperties.newBuilder(); + YamlConfigurationProperties.Builder builder = YamlConfigurationProperties + .newBuilder(); // Set a fixed serialize order, we are testing deserialization. - AdventureConfigLib.addDefaults(builder, List.of(ComponentFormat.MINI_MESSAGE), deserializeOrder); + AdventureConfigLib.addDefaults(builder, + List.of(ComponentFormat.MINI_MESSAGE), deserializeOrder); YamlConfigurationProperties properties = builder.build(); YamlConfigurationStore store = new YamlConfigurationStore<>( @@ -111,7 +113,8 @@ private List> generatePermutations(List original) { } E firstElement = original.get(0); List> returnValue = new ArrayList<>(); - List> permutations = generatePermutations(original.subList(1, original.size())); + List> permutations = generatePermutations( + original.subList(1, original.size())); for (List smallerPermutated : permutations) { for (int index = 0; index <= smallerPermutated.size(); index++) { List temp = new ArrayList<>(smallerPermutated); diff --git a/configlib-adventure/src/test/java/de/exlll/configlib/AdventureConfigurationTests.java b/configlib-adventure/src/test/java/de/exlll/configlib/AdventureConfigurationTests.java index 9a5aeca..976e46a 100644 --- a/configlib-adventure/src/test/java/de/exlll/configlib/AdventureConfigurationTests.java +++ b/configlib-adventure/src/test/java/de/exlll/configlib/AdventureConfigurationTests.java @@ -37,7 +37,8 @@ void tearDown() throws IOException { // Test to make sure data is consistent after serialization and deserialization @Test void testSerializationAndDeserializationDefaults() { - YamlConfigurationProperties.Builder builder = YamlConfigurationProperties.newBuilder(); + YamlConfigurationProperties.Builder builder = YamlConfigurationProperties + .newBuilder(); AdventureConfigLib.addDefaults(builder); YamlConfigurationProperties properties = builder.build(); @@ -52,36 +53,77 @@ void testSerializationAndDeserializationDefaults() { assertConfigsEqual(original, loaded); } - private void assertConfigsEqual(AdventureTestConfiguration expected, AdventureTestConfiguration actual) { + private void assertConfigsEqual(AdventureTestConfiguration expected, + AdventureTestConfiguration actual) { assertNotNull(actual); - assertEquals(expected.simpleText, actual.simpleText, "Simple Text mismatch"); - assertEquals(expected.coloredText, actual.coloredText, "Colored Text mismatch"); - assertEquals(expected.decoratedText, actual.decoratedText, "Decorated Text mismatch"); - assertEquals(expected.clickLink, actual.clickLink, "Click Link mismatch"); - assertEquals(expected.hoverText, actual.hoverText, "Hover Text mismatch"); - - assertEquals(serialize(expected.gradientText), serialize(actual.gradientText), "Gradient Text mismatch"); - assertEquals(serialize(expected.rainbowText), serialize(actual.rainbowText), "Rainbow Text mismatch"); - assertEquals(serialize(expected.formattedText), serialize(actual.formattedText), "Formatted Text mismatch"); - assertEquals(serialize(expected.clickText), serialize(actual.clickText), "Click Text mismatch"); - assertEquals(serialize(expected.hoverTextComplex), serialize(actual.hoverTextComplex), + assertEquals(expected.simpleText, + actual.simpleText, + "Simple Text mismatch"); + assertEquals(expected.coloredText, + actual.coloredText, + "Colored Text mismatch"); + assertEquals(expected.decoratedText, + actual.decoratedText, + "Decorated Text mismatch"); + assertEquals(expected.clickLink, + actual.clickLink, + "Click Link mismatch"); + assertEquals(expected.hoverText, + actual.hoverText, + "Hover Text mismatch"); + + assertEquals(serialize(expected.gradientText), + serialize(actual.gradientText), + "Gradient Text mismatch"); + assertEquals(serialize(expected.rainbowText), + serialize(actual.rainbowText), + "Rainbow Text mismatch"); + assertEquals(serialize(expected.formattedText), + serialize(actual.formattedText), + "Formatted Text mismatch"); + assertEquals(serialize(expected.clickText), + serialize(actual.clickText), + "Click Text mismatch"); + assertEquals(serialize(expected.hoverTextComplex), + serialize(actual.hoverTextComplex), "Hover Text Complex mismatch"); - assertEquals(serialize(expected.keybindText), serialize(actual.keybindText), "Keybind Text (MM) mismatch"); - assertEquals(serialize(expected.translatableText), serialize(actual.translatableText), + assertEquals(serialize(expected.keybindText), + serialize(actual.keybindText), + "Keybind Text (MM) mismatch"); + assertEquals(serialize(expected.translatableText), + serialize(actual.translatableText), "Translatable Text (MM) mismatch"); - assertEquals(expected.translatable, actual.translatable, "Translatable mismatch"); - assertEquals(expected.keybind, actual.keybind, "Keybind mismatch"); - - assertEquals(expected.simpleKey, actual.simpleKey, "Key mismatch"); - - assertEquals(expected.simpleSound.name(), actual.simpleSound.name(), "Sound Name mismatch"); - assertEquals(expected.simpleSound.source(), actual.simpleSound.source(), "Sound Source mismatch"); - assertEquals(expected.simpleSound.volume(), actual.simpleSound.volume(), "Sound Volume mismatch"); - assertEquals(expected.simpleSound.pitch(), actual.simpleSound.pitch(), "Sound Pitch mismatch"); - - assertEquals(expected.componentList, actual.componentList, "List mismatch"); - assertEquals(expected.componentMap, actual.componentMap, "Map mismatch"); + assertEquals(expected.translatable, + actual.translatable, + "Translatable mismatch"); + assertEquals(expected.keybind, + actual.keybind, + "Keybind mismatch"); + + assertEquals(expected.simpleKey, + actual.simpleKey, + "Key mismatch"); + + assertEquals(expected.simpleSound.name(), + actual.simpleSound.name(), + "Sound Name mismatch"); + assertEquals(expected.simpleSound.source(), + actual.simpleSound.source(), + "Sound Source mismatch"); + assertEquals(expected.simpleSound.volume(), + actual.simpleSound.volume(), + "Sound Volume mismatch"); + assertEquals(expected.simpleSound.pitch(), + actual.simpleSound.pitch(), + "Sound Pitch mismatch"); + + assertEquals(expected.componentList, + actual.componentList, + "List mismatch"); + assertEquals(expected.componentMap, + actual.componentMap, + "Map mismatch"); } private String serialize(Component component) { diff --git a/configlib-adventure/src/test/java/de/exlll/configlib/AdventureTestConfiguration.java b/configlib-adventure/src/test/java/de/exlll/configlib/AdventureTestConfiguration.java index 49ca95c..e25b1d7 100644 --- a/configlib-adventure/src/test/java/de/exlll/configlib/AdventureTestConfiguration.java +++ b/configlib-adventure/src/test/java/de/exlll/configlib/AdventureTestConfiguration.java @@ -18,38 +18,54 @@ public class AdventureTestConfiguration { Component coloredText = Component.text("Red Text", NamedTextColor.RED); - Component decoratedText = Component.text("Bold Italic", NamedTextColor.BLUE, TextDecoration.BOLD, - TextDecoration.ITALIC); + Component decoratedText = Component.text("Bold Italic", + NamedTextColor.BLUE, TextDecoration.BOLD, TextDecoration.ITALIC); Component clickLink = Component.text("Click Me") .clickEvent(ClickEvent.openUrl("https://example.com")); Component hoverText = Component.text("Hover Me") - .hoverEvent(HoverEvent.showText(Component.text("Hover Content", NamedTextColor.GREEN))); + .hoverEvent(HoverEvent.showText( + Component.text("Hover Content", NamedTextColor.GREEN))); Component gradientText = MiniMessage.miniMessage() .deserialize("Gradient Text"); - Component rainbowText = MiniMessage.miniMessage().deserialize("Rainbow"); - Component formattedText = MiniMessage.miniMessage().deserialize( - "Bold Italic Underlined Strike Obfuscated"); - Component clickText = MiniMessage.miniMessage().deserialize("Click Command"); + + Component rainbowText = MiniMessage.miniMessage() + .deserialize("Rainbow"); + + Component formattedText = MiniMessage.miniMessage() + .deserialize("Bold " + + "Italic " + + "Underlined " + + "Strike " + + "Obfuscated"); + + Component clickText = MiniMessage.miniMessage() + .deserialize("Click Command"); + // Note: Hover events with complex content inside might be sensitive to // serialization round trips Component hoverTextComplex = MiniMessage.miniMessage() .deserialize("Red Hover'>Hover Text"); - Component keybindText = MiniMessage.miniMessage().deserialize(""); - Component translatableText = MiniMessage.miniMessage().deserialize(""); + Component keybindText = MiniMessage.miniMessage() + .deserialize(""); + Component translatableText = MiniMessage.miniMessage() + .deserialize(""); Key simpleKey = Key.key("namespace", "value"); - Sound simpleSound = Sound.sound(Key.key("minecraft:entity.player.levelup"), Sound.Source.MASTER, 1f, 1f); + Sound simpleSound = Sound.sound( + Key.key("minecraft:entity.player.levelup"), + Sound.Source.MASTER, + 1f, + 1f); List componentList = List.of( Component.text("Item 1"), Component.text("Item 2", NamedTextColor.GOLD)); - Map componentMap = Map.of( - "welcome", Component.text("Welcome!"), + Map componentMap = Map.of("welcome", Component.text("Welcome!"), "goodbye", Component.text("Goodbye!", NamedTextColor.RED)); // Testing specific component types if possible via MiniMessage or construction diff --git a/configlib-adventure/src/test/java/de/exlll/configlib/KeySerializerTest.java b/configlib-adventure/src/test/java/de/exlll/configlib/KeySerializerTest.java index 567bcea..9f47ec2 100644 --- a/configlib-adventure/src/test/java/de/exlll/configlib/KeySerializerTest.java +++ b/configlib-adventure/src/test/java/de/exlll/configlib/KeySerializerTest.java @@ -43,6 +43,7 @@ void testDeserializeWithColonAndDefaultNamespace() { KeySerializer serializer = new KeySerializer("default"); // Key.key("default", "namespace:value") will throw InvalidKeyException because // ':' is not allowed in value - assertThrows(InvalidKeyException.class, () -> serializer.deserialize("namespace:value")); + assertThrows(InvalidKeyException.class, () -> + serializer.deserialize("namespace:value")); } } diff --git a/configlib-adventure/src/test/java/de/exlll/configlib/SoundSerializerTest.java b/configlib-adventure/src/test/java/de/exlll/configlib/SoundSerializerTest.java index d35a958..5fca04a 100644 --- a/configlib-adventure/src/test/java/de/exlll/configlib/SoundSerializerTest.java +++ b/configlib-adventure/src/test/java/de/exlll/configlib/SoundSerializerTest.java @@ -33,7 +33,8 @@ void testDeserializeWithPitch() { @Test void testDeserializeWithPitchAndVolume() { - String input = "minecraft:test" + SoundSerializer.DELIMINATOR + "0.5" + SoundSerializer.DELIMINATOR + "2.0"; + String input = "minecraft:test" + SoundSerializer.DELIMINATOR + "0.5" + + SoundSerializer.DELIMINATOR + "2.0"; Sound sound = serializer.deserialize(input); assertEquals(Key.key("minecraft", "test"), sound.name()); @@ -44,7 +45,8 @@ void testDeserializeWithPitchAndVolume() { @Test void testDeserializeFull() { - String input = "minecraft:test" + SoundSerializer.DELIMINATOR + "0.8" + SoundSerializer.DELIMINATOR + "0.5" + String input = "minecraft:test" + SoundSerializer.DELIMINATOR + "0.8" + + SoundSerializer.DELIMINATOR + "0.5" + SoundSerializer.DELIMINATOR + "MUSIC"; Sound sound = serializer.deserialize(input); @@ -81,7 +83,8 @@ void testDeserializeAmbiguousKeyLookingLikeSource() { @Test void testRoundTrip() { - Sound original = Sound.sound(Key.key("test:sound"), Sound.Source.AMBIENT, 0.5f, 1.2f); + Sound original = Sound.sound( + Key.key("test:sound"), Sound.Source.AMBIENT, 0.5f, 1.2f); String serialized = serializer.serialize(original); Sound deserialized = serializer.deserialize(serialized); diff --git a/configlib-paper/src/main/java/de/exlll/configlib/ConfigLib.java b/configlib-paper/src/main/java/de/exlll/configlib/ConfigLib.java index b00cc37..77cad5a 100644 --- a/configlib-paper/src/main/java/de/exlll/configlib/ConfigLib.java +++ b/configlib-paper/src/main/java/de/exlll/configlib/ConfigLib.java @@ -15,7 +15,8 @@ public final class ConfigLib extends JavaPlugin { * You can configure these properties further by creating a new builder using the * {@code toBuilder()} method of this object. */ - public static final YamlConfigurationProperties BUKKIT_DEFAULT_PROPERTIES = initializeBukkitDefaultProperties(); + public static final YamlConfigurationProperties BUKKIT_DEFAULT_PROPERTIES = + initializeBukkitDefaultProperties(); /** * A {@code YamlConfigurationProperties} object design for Paper software, it provides native support for Adventure library, @@ -24,7 +25,8 @@ public final class ConfigLib extends JavaPlugin { * You can configure these properties further by creating a new builder using the * {@code toBuilder()} method of this object. */ - public static final YamlConfigurationProperties PAPER_DEFAULT_PROPERTIES = initializePaperDefaultProperties(); + public static final YamlConfigurationProperties PAPER_DEFAULT_PROPERTIES = + initializePaperDefaultProperties(); private static YamlConfigurationProperties initializeBukkitDefaultProperties() { return builder().build(); From 60883de95110ac3a33056e352df258f304e4de4c Mon Sep 17 00:00:00 2001 From: zvyap Date: Tue, 3 Feb 2026 14:22:41 +0800 Subject: [PATCH 12/18] Prefix all top level file --- ...mat.java => AdventureComponentFormat.java} | 4 ++-- ...java => AdventureComponentSerializer.java} | 20 ++++++++--------- .../exlll/configlib/AdventureConfigLib.java | 14 ++++++------ ...lizer.java => AdventureKeySerializer.java} | 6 ++--- ...zer.java => AdventureSoundSerializer.java} | 6 ++--- .../configlib/AdventureComponentTests.java | 18 +++++++-------- ...t.java => AdventureKeySerializerTest.java} | 12 +++++----- ...java => AdventureSoundSerializerTest.java} | 22 +++++++++---------- 8 files changed, 51 insertions(+), 51 deletions(-) rename configlib-adventure/src/main/java/de/exlll/configlib/{ComponentFormat.java => AdventureComponentFormat.java} (94%) rename configlib-adventure/src/main/java/de/exlll/configlib/{ComponentSerializer.java => AdventureComponentSerializer.java} (79%) rename configlib-adventure/src/main/java/de/exlll/configlib/{KeySerializer.java => AdventureKeySerializer.java} (88%) rename configlib-adventure/src/main/java/de/exlll/configlib/{SoundSerializer.java => AdventureSoundSerializer.java} (95%) rename configlib-adventure/src/test/java/de/exlll/configlib/{KeySerializerTest.java => AdventureKeySerializerTest.java} (76%) rename configlib-adventure/src/test/java/de/exlll/configlib/{SoundSerializerTest.java => AdventureSoundSerializerTest.java} (76%) diff --git a/configlib-adventure/src/main/java/de/exlll/configlib/ComponentFormat.java b/configlib-adventure/src/main/java/de/exlll/configlib/AdventureComponentFormat.java similarity index 94% rename from configlib-adventure/src/main/java/de/exlll/configlib/ComponentFormat.java rename to configlib-adventure/src/main/java/de/exlll/configlib/AdventureComponentFormat.java index 59c765c..e42fd0f 100644 --- a/configlib-adventure/src/main/java/de/exlll/configlib/ComponentFormat.java +++ b/configlib-adventure/src/main/java/de/exlll/configlib/AdventureComponentFormat.java @@ -9,7 +9,7 @@ * Represents the different text formats supported for Adventure Component * serialization. */ -public enum ComponentFormat { +public enum AdventureComponentFormat { /** * MiniMessage format with tags like {@code } or {@code }. */ @@ -45,7 +45,7 @@ private static class Patterns { private final Predicate inputPredicate; - ComponentFormat(Predicate inputPredicate) { + AdventureComponentFormat(Predicate inputPredicate) { this.inputPredicate = inputPredicate; } diff --git a/configlib-adventure/src/main/java/de/exlll/configlib/ComponentSerializer.java b/configlib-adventure/src/main/java/de/exlll/configlib/AdventureComponentSerializer.java similarity index 79% rename from configlib-adventure/src/main/java/de/exlll/configlib/ComponentSerializer.java rename to configlib-adventure/src/main/java/de/exlll/configlib/AdventureComponentSerializer.java index 690ac9b..63106ed 100644 --- a/configlib-adventure/src/main/java/de/exlll/configlib/ComponentSerializer.java +++ b/configlib-adventure/src/main/java/de/exlll/configlib/AdventureComponentSerializer.java @@ -13,9 +13,9 @@ * Serializer for Adventure {@link Component} objects. * Supports multiple formats including MiniMessage, legacy, and JSON. */ -public final class ComponentSerializer implements Serializer { - private final List serializeOrder; - private final List deserializeOrder; +public final class AdventureComponentSerializer implements Serializer { + private final List serializeOrder; + private final List deserializeOrder; /** * Creates a new ComponentSerializer with separate format orders for @@ -24,8 +24,8 @@ public final class ComponentSerializer implements Serializer * @param serializeOrder the order of formats to try when serializing * @param deserializeOrder the order of formats to try when deserializing */ - public ComponentSerializer(List serializeOrder, - List deserializeOrder) { + public AdventureComponentSerializer(List serializeOrder, + List deserializeOrder) { this.serializeOrder = List.copyOf(serializeOrder); this.deserializeOrder = List.copyOf(deserializeOrder); } @@ -36,7 +36,7 @@ public ComponentSerializer(List serializeOrder, * * @param formats the formats to use, in order of preference */ - public ComponentSerializer(ComponentFormat... formats) { + public AdventureComponentSerializer(AdventureComponentFormat... formats) { this(Arrays.asList(formats), Arrays.asList(formats)); } @@ -46,7 +46,7 @@ public String serialize(Component element) { return null; } - for (ComponentFormat format : serializeOrder) { + for (AdventureComponentFormat format : serializeOrder) { return serialize(element, format); } @@ -60,7 +60,7 @@ public Component deserialize(String element) { return null; } - for (ComponentFormat format : deserializeOrder) { + for (AdventureComponentFormat format : deserializeOrder) { if (!format.matches(element)) { continue; } @@ -72,7 +72,7 @@ public Component deserialize(String element) { return MiniMessage.miniMessage().deserialize(element); } - private String serialize(Component component, ComponentFormat format) { + private String serialize(Component component, AdventureComponentFormat format) { return switch (format) { case MINI_MESSAGE -> MiniMessage.miniMessage().serialize(component); case LEGACY_AMPERSAND -> @@ -85,7 +85,7 @@ private String serialize(Component component, ComponentFormat format) { }; } - private Component deserialize(String string, ComponentFormat format) { + private Component deserialize(String string, AdventureComponentFormat format) { return switch (format) { case MINI_MESSAGE -> MiniMessage.miniMessage().deserialize(string); case LEGACY_AMPERSAND -> diff --git a/configlib-adventure/src/main/java/de/exlll/configlib/AdventureConfigLib.java b/configlib-adventure/src/main/java/de/exlll/configlib/AdventureConfigLib.java index ada468a..07f0696 100644 --- a/configlib-adventure/src/main/java/de/exlll/configlib/AdventureConfigLib.java +++ b/configlib-adventure/src/main/java/de/exlll/configlib/AdventureConfigLib.java @@ -12,8 +12,8 @@ public final class AdventureConfigLib { // Use MiniMessage as the default format since MiniMessage covered all // component type - private static final List DEFAULT_FORMAT_ORDER = List.of( - ComponentFormat.MINI_MESSAGE + private static final List DEFAULT_FORMAT_ORDER = List.of( + AdventureComponentFormat.MINI_MESSAGE ); private AdventureConfigLib() { @@ -47,12 +47,12 @@ ConfigurationProperties.Builder addDefaults( public static > ConfigurationProperties.Builder addDefaults( ConfigurationProperties.Builder builder, - List serializeOrder, - List deserializeOrder) { + List serializeOrder, + List deserializeOrder) { builder.addSerializer(Component.class, - new ComponentSerializer(serializeOrder, deserializeOrder)); - builder.addSerializer(Key.class, new KeySerializer()); - builder.addSerializer(Sound.class, new SoundSerializer()); + new AdventureComponentSerializer(serializeOrder, deserializeOrder)); + builder.addSerializer(Key.class, new AdventureKeySerializer()); + builder.addSerializer(Sound.class, new AdventureSoundSerializer()); return builder; } } diff --git a/configlib-adventure/src/main/java/de/exlll/configlib/KeySerializer.java b/configlib-adventure/src/main/java/de/exlll/configlib/AdventureKeySerializer.java similarity index 88% rename from configlib-adventure/src/main/java/de/exlll/configlib/KeySerializer.java rename to configlib-adventure/src/main/java/de/exlll/configlib/AdventureKeySerializer.java index c692bd2..5e0eaa2 100644 --- a/configlib-adventure/src/main/java/de/exlll/configlib/KeySerializer.java +++ b/configlib-adventure/src/main/java/de/exlll/configlib/AdventureKeySerializer.java @@ -7,7 +7,7 @@ /** * Serializer for Adventure {@link Key} objects. */ -public final class KeySerializer implements Serializer { +public final class AdventureKeySerializer implements Serializer { private final String defaultNamespace; @@ -18,7 +18,7 @@ public final class KeySerializer implements Serializer { * without a namespace * @throws IllegalArgumentException if the namespace is invalid */ - public KeySerializer(String defaultNamespace) { + public AdventureKeySerializer(String defaultNamespace) { this.defaultNamespace = defaultNamespace; OptionalInt result = Key.checkNamespace(defaultNamespace); if (result.isPresent()) { @@ -31,7 +31,7 @@ public KeySerializer(String defaultNamespace) { /** * Creates a new KeySerializer using Adventure's default namespace (minecraft). */ - public KeySerializer() { + public AdventureKeySerializer() { this.defaultNamespace = null; // Use Adventure's default namespace } diff --git a/configlib-adventure/src/main/java/de/exlll/configlib/SoundSerializer.java b/configlib-adventure/src/main/java/de/exlll/configlib/AdventureSoundSerializer.java similarity index 95% rename from configlib-adventure/src/main/java/de/exlll/configlib/SoundSerializer.java rename to configlib-adventure/src/main/java/de/exlll/configlib/AdventureSoundSerializer.java index d072b82..283a1b4 100644 --- a/configlib-adventure/src/main/java/de/exlll/configlib/SoundSerializer.java +++ b/configlib-adventure/src/main/java/de/exlll/configlib/AdventureSoundSerializer.java @@ -16,7 +16,7 @@ *

* Example: {@code "minecraft:entity.player.levelup 1.0 1.0 MASTER"} */ -public final class SoundSerializer implements Serializer { +public final class AdventureSoundSerializer implements Serializer { /** * The delimiter used to separate sound components in the serialized string. */ @@ -30,7 +30,7 @@ public final class SoundSerializer implements Serializer { * Creates a new SoundSerializer with the default source set to * {@link Sound.Source#MASTER}. */ - public SoundSerializer() { + public AdventureSoundSerializer() { this.defaultSource = Sound.Source.MASTER; } @@ -39,7 +39,7 @@ public SoundSerializer() { * * @param defaultSource the default sound source to use when deserializing */ - public SoundSerializer(Sound.Source defaultSource) { + public AdventureSoundSerializer(Sound.Source defaultSource) { Objects.requireNonNull(defaultSource, "defaultSource must not be null"); this.defaultSource = defaultSource; } diff --git a/configlib-adventure/src/test/java/de/exlll/configlib/AdventureComponentTests.java b/configlib-adventure/src/test/java/de/exlll/configlib/AdventureComponentTests.java index 3a253bc..85b62fe 100644 --- a/configlib-adventure/src/test/java/de/exlll/configlib/AdventureComponentTests.java +++ b/configlib-adventure/src/test/java/de/exlll/configlib/AdventureComponentTests.java @@ -37,25 +37,25 @@ void tearDown() throws IOException { @Test void testMultipleFormats() { - List baseFormats = new ArrayList<>(List.of( - ComponentFormat.MINI_MESSAGE, - ComponentFormat.MINECRAFT_JSON, - ComponentFormat.LEGACY_AMPERSAND, - ComponentFormat.LEGACY_SECTION)); + List baseFormats = new ArrayList<>(List.of( + AdventureComponentFormat.MINI_MESSAGE, + AdventureComponentFormat.MINECRAFT_JSON, + AdventureComponentFormat.LEGACY_AMPERSAND, + AdventureComponentFormat.LEGACY_SECTION)); - List> allPermutations = generatePermutations(baseFormats); + List> allPermutations = generatePermutations(baseFormats); - for (List deserializeOrder : allPermutations) { + for (List deserializeOrder : allPermutations) { runTestWithOrder(deserializeOrder); } } - private void runTestWithOrder(List deserializeOrder) { + private void runTestWithOrder(List deserializeOrder) { YamlConfigurationProperties.Builder builder = YamlConfigurationProperties .newBuilder(); // Set a fixed serialize order, we are testing deserialization. AdventureConfigLib.addDefaults(builder, - List.of(ComponentFormat.MINI_MESSAGE), deserializeOrder); + List.of(AdventureComponentFormat.MINI_MESSAGE), deserializeOrder); YamlConfigurationProperties properties = builder.build(); YamlConfigurationStore store = new YamlConfigurationStore<>( diff --git a/configlib-adventure/src/test/java/de/exlll/configlib/KeySerializerTest.java b/configlib-adventure/src/test/java/de/exlll/configlib/AdventureKeySerializerTest.java similarity index 76% rename from configlib-adventure/src/test/java/de/exlll/configlib/KeySerializerTest.java rename to configlib-adventure/src/test/java/de/exlll/configlib/AdventureKeySerializerTest.java index 9f47ec2..35e4752 100644 --- a/configlib-adventure/src/test/java/de/exlll/configlib/KeySerializerTest.java +++ b/configlib-adventure/src/test/java/de/exlll/configlib/AdventureKeySerializerTest.java @@ -7,25 +7,25 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; -class KeySerializerTest { +class AdventureKeySerializerTest { @Test void testSerialize() { - KeySerializer serializer = new KeySerializer(); + AdventureKeySerializer serializer = new AdventureKeySerializer(); Key key = Key.key("namespace", "value"); assertEquals("namespace:value", serializer.serialize(key)); } @Test void testDeserializeWithColon() { - KeySerializer serializer = new KeySerializer(); + AdventureKeySerializer serializer = new AdventureKeySerializer(); Key key = serializer.deserialize("namespace:value"); assertEquals(Key.key("namespace", "value"), key); } @Test void testDeserializeWithoutColonAndNoDefaultNamespace() { - KeySerializer serializer = new KeySerializer(); + AdventureKeySerializer serializer = new AdventureKeySerializer(); // Adventure default namespace is "minecraft" Key key = serializer.deserialize("value"); assertEquals(Key.key("minecraft", "value"), key); @@ -33,14 +33,14 @@ void testDeserializeWithoutColonAndNoDefaultNamespace() { @Test void testDeserializeWithoutColonAndDefaultNamespace() { - KeySerializer serializer = new KeySerializer("default"); + AdventureKeySerializer serializer = new AdventureKeySerializer("default"); Key key = serializer.deserialize("value"); assertEquals(Key.key("default", "value"), key); } @Test void testDeserializeWithColonAndDefaultNamespace() { - KeySerializer serializer = new KeySerializer("default"); + AdventureKeySerializer serializer = new AdventureKeySerializer("default"); // Key.key("default", "namespace:value") will throw InvalidKeyException because // ':' is not allowed in value assertThrows(InvalidKeyException.class, () -> diff --git a/configlib-adventure/src/test/java/de/exlll/configlib/SoundSerializerTest.java b/configlib-adventure/src/test/java/de/exlll/configlib/AdventureSoundSerializerTest.java similarity index 76% rename from configlib-adventure/src/test/java/de/exlll/configlib/SoundSerializerTest.java rename to configlib-adventure/src/test/java/de/exlll/configlib/AdventureSoundSerializerTest.java index 5fca04a..5bdf47c 100644 --- a/configlib-adventure/src/test/java/de/exlll/configlib/SoundSerializerTest.java +++ b/configlib-adventure/src/test/java/de/exlll/configlib/AdventureSoundSerializerTest.java @@ -6,8 +6,8 @@ import static org.junit.jupiter.api.Assertions.assertEquals; -class SoundSerializerTest { - private final SoundSerializer serializer = new SoundSerializer(); +class AdventureSoundSerializerTest { + private final AdventureSoundSerializer serializer = new AdventureSoundSerializer(); @Test void testDeserializeBasic() { @@ -22,7 +22,7 @@ void testDeserializeBasic() { @Test void testDeserializeWithPitch() { - String input = "minecraft:test" + SoundSerializer.DELIMINATOR + "1.5"; + String input = "minecraft:test" + AdventureSoundSerializer.DELIMINATOR + "1.5"; Sound sound = serializer.deserialize(input); assertEquals(Key.key("minecraft", "test"), sound.name()); @@ -33,8 +33,8 @@ void testDeserializeWithPitch() { @Test void testDeserializeWithPitchAndVolume() { - String input = "minecraft:test" + SoundSerializer.DELIMINATOR + "0.5" - + SoundSerializer.DELIMINATOR + "2.0"; + String input = "minecraft:test" + AdventureSoundSerializer.DELIMINATOR + "0.5" + + AdventureSoundSerializer.DELIMINATOR + "2.0"; Sound sound = serializer.deserialize(input); assertEquals(Key.key("minecraft", "test"), sound.name()); @@ -45,9 +45,9 @@ void testDeserializeWithPitchAndVolume() { @Test void testDeserializeFull() { - String input = "minecraft:test" + SoundSerializer.DELIMINATOR + "0.8" - + SoundSerializer.DELIMINATOR + "0.5" - + SoundSerializer.DELIMINATOR + "MUSIC"; + String input = "minecraft:test" + AdventureSoundSerializer.DELIMINATOR + "0.8" + + AdventureSoundSerializer.DELIMINATOR + "0.5" + + AdventureSoundSerializer.DELIMINATOR + "MUSIC"; Sound sound = serializer.deserialize(input); assertEquals(Key.key("minecraft", "test"), sound.name()); @@ -58,7 +58,7 @@ void testDeserializeFull() { @Test void testDeserializeComplexKey() { - String input = "minecraft:complex.key.name" + SoundSerializer.DELIMINATOR + "1.2"; + String input = "minecraft:complex.key.name" + AdventureSoundSerializer.DELIMINATOR + "1.2"; Sound sound = serializer.deserialize(input); assertEquals(Key.key("minecraft", "complex.key.name"), sound.name()); @@ -67,7 +67,7 @@ void testDeserializeComplexKey() { @Test void testDeserializeAmbiguousKeyLookingLikeFloat() { - String input = "custom" + SoundSerializer.DELIMINATOR + "1.5"; + String input = "custom" + AdventureSoundSerializer.DELIMINATOR + "1.5"; Sound sound = serializer.deserialize(input); assertEquals(Key.key("minecraft", "custom"), sound.name()); assertEquals(1.5f, sound.pitch()); @@ -75,7 +75,7 @@ void testDeserializeAmbiguousKeyLookingLikeFloat() { @Test void testDeserializeAmbiguousKeyLookingLikeSource() { - String input = "custom" + SoundSerializer.DELIMINATOR + "MUSIC"; + String input = "custom" + AdventureSoundSerializer.DELIMINATOR + "MUSIC"; Sound sound = serializer.deserialize(input); assertEquals(Key.key("minecraft", "custom"), sound.name()); assertEquals(Sound.Source.MUSIC, sound.source()); From c5ff72909b1564625e586002541cf47d46297b29 Mon Sep 17 00:00:00 2001 From: zvyap Date: Tue, 3 Feb 2026 14:29:20 +0800 Subject: [PATCH 13/18] Make sound serializer regex respect DELIMINATOR --- .../de/exlll/configlib/AdventureSoundSerializer.java | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/configlib-adventure/src/main/java/de/exlll/configlib/AdventureSoundSerializer.java b/configlib-adventure/src/main/java/de/exlll/configlib/AdventureSoundSerializer.java index 283a1b4..5afec7f 100644 --- a/configlib-adventure/src/main/java/de/exlll/configlib/AdventureSoundSerializer.java +++ b/configlib-adventure/src/main/java/de/exlll/configlib/AdventureSoundSerializer.java @@ -98,10 +98,11 @@ private static String buildRegex() { .map(Enum::name) .collect(Collectors.joining("|")); + String deliminator = Pattern.quote(DELIMINATOR); return "^(?[a-zA-Z0-9:._-]+)" + - "(?:\\s+(?\\d+(?:\\.\\d+)?))?" + - "(?:\\s+(?\\d+(?:\\.\\d+)?))?" + - "(?:\\s+(?" + sourcePart + "))?" + - "\\s*$"; + "(?:" + deliminator + "+(?\\d+(?:\\.\\d+)?))?" + + "(?:" + deliminator + "+(?\\d+(?:\\.\\d+)?))?" + + "(?:" + deliminator + "+(?" + sourcePart + "))?" + + deliminator + "*$"; } } From ddf147e18e05badf64631e444f98e78b064d9888 Mon Sep 17 00:00:00 2001 From: zvyap <52874570+zvyap@users.noreply.github.com> Date: Tue, 3 Feb 2026 14:48:10 +0800 Subject: [PATCH 14/18] Update README.md for miss type Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index b6a0383..272b234 100644 --- a/README.md +++ b/README.md @@ -445,7 +445,7 @@ AdventureConfigLib.addDefaults(builder, serializeOrder, deserializeOrder); ##### Sound Serialization -Sounds are serialized in a compact string format: ` [pitch] volume source`. +Sounds are serialized in a compact string format: ` [pitch] [volume] [source]`. Sound id can be anything even it is not in vanilla Minecraft to support custom sound from texture packs. ```yaml # Full format From 5347252812d913f7b9685b4eab6d69c46c7c3657 Mon Sep 17 00:00:00 2001 From: zvyap <52874570+zvyap@users.noreply.github.com> Date: Tue, 3 Feb 2026 14:49:08 +0800 Subject: [PATCH 15/18] Update JavaDocs for grammer error by AI Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../src/main/java/de/exlll/configlib/ConfigLib.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/configlib-paper/src/main/java/de/exlll/configlib/ConfigLib.java b/configlib-paper/src/main/java/de/exlll/configlib/ConfigLib.java index 77cad5a..87a712c 100644 --- a/configlib-paper/src/main/java/de/exlll/configlib/ConfigLib.java +++ b/configlib-paper/src/main/java/de/exlll/configlib/ConfigLib.java @@ -19,8 +19,9 @@ public final class ConfigLib extends JavaPlugin { initializeBukkitDefaultProperties(); /** - * A {@code YamlConfigurationProperties} object design for Paper software, it provides native support for Adventure library, - * serializers classes like {@link ItemStack} and other {@link ConfigurationSerializable} types. + * A {@code YamlConfigurationProperties} object designed for the Paper platform. It provides + * native support for the Adventure library and serializer classes for types like + * {@link ItemStack} and other {@link ConfigurationSerializable} implementations. *

* You can configure these properties further by creating a new builder using the * {@code toBuilder()} method of this object. From 6add010a2d39dd406de237b6b623d0a29a01b6ed Mon Sep 17 00:00:00 2001 From: zvyap Date: Tue, 3 Feb 2026 15:21:00 +0800 Subject: [PATCH 16/18] Fix readme class name --- README.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index b6a0383..c5c252a 100644 --- a/README.md +++ b/README.md @@ -422,7 +422,7 @@ YamlConfigurationProperties properties = builder.build(); ##### Component Formats -The `ComponentSerializer` supports multiple text formats through the `ComponentFormat` enum: +The `AdventureComponentSerializer` supports multiple text formats through the `AdventureComponentFormat` enum: | Format | Description | Example | |---------------------|----------------------------------------------------|------------------------------------| @@ -435,10 +435,10 @@ The `ComponentSerializer` supports multiple text formats through the `ComponentF You can customize the serialization and deserialization format order: ```java -List serializeOrder = List.of(ComponentFormat.MINI_MESSAGE); -List deserializeOrder = List.of( - ComponentFormat.MINI_MESSAGE, - ComponentFormat.LEGACY_AMPERSAND +List serializeOrder = List.of(AdventureComponentFormat.MINI_MESSAGE); +List deserializeOrder = List.of( + AdventureComponentFormat.MINI_MESSAGE, + AdventureComponentFormat.LEGACY_AMPERSAND ); AdventureConfigLib.addDefaults(builder, serializeOrder, deserializeOrder); ``` From 0350ec42b48a477781da71dcfde482e6006ff978 Mon Sep 17 00:00:00 2001 From: zvyap Date: Tue, 3 Feb 2026 15:37:02 +0800 Subject: [PATCH 17/18] Fix issue mention by copilot --- .../de/exlll/configlib/AdventureConfigLib.java | 9 ++++----- .../configlib/AdventureSoundSerializer.java | 16 ++++++++-------- .../AdventureSoundSerializerTest.java | 18 +++++++++--------- .../de/exlll/configlib/SerializersTest.java | 4 ++-- 4 files changed, 23 insertions(+), 24 deletions(-) diff --git a/configlib-adventure/src/main/java/de/exlll/configlib/AdventureConfigLib.java b/configlib-adventure/src/main/java/de/exlll/configlib/AdventureConfigLib.java index 07f0696..876c6db 100644 --- a/configlib-adventure/src/main/java/de/exlll/configlib/AdventureConfigLib.java +++ b/configlib-adventure/src/main/java/de/exlll/configlib/AdventureConfigLib.java @@ -27,8 +27,7 @@ private AdventureConfigLib() { * @return the builder with default serializers added */ public static > - ConfigurationProperties.Builder addDefaults( - ConfigurationProperties.Builder builder) { + ConfigurationProperties.Builder addDefaults(B builder) { return addDefaults(builder, DEFAULT_FORMAT_ORDER, DEFAULT_FORMAT_ORDER); } @@ -46,9 +45,9 @@ ConfigurationProperties.Builder addDefaults( */ public static > ConfigurationProperties.Builder addDefaults( - ConfigurationProperties.Builder builder, - List serializeOrder, - List deserializeOrder) { + B builder, + List serializeOrder, + List deserializeOrder) { builder.addSerializer(Component.class, new AdventureComponentSerializer(serializeOrder, deserializeOrder)); builder.addSerializer(Key.class, new AdventureKeySerializer()); diff --git a/configlib-adventure/src/main/java/de/exlll/configlib/AdventureSoundSerializer.java b/configlib-adventure/src/main/java/de/exlll/configlib/AdventureSoundSerializer.java index 5afec7f..70cafdc 100644 --- a/configlib-adventure/src/main/java/de/exlll/configlib/AdventureSoundSerializer.java +++ b/configlib-adventure/src/main/java/de/exlll/configlib/AdventureSoundSerializer.java @@ -20,7 +20,7 @@ public final class AdventureSoundSerializer implements Serializer /** * The delimiter used to separate sound components in the serialized string. */ - public static final String DELIMINATOR = " "; + public static final String DELIMITER = " "; private static final Pattern SOUND_PATTERN = Pattern.compile(buildRegex()); @@ -48,14 +48,14 @@ public AdventureSoundSerializer(Sound.Source defaultSource) { public String serialize(Sound element) { StringBuilder builder = new StringBuilder(element.name().asString()); if (element.source() != defaultSource) { - builder.append(DELIMINATOR).append(formatFloatSimple(element.pitch())); - builder.append(DELIMINATOR).append(formatFloatSimple(element.volume())); - builder.append(DELIMINATOR).append(element.source().name()); + builder.append(DELIMITER).append(formatFloatSimple(element.pitch())); + builder.append(DELIMITER).append(formatFloatSimple(element.volume())); + builder.append(DELIMITER).append(element.source().name()); } else if (element.volume() != 1f) { - builder.append(DELIMINATOR).append(formatFloatSimple(element.pitch())); - builder.append(DELIMINATOR).append(formatFloatSimple(element.volume())); + builder.append(DELIMITER).append(formatFloatSimple(element.pitch())); + builder.append(DELIMITER).append(formatFloatSimple(element.volume())); } else if (element.pitch() != 1f) { - builder.append(DELIMINATOR).append(formatFloatSimple(element.pitch())); + builder.append(DELIMITER).append(formatFloatSimple(element.pitch())); } return builder.toString(); @@ -98,7 +98,7 @@ private static String buildRegex() { .map(Enum::name) .collect(Collectors.joining("|")); - String deliminator = Pattern.quote(DELIMINATOR); + String deliminator = Pattern.quote(DELIMITER); return "^(?[a-zA-Z0-9:._-]+)" + "(?:" + deliminator + "+(?\\d+(?:\\.\\d+)?))?" + "(?:" + deliminator + "+(?\\d+(?:\\.\\d+)?))?" + diff --git a/configlib-adventure/src/test/java/de/exlll/configlib/AdventureSoundSerializerTest.java b/configlib-adventure/src/test/java/de/exlll/configlib/AdventureSoundSerializerTest.java index 5bdf47c..28d2054 100644 --- a/configlib-adventure/src/test/java/de/exlll/configlib/AdventureSoundSerializerTest.java +++ b/configlib-adventure/src/test/java/de/exlll/configlib/AdventureSoundSerializerTest.java @@ -22,7 +22,7 @@ void testDeserializeBasic() { @Test void testDeserializeWithPitch() { - String input = "minecraft:test" + AdventureSoundSerializer.DELIMINATOR + "1.5"; + String input = "minecraft:test" + AdventureSoundSerializer.DELIMITER + "1.5"; Sound sound = serializer.deserialize(input); assertEquals(Key.key("minecraft", "test"), sound.name()); @@ -33,8 +33,8 @@ void testDeserializeWithPitch() { @Test void testDeserializeWithPitchAndVolume() { - String input = "minecraft:test" + AdventureSoundSerializer.DELIMINATOR + "0.5" - + AdventureSoundSerializer.DELIMINATOR + "2.0"; + String input = "minecraft:test" + AdventureSoundSerializer.DELIMITER + "0.5" + + AdventureSoundSerializer.DELIMITER + "2.0"; Sound sound = serializer.deserialize(input); assertEquals(Key.key("minecraft", "test"), sound.name()); @@ -45,9 +45,9 @@ void testDeserializeWithPitchAndVolume() { @Test void testDeserializeFull() { - String input = "minecraft:test" + AdventureSoundSerializer.DELIMINATOR + "0.8" - + AdventureSoundSerializer.DELIMINATOR + "0.5" - + AdventureSoundSerializer.DELIMINATOR + "MUSIC"; + String input = "minecraft:test" + AdventureSoundSerializer.DELIMITER + "0.8" + + AdventureSoundSerializer.DELIMITER + "0.5" + + AdventureSoundSerializer.DELIMITER + "MUSIC"; Sound sound = serializer.deserialize(input); assertEquals(Key.key("minecraft", "test"), sound.name()); @@ -58,7 +58,7 @@ void testDeserializeFull() { @Test void testDeserializeComplexKey() { - String input = "minecraft:complex.key.name" + AdventureSoundSerializer.DELIMINATOR + "1.2"; + String input = "minecraft:complex.key.name" + AdventureSoundSerializer.DELIMITER + "1.2"; Sound sound = serializer.deserialize(input); assertEquals(Key.key("minecraft", "complex.key.name"), sound.name()); @@ -67,7 +67,7 @@ void testDeserializeComplexKey() { @Test void testDeserializeAmbiguousKeyLookingLikeFloat() { - String input = "custom" + AdventureSoundSerializer.DELIMINATOR + "1.5"; + String input = "custom" + AdventureSoundSerializer.DELIMITER + "1.5"; Sound sound = serializer.deserialize(input); assertEquals(Key.key("minecraft", "custom"), sound.name()); assertEquals(1.5f, sound.pitch()); @@ -75,7 +75,7 @@ void testDeserializeAmbiguousKeyLookingLikeFloat() { @Test void testDeserializeAmbiguousKeyLookingLikeSource() { - String input = "custom" + AdventureSoundSerializer.DELIMINATOR + "MUSIC"; + String input = "custom" + AdventureSoundSerializer.DELIMITER + "MUSIC"; Sound sound = serializer.deserialize(input); assertEquals(Key.key("minecraft", "custom"), sound.name()); assertEquals(Sound.Source.MUSIC, sound.source()); diff --git a/configlib-core/src/test/java/de/exlll/configlib/SerializersTest.java b/configlib-core/src/test/java/de/exlll/configlib/SerializersTest.java index b225c87..54e08cf 100644 --- a/configlib-core/src/test/java/de/exlll/configlib/SerializersTest.java +++ b/configlib-core/src/test/java/de/exlll/configlib/SerializersTest.java @@ -1216,7 +1216,7 @@ void deserializeThrowsForObjectsIfObjectToStringDeserializationTypeNotAdded() { assertThrowsConfigurationException( () -> serializer.deserialize(new File(TMP_CONFIG_PATH)), - buildExceptionMessage("File", TestUtils.createPlatformSpecificFilePath("/tmp/config.yml"), + buildExceptionMessage("File", TMP_CONFIG_PATH, "OBJECT_TO_STRING") ); @@ -1277,7 +1277,7 @@ void deserializeObjectToStringIfDeserializationTypeAdded() { assertThat( serializer.deserialize(new File(TMP_CONFIG_PATH)), - is(TestUtils.createPlatformSpecificFilePath("/tmp/config.yml")) + is(TMP_CONFIG_PATH) ); assertThat( serializer.deserialize(URI.create("https://example.com")), From e016dad6f25ed49fae4bb446d79ace06e494eb69 Mon Sep 17 00:00:00 2001 From: zvyap Date: Tue, 3 Feb 2026 16:00:02 +0800 Subject: [PATCH 18/18] Add test and fix TranslatableComponent round trip issue --- .../AdventureComponentSerializer.java | 14 +++++--- .../exlll/configlib/AdventureConfigLib.java | 11 +++--- .../configlib/AdventureComponentTests.java | 36 ++++++++++++++++++- 3 files changed, 51 insertions(+), 10 deletions(-) diff --git a/configlib-adventure/src/main/java/de/exlll/configlib/AdventureComponentSerializer.java b/configlib-adventure/src/main/java/de/exlll/configlib/AdventureComponentSerializer.java index 63106ed..878395d 100644 --- a/configlib-adventure/src/main/java/de/exlll/configlib/AdventureComponentSerializer.java +++ b/configlib-adventure/src/main/java/de/exlll/configlib/AdventureComponentSerializer.java @@ -1,6 +1,7 @@ package de.exlll.configlib; import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.TranslatableComponent; import net.kyori.adventure.text.minimessage.MiniMessage; import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer; import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer; @@ -81,19 +82,24 @@ private String serialize(Component component, AdventureComponentFormat format) { LegacyComponentSerializer.legacySection().serialize(component); case MINECRAFT_JSON -> GsonComponentSerializer.gson().serialize(component); case TRANSLATION_KEY -> - PlainTextComponentSerializer.plainText().serialize(component); + component instanceof TranslatableComponent translatableComponent + ? translatableComponent.key() + : PlainTextComponentSerializer.plainText().serialize(component); }; } private Component deserialize(String string, AdventureComponentFormat format) { return switch (format) { - case MINI_MESSAGE -> MiniMessage.miniMessage().deserialize(string); + case MINI_MESSAGE -> + MiniMessage.miniMessage().deserialize(string); case LEGACY_AMPERSAND -> LegacyComponentSerializer.legacyAmpersand().deserialize(string); case LEGACY_SECTION -> LegacyComponentSerializer.legacySection().deserialize(string); - case MINECRAFT_JSON -> GsonComponentSerializer.gson().deserialize(string); - case TRANSLATION_KEY -> Component.translatable(string); + case MINECRAFT_JSON -> + GsonComponentSerializer.gson().deserialize(string); + case TRANSLATION_KEY -> + Component.translatable(string); }; } } diff --git a/configlib-adventure/src/main/java/de/exlll/configlib/AdventureConfigLib.java b/configlib-adventure/src/main/java/de/exlll/configlib/AdventureConfigLib.java index 876c6db..5bf4997 100644 --- a/configlib-adventure/src/main/java/de/exlll/configlib/AdventureConfigLib.java +++ b/configlib-adventure/src/main/java/de/exlll/configlib/AdventureConfigLib.java @@ -27,7 +27,8 @@ private AdventureConfigLib() { * @return the builder with default serializers added */ public static > - ConfigurationProperties.Builder addDefaults(B builder) { + ConfigurationProperties.Builder addDefaults( + ConfigurationProperties.Builder builder) { return addDefaults(builder, DEFAULT_FORMAT_ORDER, DEFAULT_FORMAT_ORDER); } @@ -44,10 +45,10 @@ ConfigurationProperties.Builder addDefaults(B builder) { * @return the builder with default serializers added */ public static > - ConfigurationProperties.Builder addDefaults( - B builder, - List serializeOrder, - List deserializeOrder) { + ConfigurationProperties.Builder addDefaults( + ConfigurationProperties.Builder builder, + List serializeOrder, + List deserializeOrder) { builder.addSerializer(Component.class, new AdventureComponentSerializer(serializeOrder, deserializeOrder)); builder.addSerializer(Key.class, new AdventureKeySerializer()); diff --git a/configlib-adventure/src/test/java/de/exlll/configlib/AdventureComponentTests.java b/configlib-adventure/src/test/java/de/exlll/configlib/AdventureComponentTests.java index 85b62fe..586540b 100644 --- a/configlib-adventure/src/test/java/de/exlll/configlib/AdventureComponentTests.java +++ b/configlib-adventure/src/test/java/de/exlll/configlib/AdventureComponentTests.java @@ -2,6 +2,7 @@ import com.google.common.jimfs.Jimfs; import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.TranslatableComponent; import net.kyori.adventure.text.format.NamedTextColor; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; @@ -15,7 +16,7 @@ import java.util.List; import static de.exlll.configlib.TestUtils.createPlatformSpecificFilePath; -import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.*; class AdventureComponentTests { private FileSystem fs; @@ -35,6 +36,39 @@ void tearDown() throws IOException { } } + @Test + void testTranslatableComponentRoundTrip() { + AdventureComponentSerializer serializer = new AdventureComponentSerializer( + AdventureComponentFormat.TRANSLATION_KEY); + + TranslatableComponent original = Component.translatable("item.minecraft.diamond_sword"); + TranslatableComponent originalWithDefaultMessage = Component.translatable( + "item.minecraft.diamond_sword", + "Default Message"); + + String serialized = serializer.serialize(original); + + String serializedWithDefaultMessage = + serializer.serialize(originalWithDefaultMessage); + + // Deserialize and validate returned TranslatableComponent + TranslatableComponent deserialized = + assertInstanceOf(TranslatableComponent.class, + serializer.deserialize(serialized)); + TranslatableComponent deserializedWithDefaultMessage = + assertInstanceOf(TranslatableComponent.class, + serializer.deserialize(serializedWithDefaultMessage)); + + // Basic round-trip equality check + assertEquals(original, deserialized, + "Deserialized component does not match the original"); + + // The default message should not be preserved in this format + assertNotEquals(originalWithDefaultMessage, deserializedWithDefaultMessage, + "Deserialized component should not match the original with default message"); + assertEquals(originalWithDefaultMessage.key(), deserializedWithDefaultMessage.key()); + } + @Test void testMultipleFormats() { List baseFormats = new ArrayList<>(List.of(