From 87b767245292d98c0950e21f48c8d4a60ce48173 Mon Sep 17 00:00:00 2001 From: Kieran Wallbanks Date: Mon, 26 Feb 2024 13:49:43 +0000 Subject: [PATCH] fix: Allow for case-insensitivity in legacy serializer, closes #1043 --- .../serializer/legacy/CharacterAndFormat.java | 80 ++++++++++++------- .../legacy/CharacterAndFormatImpl.java | 13 ++- .../legacy/CharacterAndFormatSet.java | 28 ++++++- .../legacy/LegacyComponentSerializerTest.java | 24 ++++++ 4 files changed, 112 insertions(+), 33 deletions(-) diff --git a/text-serializer-legacy/src/main/java/net/kyori/adventure/text/serializer/legacy/CharacterAndFormat.java b/text-serializer-legacy/src/main/java/net/kyori/adventure/text/serializer/legacy/CharacterAndFormat.java index 8bcc0a2e8..634d72846 100644 --- a/text-serializer-legacy/src/main/java/net/kyori/adventure/text/serializer/legacy/CharacterAndFormat.java +++ b/text-serializer-legacy/src/main/java/net/kyori/adventure/text/serializer/legacy/CharacterAndFormat.java @@ -35,7 +35,7 @@ import org.jetbrains.annotations.Unmodifiable; /** - * A combination of a {@code character} and a {@link TextFormat}. + * A combination of a {@code character}, a {@link TextFormat}, and if the character is {@link #caseInsensitive()}. * * @since 4.14.0 */ @@ -46,152 +46,165 @@ public interface CharacterAndFormat extends Examinable { * * @since 4.14.0 */ - CharacterAndFormat BLACK = characterAndFormat('0', NamedTextColor.BLACK); + CharacterAndFormat BLACK = characterAndFormat('0', NamedTextColor.BLACK, true); /** * Character and format pair representing {@link NamedTextColor#DARK_BLUE}. * * @since 4.14.0 */ - CharacterAndFormat DARK_BLUE = characterAndFormat('1', NamedTextColor.DARK_BLUE); + CharacterAndFormat DARK_BLUE = characterAndFormat('1', NamedTextColor.DARK_BLUE, true); /** * Character and format pair representing {@link NamedTextColor#DARK_GREEN}. * * @since 4.14.0 */ - CharacterAndFormat DARK_GREEN = characterAndFormat('2', NamedTextColor.DARK_GREEN); + CharacterAndFormat DARK_GREEN = characterAndFormat('2', NamedTextColor.DARK_GREEN, true); /** * Character and format pair representing {@link NamedTextColor#DARK_AQUA}. * * @since 4.14.0 */ - CharacterAndFormat DARK_AQUA = characterAndFormat('3', NamedTextColor.DARK_AQUA); + CharacterAndFormat DARK_AQUA = characterAndFormat('3', NamedTextColor.DARK_AQUA, true); /** * Character and format pair representing {@link NamedTextColor#DARK_RED}. * * @since 4.14.0 */ - CharacterAndFormat DARK_RED = characterAndFormat('4', NamedTextColor.DARK_RED); + CharacterAndFormat DARK_RED = characterAndFormat('4', NamedTextColor.DARK_RED, true); /** * Character and format pair representing {@link NamedTextColor#DARK_PURPLE}. * * @since 4.14.0 */ - CharacterAndFormat DARK_PURPLE = characterAndFormat('5', NamedTextColor.DARK_PURPLE); + CharacterAndFormat DARK_PURPLE = characterAndFormat('5', NamedTextColor.DARK_PURPLE, true); /** * Character and format pair representing {@link NamedTextColor#GOLD}. * * @since 4.14.0 */ - CharacterAndFormat GOLD = characterAndFormat('6', NamedTextColor.GOLD); + CharacterAndFormat GOLD = characterAndFormat('6', NamedTextColor.GOLD, true); /** * Character and format pair representing {@link NamedTextColor#GRAY}. * * @since 4.14.0 */ - CharacterAndFormat GRAY = characterAndFormat('7', NamedTextColor.GRAY); + CharacterAndFormat GRAY = characterAndFormat('7', NamedTextColor.GRAY, true); /** * Character and format pair representing {@link NamedTextColor#DARK_GRAY}. * * @since 4.14.0 */ - CharacterAndFormat DARK_GRAY = characterAndFormat('8', NamedTextColor.DARK_GRAY); + CharacterAndFormat DARK_GRAY = characterAndFormat('8', NamedTextColor.DARK_GRAY, true); /** * Character and format pair representing {@link NamedTextColor#BLUE}. * * @since 4.14.0 */ - CharacterAndFormat BLUE = characterAndFormat('9', NamedTextColor.BLUE); + CharacterAndFormat BLUE = characterAndFormat('9', NamedTextColor.BLUE, true); /** * Character and format pair representing {@link NamedTextColor#GREEN}. * * @since 4.14.0 */ - CharacterAndFormat GREEN = characterAndFormat('a', NamedTextColor.GREEN); + CharacterAndFormat GREEN = characterAndFormat('a', NamedTextColor.GREEN, true); /** * Character and format pair representing {@link NamedTextColor#AQUA}. * * @since 4.14.0 */ - CharacterAndFormat AQUA = characterAndFormat('b', NamedTextColor.AQUA); + CharacterAndFormat AQUA = characterAndFormat('b', NamedTextColor.AQUA, true); /** * Character and format pair representing {@link NamedTextColor#RED}. * * @since 4.14.0 */ - CharacterAndFormat RED = characterAndFormat('c', NamedTextColor.RED); + CharacterAndFormat RED = characterAndFormat('c', NamedTextColor.RED, true); /** * Character and format pair representing {@link NamedTextColor#LIGHT_PURPLE}. * * @since 4.14.0 */ - CharacterAndFormat LIGHT_PURPLE = characterAndFormat('d', NamedTextColor.LIGHT_PURPLE); + CharacterAndFormat LIGHT_PURPLE = characterAndFormat('d', NamedTextColor.LIGHT_PURPLE, true); /** * Character and format pair representing {@link NamedTextColor#YELLOW}. * * @since 4.14.0 */ - CharacterAndFormat YELLOW = characterAndFormat('e', NamedTextColor.YELLOW); + CharacterAndFormat YELLOW = characterAndFormat('e', NamedTextColor.YELLOW, true); /** * Character and format pair representing {@link NamedTextColor#WHITE}. * * @since 4.14.0 */ - CharacterAndFormat WHITE = characterAndFormat('f', NamedTextColor.WHITE); + CharacterAndFormat WHITE = characterAndFormat('f', NamedTextColor.WHITE, true); /** * Character and format pair representing {@link TextDecoration#OBFUSCATED}. * * @since 4.14.0 */ - CharacterAndFormat OBFUSCATED = characterAndFormat('k', TextDecoration.OBFUSCATED); + CharacterAndFormat OBFUSCATED = characterAndFormat('k', TextDecoration.OBFUSCATED, true); /** * Character and format pair representing {@link TextDecoration#BOLD}. * * @since 4.14.0 */ - CharacterAndFormat BOLD = characterAndFormat('l', TextDecoration.BOLD); + CharacterAndFormat BOLD = characterAndFormat('l', TextDecoration.BOLD, true); /** * Character and format pair representing {@link TextDecoration#STRIKETHROUGH}. * * @since 4.14.0 */ - CharacterAndFormat STRIKETHROUGH = characterAndFormat('m', TextDecoration.STRIKETHROUGH); + CharacterAndFormat STRIKETHROUGH = characterAndFormat('m', TextDecoration.STRIKETHROUGH, true); /** * Character and format pair representing {@link TextDecoration#UNDERLINED}. * * @since 4.14.0 */ - CharacterAndFormat UNDERLINED = characterAndFormat('n', TextDecoration.UNDERLINED); + CharacterAndFormat UNDERLINED = characterAndFormat('n', TextDecoration.UNDERLINED, true); /** * Character and format pair representing {@link TextDecoration#ITALIC}. * * @since 4.14.0 */ - CharacterAndFormat ITALIC = characterAndFormat('o', TextDecoration.ITALIC); + CharacterAndFormat ITALIC = characterAndFormat('o', TextDecoration.ITALIC, true); /** * Character and format pair representing {@link Reset#INSTANCE}. * * @since 4.14.0 */ - CharacterAndFormat RESET = characterAndFormat('r', Reset.INSTANCE); + CharacterAndFormat RESET = characterAndFormat('r', Reset.INSTANCE, true); /** - * Creates a new combination of a {@code character} and a {@link TextFormat}. + * Creates a new combination of a case-sensitive {@code character} and a {@link TextFormat}. * * @param character the character * @param format the format - * @return a new character and format pair. + * @return a new character and format instance. * @since 4.14.0 */ static @NotNull CharacterAndFormat characterAndFormat(final char character, final @NotNull TextFormat format) { - return new CharacterAndFormatImpl(character, format); + return characterAndFormat(character, format, false); } /** - * Gets an unmodifiable list of character and format pairs containing all default vanilla formats. + * Creates a new combination of a {@code character} and a {@link TextFormat}. * - * @return am unmodifiable list of character and format pairs containing all default vanilla formats + * @param character the character + * @param format the format + * @param caseInsensitive if the character is case-insensitive + * @return a new character and format instance. + * @since 4.17.0 + */ + static @NotNull CharacterAndFormat characterAndFormat(final char character, final @NotNull TextFormat format, final boolean caseInsensitive) { + return new CharacterAndFormatImpl(character, format, caseInsensitive); + } + + /** + * Gets an unmodifiable list of character and format instances containing all default vanilla formats. + * + * @return an unmodifiable list of character and format instances containing all default vanilla formats * @since 4.14.0 */ @Unmodifiable @@ -215,11 +228,20 @@ public interface CharacterAndFormat extends Examinable { */ @NotNull TextFormat format(); + /** + * If the {@link #character()} is case-insensitive. + * + * @return if the character is case-insensitive + * @since 4.17.0 + */ + boolean caseInsensitive(); + @Override default @NotNull Stream examinableProperties() { return Stream.of( ExaminableProperty.of("character", this.character()), - ExaminableProperty.of("format", this.format()) + ExaminableProperty.of("format", this.format()), + ExaminableProperty.of("caseInsensitive", this.caseInsensitive()) ); } } diff --git a/text-serializer-legacy/src/main/java/net/kyori/adventure/text/serializer/legacy/CharacterAndFormatImpl.java b/text-serializer-legacy/src/main/java/net/kyori/adventure/text/serializer/legacy/CharacterAndFormatImpl.java index 55bbe96a8..a3f50b0a2 100644 --- a/text-serializer-legacy/src/main/java/net/kyori/adventure/text/serializer/legacy/CharacterAndFormatImpl.java +++ b/text-serializer-legacy/src/main/java/net/kyori/adventure/text/serializer/legacy/CharacterAndFormatImpl.java @@ -36,10 +36,12 @@ final class CharacterAndFormatImpl implements CharacterAndFormat { private final char character; private final TextFormat format; + private final boolean caseInsensitive; - CharacterAndFormatImpl(final char character, final @NotNull TextFormat format) { + CharacterAndFormatImpl(final char character, final @NotNull TextFormat format, final boolean caseInsensitive) { this.character = character; this.format = requireNonNull(format, "format"); + this.caseInsensitive = caseInsensitive; } @Override @@ -52,19 +54,26 @@ public char character() { return this.format; } + @Override + public boolean caseInsensitive() { + return this.caseInsensitive; + } + @Override public boolean equals(final @Nullable Object other) { if (this == other) return true; if (!(other instanceof CharacterAndFormatImpl)) return false; final CharacterAndFormatImpl that = (CharacterAndFormatImpl) other; return this.character == that.character - && this.format.equals(that.format); + && this.format.equals(that.format) + && this.caseInsensitive == that.caseInsensitive; } @Override public int hashCode() { int result = this.character; result = 31 * result + this.format.hashCode(); + result = 31 * result + Boolean.hashCode(this.caseInsensitive); return result; } diff --git a/text-serializer-legacy/src/main/java/net/kyori/adventure/text/serializer/legacy/CharacterAndFormatSet.java b/text-serializer-legacy/src/main/java/net/kyori/adventure/text/serializer/legacy/CharacterAndFormatSet.java index e626eee83..4ffc66d4c 100644 --- a/text-serializer-legacy/src/main/java/net/kyori/adventure/text/serializer/legacy/CharacterAndFormatSet.java +++ b/text-serializer-legacy/src/main/java/net/kyori/adventure/text/serializer/legacy/CharacterAndFormatSet.java @@ -42,12 +42,36 @@ static CharacterAndFormatSet of(final List pairs) { final StringBuilder characters = new StringBuilder(size); for (int i = 0; i < size; i++) { final CharacterAndFormat pair = pairs.get(i); - characters.append(pair.character()); + final char character = pair.character(); final TextFormat format = pair.format(); + final boolean formatIsTextColor = format instanceof TextColor; + + // First, add the "standard" character. + characters.append(character); formats.add(format); - if (format instanceof TextColor) { + if (formatIsTextColor) { colors.add((TextColor) format); } + + // If the character is case-insensitive, we need to add the other character too. + if (pair.caseInsensitive()) { + boolean added = false; + + if (Character.isUpperCase(character)) { + characters.append(Character.toLowerCase(character)); + added = true; + } else if (Character.isLowerCase(character)) { + characters.append(Character.toUpperCase(character)); + added = true; + } + + if (added) { + formats.add(format); + if (formatIsTextColor) { + colors.add((TextColor) format); + } + } + } } if (formats.size() != characters.length()) { throw new IllegalStateException("formats length differs from characters length"); diff --git a/text-serializer-legacy/src/test/java/net/kyori/adventure/text/serializer/legacy/LegacyComponentSerializerTest.java b/text-serializer-legacy/src/test/java/net/kyori/adventure/text/serializer/legacy/LegacyComponentSerializerTest.java index a3f8c0b40..f1245a85a 100644 --- a/text-serializer-legacy/src/test/java/net/kyori/adventure/text/serializer/legacy/LegacyComponentSerializerTest.java +++ b/text-serializer-legacy/src/test/java/net/kyori/adventure/text/serializer/legacy/LegacyComponentSerializerTest.java @@ -24,6 +24,7 @@ package net.kyori.adventure.text.serializer.legacy; import java.util.ArrayList; +import java.util.Collections; import java.util.List; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.TextComponent; @@ -331,4 +332,27 @@ void testNullTextFormat() { final String serialized = serializer.serialize(strikethough); assertEquals(serialized, "Hello World"); } + + // https://github.com/KyoriPowered/adventure/issues/1043 + @Test + void testCaseInsensitivity() { + final Component expected = Component.text("pop4959", NamedTextColor.YELLOW); + final Component lowercaseActual = LegacyComponentSerializer.legacyAmpersand().deserialize("&epop4959"); + assertEquals(expected, lowercaseActual); + + final Component uppercaseActual = LegacyComponentSerializer.legacyAmpersand().deserialize("&Epop4959"); + assertEquals(expected, uppercaseActual); + } + + @Test + void testCaseSensitivity() { + final Component expected = Component.text("&Epop4959"); + final Component lowercaseActual = LegacyComponentSerializer + .legacyAmpersand() + .toBuilder() + .formats(Collections.singletonList(CharacterAndFormat.characterAndFormat('e', NamedTextColor.YELLOW))) + .build() + .deserialize("&Epop4959"); + assertEquals(expected, lowercaseActual); + } }