Skip to content

Conversation

@zvyap
Copy link

@zvyap zvyap commented Jan 28, 2026

This pull request addresses issue #29, which I opened previously, to add native support for the Adventure API Component.

Changes in this PR

  • Added a new module: configlib-adventure, shaded into the Paper and Velocity modules (since both natively support Adventure).
  • Implemented serializers for Component, Key, and Sound.
  • Added related tests (some were initially AI-generated, but I reviewed, modified, and approved them).
  • Added related Javadocs.
  • Updated readme

Notes

  • I noticed that running gradle test at the root or within any module only executed tests in the core module. I am not sure whether this behavior was intentional.
    I modified the behavior so that:

    • Running tests at the root executes all tests across all modules.
    • Running tests inside a specific library module runs only that module’s tests.
    • Running tests inside a plugin module continues to run core tests, as per the previous behavior.

    If the previous test configuration was intentional, please let me know and I will revert the changes accordingly.

  • I also fixed two failing tests in the core module that were caused by Windows-specific behavior (as shown in the screenshot). All core module tests now pass on Windows.

  • If you are not satisfied with the changes mentioned above, I am willing to revert or adjust them as needed.

@Emibergo02
Copy link

This is cool!

@Exlll Exlll self-assigned this Jan 30, 2026
@Exlll
Copy link
Owner

Exlll commented Jan 30, 2026

Hey, thanks a lot for your contribution! I have a lot of stuff going on in my private life lately but I will try to find some time this weekend to have a look at it.

Copy link
Owner

@Exlll Exlll left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Besides the things that I commented: I generally try to stay below 80 chars per line. 90 chars per line is a hard limit except for some edge cases and in tests. Could you please reformat your code so that the line length is within these limits?
Also please prefix all top-level classes with Adventure, i.e. AdventureComponentSerializer, AdventureComponentFormat, etc.
Overall pretty nice work 👍

// Hack to avoid compiler error while singleton pattern initialization
private static class Patterns {
// Pattern to detect any <tag> in a string
static final Pattern MINI_MESSAGE_PATTERN = Pattern.compile("<[^>]+>");
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This pattern is overly broad and matches strings like < gre eeen > and 3 < 4 and 4 > 2.

*/
public enum ComponentFormat {
/** MiniMessage format with tags like {@code <red>} or {@code <bold>}. */
MINI_MESSAGE(input -> Patterns.MINI_MESSAGE_PATTERN.matcher(input).find()),
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can just use Pattern.compile("...").asPredicate()

* @param formats the formats to use, in order of preference
*/
public ComponentSerializer(ComponentFormat... formats) {
this(Arrays.asList(formats), Arrays.asList(formats));
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You should make a defensive copy here, too. Calling asList is not enough.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I already called List.copyOf in another constructor. This constructor just converts the array to a List to pass it to the other constructor. or I miss understanding here?

Comment on lines +47 to +52
try {
return serialize(element, format);
} catch (Exception ignored) {
// This should never happen, but in case of adventure library issue
ignored.printStackTrace();
}
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't do this.

Comment on lines +70 to +75
try {
return deserialize(element, format);
} catch (Exception ignored) {
// This should never happen, but in case of adventure library issue
ignored.printStackTrace();
}
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't do this.

Comment on lines +43 to +45
// Key.key("default", "namespace:value") will throw InvalidKeyException because
// ':' is not allowed in value
assertThrows(Exception.class, () -> serializer.deserialize("namespace:value"));
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't expect Exception but InvalidKeyException in this case

assertThat(
serializer.deserialize(new File(TMP_CONFIG_PATH)),
is("/tmp/config.yml")
is(TestUtils.isWindows() ? "C:\\tmp\\config.yml" : "/tmp/config.yml")
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for fixing this. You can use TestUtils.createPlatformSpecificFilePath here.

Comment on lines +8 to +9
@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" })
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please restore the original format.

@Override
public String serialize(Sound element) {
StringBuilder builder = new StringBuilder(element.name().asString());
if (element.source() != null) {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This if statement is always true.

Comment on lines +54 to +95
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);
}
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This parsing logic seems overly complex. Perhaps you can replace this with a Pattern? Something like this:

Pattern pattern = Pattern.compile(
        """
        ^(?<key>[a-zA-Z:]+)\
        (?:\\s+(?<volume>\\d+(?:\\.\\d+)?)?)?\
        (?:\\s+(?<pitch>\\d+(?:\\.\\d+)?)?)?\
        (?:\\s+(?<source>MASTER|AMBIENT|MUSIC)?)?\
        \\s*$\
        """);
Matcher matcher = pattern.matcher("minecraft 0.4 MASTER");
System.out.println(matcher.matches());
System.out.println(matcher.group("key"));
System.out.println(matcher.group("volume"));
System.out.println(matcher.group("pitch"));
System.out.println(matcher.group("source"));

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants