From 4186ee149c1b152f06d507e91e0c7d435170f42b Mon Sep 17 00:00:00 2001 From: Nassim Jahnke Date: Thu, 27 Feb 2025 21:50:57 +0100 Subject: [PATCH 01/86] feat(nbt): update number parsing for 1.21.5 --- .../kyori/adventure/nbt/TagStringReader.java | 121 ++++++++++++++---- .../java/net/kyori/adventure/nbt/Tokens.java | 22 ++-- .../net/kyori/adventure/nbt/StringIOTest.java | 13 ++ 3 files changed, 125 insertions(+), 31 deletions(-) diff --git a/nbt/src/main/java/net/kyori/adventure/nbt/TagStringReader.java b/nbt/src/main/java/net/kyori/adventure/nbt/TagStringReader.java index ad847870c0..07b0b21825 100644 --- a/nbt/src/main/java/net/kyori/adventure/nbt/TagStringReader.java +++ b/nbt/src/main/java/net/kyori/adventure/nbt/TagStringReader.java @@ -30,6 +30,9 @@ final class TagStringReader { private static final int MAX_DEPTH = 512; + private static final int HEX_RADIX = 16; + private static final int BINARY_RADIX = 2; + private static final int DECIMAL_RADIX = 10; private static final byte[] EMPTY_BYTE_ARRAY = new byte[0]; private static final int[] EMPTY_INT_ARRAY = new int[0]; private static final long[] EMPTY_LONG_ARRAY = new long[0]; @@ -238,9 +241,8 @@ public BinaryTag tag() throws StringTagParseException { * * @return a parsed tag */ - private BinaryTag scalar() { + private BinaryTag scalar() throws StringTagParseException { final StringBuilder builder = new StringBuilder(); - int noLongerNumericAt = -1; while (this.buffer.hasMore()) { char current = this.buffer.peek(); if (current == '\\') { // escape -- we are significantly more lenient than original format at the moment @@ -252,33 +254,79 @@ private BinaryTag scalar() { break; } builder.append(current); - if (noLongerNumericAt == -1 && !Tokens.numeric(current)) { - noLongerNumericAt = builder.length(); + } + if (builder.length() == 0) { + throw this.buffer.makeError("Expected a value but got nothing"); + } + final String original = builder.toString(); // use unmodified string when number parsing fails + + // Start stripping down the string so we can use Java's number parsing instead of having to write our own. + // Determine the radix and strip its prefix if present + final char first = builder.charAt(0); + int radixPrefixOffset = 0; + final int radix; + if (first == '+' || first == '-') { + radixPrefixOffset = 1; + } + if (original.startsWith("0b", radixPrefixOffset) || original.startsWith("0B", radixPrefixOffset)) { + radix = BINARY_RADIX; + } else if (original.startsWith("0x", radixPrefixOffset) || original.startsWith("0X", radixPrefixOffset)) { + radix = HEX_RADIX; + } else { + radix = DECIMAL_RADIX; + } + if (radix != DECIMAL_RADIX) { + builder.delete(radixPrefixOffset, 2 + radixPrefixOffset); + } + + final char last = builder.charAt(builder.length() - 1); + boolean hasTypeToken = Tokens.numericType(last); + char typeToken = hasTypeToken ? Character.toLowerCase(last) : Tokens.TYPE_INT; + boolean hasSignToken = false; + boolean signed = radix != HEX_RADIX; // hex defaults to unsigned + + // Check for the sign before removing the type token because of hex number always needing a sign thanks to byte types + if (builder.length() > 2) { + final char signChar = builder.charAt(builder.length() - 2); + if (signChar == Tokens.TYPE_SIGNED || signChar == Tokens.TYPE_UNSIGNED) { + hasSignToken = true; + signed = signChar == Tokens.TYPE_SIGNED; + builder.deleteCharAt(builder.length() - 2); } } + if (hasTypeToken) { + if (!hasSignToken && radix == HEX_RADIX) { + // We fell into the hex trap! + hasTypeToken = false; + typeToken = Tokens.TYPE_INT; + } else { + builder.deleteCharAt(builder.length() - 1); + } + } + if (!signed && (typeToken == Tokens.TYPE_FLOAT || typeToken == Tokens.TYPE_DOUBLE)) { + throw this.buffer.makeError("Cannot create unsigned floating point numbers"); + } - final int length = builder.length(); - final String built = builder.toString(); - if (noLongerNumericAt == length && length > 1) { - final char last = built.charAt(length - 1); + final String strippedString = builder.toString().replace("_", ""); + if (hasTypeToken) { try { - switch (Character.toLowerCase(last)) { // try to read and return as a number + switch (typeToken) { case Tokens.TYPE_BYTE: - return ByteBinaryTag.byteBinaryTag(Byte.parseByte(built.substring(0, length - 1))); + return ByteBinaryTag.byteBinaryTag(this.parseByte(strippedString, radix, signed)); case Tokens.TYPE_SHORT: - return ShortBinaryTag.shortBinaryTag(Short.parseShort(built.substring(0, length - 1))); + return ShortBinaryTag.shortBinaryTag(this.parseShort(strippedString, radix, signed)); case Tokens.TYPE_INT: - return IntBinaryTag.intBinaryTag(Integer.parseInt(built.substring(0, length - 1))); + return IntBinaryTag.intBinaryTag(this.parseInt(strippedString, radix, signed)); case Tokens.TYPE_LONG: - return LongBinaryTag.longBinaryTag(Long.parseLong(built.substring(0, length - 1))); + return LongBinaryTag.longBinaryTag(this.parseLong(strippedString, radix, signed)); case Tokens.TYPE_FLOAT: - final float floatValue = Float.parseFloat(built.substring(0, length - 1)); + final float floatValue = Float.parseFloat(strippedString); if (Float.isFinite(floatValue)) { // don't accept NaN and Infinity return FloatBinaryTag.floatBinaryTag(floatValue); } break; case Tokens.TYPE_DOUBLE: - final double doubleValue = Double.parseDouble(built.substring(0, length - 1)); + final double doubleValue = Double.parseDouble(strippedString); if (Double.isFinite(doubleValue)) { // don't accept NaN and Infinity return DoubleBinaryTag.doubleBinaryTag(doubleValue); } @@ -287,13 +335,13 @@ private BinaryTag scalar() { } catch (final NumberFormatException ignored) { // not a numeric tag of the appropriate type } - } else if (noLongerNumericAt == -1) { // if we run out of content without an explicit value separator, then we're either an integer or string tag -- all others have a character at the end + } else { // default to int or double parsing before falling back to string try { - return IntBinaryTag.intBinaryTag(Integer.parseInt(built)); + return IntBinaryTag.intBinaryTag(this.parseInt(strippedString, radix, signed)); } catch (final NumberFormatException ex) { - if (built.indexOf('.') != -1) { // see if we have an unsuffixed double; always needs a dot + if (strippedString.indexOf('.') != -1) { // see if we have an unsuffixed double; always needs a dot try { - return DoubleBinaryTag.doubleBinaryTag(Double.parseDouble(built)); + return DoubleBinaryTag.doubleBinaryTag(Double.parseDouble(strippedString)); } catch (final NumberFormatException ex2) { // ignore } @@ -301,13 +349,42 @@ private BinaryTag scalar() { } } - if (built.equalsIgnoreCase(Tokens.LITERAL_TRUE)) { + if (original.equalsIgnoreCase(Tokens.LITERAL_TRUE)) { return ByteBinaryTag.ONE; - } else if (built.equalsIgnoreCase(Tokens.LITERAL_FALSE)) { + } else if (original.equalsIgnoreCase(Tokens.LITERAL_FALSE)) { return ByteBinaryTag.ZERO; } - return StringBinaryTag.stringBinaryTag(built); + return StringBinaryTag.stringBinaryTag(original); + } + + private byte parseByte(final String s, final int radix, final boolean signed) { + if (signed) { + return Byte.parseByte(s, radix); + } + final int parsedInt = Integer.parseInt(s, radix); + if (parsedInt >> Byte.SIZE == 0) { + return (byte) parsedInt; + } + throw new NumberFormatException(); + } + + private short parseShort(final String s, final int radix, final boolean signed) { + if (signed) { + return Short.parseShort(s, radix); + } + final int parsedInt = Integer.parseInt(s, radix); + if (parsedInt >> Short.SIZE == 0) { + return (short) parsedInt; + } + throw new NumberFormatException(); + } + + private int parseInt(final String s, final int radix, final boolean signed) { + return signed ? Integer.parseInt(s, radix) : Integer.parseUnsignedInt(s, radix); + } + private long parseLong(final String s, final int radix, final boolean signed) { + return signed ? Long.parseLong(s, radix) : Long.parseUnsignedLong(s, radix); } private boolean separatorOrCompleteWith(final char endCharacter) throws StringTagParseException { diff --git a/nbt/src/main/java/net/kyori/adventure/nbt/Tokens.java b/nbt/src/main/java/net/kyori/adventure/nbt/Tokens.java index 5a34fffe28..9d4f43d344 100644 --- a/nbt/src/main/java/net/kyori/adventure/nbt/Tokens.java +++ b/nbt/src/main/java/net/kyori/adventure/nbt/Tokens.java @@ -47,6 +47,9 @@ final class Tokens { static final char TYPE_FLOAT = 'f'; static final char TYPE_DOUBLE = 'd'; + static final char TYPE_SIGNED = 's'; + static final char TYPE_UNSIGNED = 'u'; + static final String LITERAL_TRUE = "true"; static final String LITERAL_FALSE = "false"; @@ -73,17 +76,18 @@ static boolean id(final char c) { } /** - * Return whether a character could be at some position in a number. - * - *

A string passing this check does not necessarily mean it is syntactically valid.

+ * Return whether a character is a numeric type identifier. * * @param c character to check - * @return if possibly part of a number + * @return if a numeric type identifier */ - static boolean numeric(final char c) { - return (c >= '0' && c <= '9') // digit - || c == '+' || c == '-' // positive or negative - || c == 'e' || c == 'E' // exponent - || c == '.'; // decimal + static boolean numericType(char c) { + c = Character.toLowerCase(c); + return c == TYPE_BYTE + || c == TYPE_SHORT + || c == TYPE_INT + || c == TYPE_LONG + || c == TYPE_FLOAT + || c == TYPE_DOUBLE; } } diff --git a/nbt/src/test/java/net/kyori/adventure/nbt/StringIOTest.java b/nbt/src/test/java/net/kyori/adventure/nbt/StringIOTest.java index 4c0f3a8d2a..1ebe772078 100644 --- a/nbt/src/test/java/net/kyori/adventure/nbt/StringIOTest.java +++ b/nbt/src/test/java/net/kyori/adventure/nbt/StringIOTest.java @@ -153,7 +153,17 @@ void testIntTag() throws IOException { assertEquals("448228", this.tagToString(IntBinaryTag.intBinaryTag(448228))); assertEquals(IntBinaryTag.intBinaryTag(4482828), this.stringToTag("4482828")); + assertEquals(IntBinaryTag.intBinaryTag(4482828), this.stringToTag("4_4_8______2_8_2_8")); assertEquals(IntBinaryTag.intBinaryTag(-24), this.stringToTag("-24")); + assertEquals(IntBinaryTag.intBinaryTag(0xABC), this.stringToTag("0xABC")); + assertEquals(IntBinaryTag.intBinaryTag(0b1001), this.stringToTag("0b1001")); + } + + @Test + void testNumberSign() throws IOException { + assertEquals(ByteBinaryTag.byteBinaryTag((byte) -16), this.stringToTag("240ub")); + assertEquals(ByteBinaryTag.byteBinaryTag((byte) -16), this.stringToTag("-16sb")); + assertEquals(IntBinaryTag.intBinaryTag(-0xABC), this.stringToTag("-0xABCsI")); } @Test @@ -180,6 +190,7 @@ void testFloatTag() throws IOException { assertEquals(FloatBinaryTag.floatBinaryTag(-4.3e-4f), this.stringToTag("-4.3e-4F")); assertEquals(FloatBinaryTag.floatBinaryTag(4.3e-4f), this.stringToTag("+4.3e-4F")); assertEquals(FloatBinaryTag.floatBinaryTag(0.3f), this.stringToTag(".3F")); + assertEquals(FloatBinaryTag.floatBinaryTag(3.0f), this.stringToTag("3.F")); } @Test @@ -190,6 +201,8 @@ void testDoubleTag() throws IOException { assertEquals(DoubleBinaryTag.doubleBinaryTag(4.3e-4d), this.stringToTag("4.3e-4d")); assertEquals(DoubleBinaryTag.doubleBinaryTag(-4.3e-4d), this.stringToTag("-4.3e-4D")); assertEquals(DoubleBinaryTag.doubleBinaryTag(4.3e-4d), this.stringToTag("+4.3e-4D")); + assertEquals(DoubleBinaryTag.doubleBinaryTag(3.0d), this.stringToTag("3.")); + assertEquals(DoubleBinaryTag.doubleBinaryTag(0.3d), this.stringToTag(".3")); } @Test From 4f0a3c1ad7ebece7bbedf780a5d6e646a8d01dd5 Mon Sep 17 00:00:00 2001 From: Nassim Jahnke Date: Thu, 27 Feb 2025 22:22:43 +0100 Subject: [PATCH 02/86] chore: move larger chunks of code into separate methods --- .../kyori/adventure/nbt/TagStringReader.java | 110 ++++++++++-------- 1 file changed, 61 insertions(+), 49 deletions(-) diff --git a/nbt/src/main/java/net/kyori/adventure/nbt/TagStringReader.java b/nbt/src/main/java/net/kyori/adventure/nbt/TagStringReader.java index 07b0b21825..00b447540c 100644 --- a/nbt/src/main/java/net/kyori/adventure/nbt/TagStringReader.java +++ b/nbt/src/main/java/net/kyori/adventure/nbt/TagStringReader.java @@ -27,6 +27,7 @@ import java.util.List; import java.util.stream.IntStream; import java.util.stream.LongStream; +import org.jetbrains.annotations.Nullable; final class TagStringReader { private static final int MAX_DEPTH = 512; @@ -262,30 +263,12 @@ private BinaryTag scalar() throws StringTagParseException { // Start stripping down the string so we can use Java's number parsing instead of having to write our own. // Determine the radix and strip its prefix if present - final char first = builder.charAt(0); - int radixPrefixOffset = 0; - final int radix; - if (first == '+' || first == '-') { - radixPrefixOffset = 1; - } - if (original.startsWith("0b", radixPrefixOffset) || original.startsWith("0B", radixPrefixOffset)) { - radix = BINARY_RADIX; - } else if (original.startsWith("0x", radixPrefixOffset) || original.startsWith("0X", radixPrefixOffset)) { - radix = HEX_RADIX; - } else { - radix = DECIMAL_RADIX; - } - if (radix != DECIMAL_RADIX) { - builder.delete(radixPrefixOffset, 2 + radixPrefixOffset); - } + final int radix = this.extractRadix(builder, original); + // Check for the sign before removing the type token because of hex number always needing a sign thanks to byte types final char last = builder.charAt(builder.length() - 1); - boolean hasTypeToken = Tokens.numericType(last); - char typeToken = hasTypeToken ? Character.toLowerCase(last) : Tokens.TYPE_INT; boolean hasSignToken = false; boolean signed = radix != HEX_RADIX; // hex defaults to unsigned - - // Check for the sign before removing the type token because of hex number always needing a sign thanks to byte types if (builder.length() > 2) { final char signChar = builder.charAt(builder.length() - 2); if (signChar == Tokens.TYPE_SIGNED || signChar == Tokens.TYPE_UNSIGNED) { @@ -294,15 +277,16 @@ private BinaryTag scalar() throws StringTagParseException { builder.deleteCharAt(builder.length() - 2); } } - if (hasTypeToken) { - if (!hasSignToken && radix == HEX_RADIX) { - // We fell into the hex trap! - hasTypeToken = false; - typeToken = Tokens.TYPE_INT; - } else { - builder.deleteCharAt(builder.length() - 1); - } + + // Check for the type token and make sure we didn't fall into the hex trap (e.g. 0xAB) + boolean hasTypeToken = false; + char typeToken = Tokens.TYPE_INT; + if (Tokens.numericType(last) && (hasSignToken || radix != HEX_RADIX)) { + hasTypeToken = true; + typeToken = Character.toLowerCase(last); + builder.deleteCharAt(builder.length() - 1); } + if (!signed && (typeToken == Tokens.TYPE_FLOAT || typeToken == Tokens.TYPE_DOUBLE)) { throw this.buffer.makeError("Cannot create unsigned floating point numbers"); } @@ -310,27 +294,9 @@ private BinaryTag scalar() throws StringTagParseException { final String strippedString = builder.toString().replace("_", ""); if (hasTypeToken) { try { - switch (typeToken) { - case Tokens.TYPE_BYTE: - return ByteBinaryTag.byteBinaryTag(this.parseByte(strippedString, radix, signed)); - case Tokens.TYPE_SHORT: - return ShortBinaryTag.shortBinaryTag(this.parseShort(strippedString, radix, signed)); - case Tokens.TYPE_INT: - return IntBinaryTag.intBinaryTag(this.parseInt(strippedString, radix, signed)); - case Tokens.TYPE_LONG: - return LongBinaryTag.longBinaryTag(this.parseLong(strippedString, radix, signed)); - case Tokens.TYPE_FLOAT: - final float floatValue = Float.parseFloat(strippedString); - if (Float.isFinite(floatValue)) { // don't accept NaN and Infinity - return FloatBinaryTag.floatBinaryTag(floatValue); - } - break; - case Tokens.TYPE_DOUBLE: - final double doubleValue = Double.parseDouble(strippedString); - if (Double.isFinite(doubleValue)) { // don't accept NaN and Infinity - return DoubleBinaryTag.doubleBinaryTag(doubleValue); - } - break; + final NumberBinaryTag tag = this.parseNumberTag(strippedString, typeToken, radix, signed); + if (tag != null) { + return tag; } } catch (final NumberFormatException ignored) { // not a numeric tag of the appropriate type @@ -357,6 +323,52 @@ private BinaryTag scalar() throws StringTagParseException { return StringBinaryTag.stringBinaryTag(original); } + private int extractRadix(final StringBuilder builder, final String original) { + int radixPrefixOffset = 0; + final int radix; + final char first = builder.charAt(0); + if (first == '+' || first == '-') { + radixPrefixOffset = 1; + } + if (original.startsWith("0b", radixPrefixOffset) || original.startsWith("0B", radixPrefixOffset)) { + radix = BINARY_RADIX; + } else if (original.startsWith("0x", radixPrefixOffset) || original.startsWith("0X", radixPrefixOffset)) { + radix = HEX_RADIX; + } else { + radix = DECIMAL_RADIX; + } + if (radix != DECIMAL_RADIX) { + builder.delete(radixPrefixOffset, 2 + radixPrefixOffset); + } + return radix; + } + + private @Nullable NumberBinaryTag parseNumberTag(final String s, final char typeToken, final int radix, final boolean signed) { + switch (typeToken) { + case Tokens.TYPE_BYTE: + return ByteBinaryTag.byteBinaryTag(this.parseByte(s, radix, signed)); + case Tokens.TYPE_SHORT: + return ShortBinaryTag.shortBinaryTag(this.parseShort(s, radix, signed)); + case Tokens.TYPE_INT: + return IntBinaryTag.intBinaryTag(this.parseInt(s, radix, signed)); + case Tokens.TYPE_LONG: + return LongBinaryTag.longBinaryTag(this.parseLong(s, radix, signed)); + case Tokens.TYPE_FLOAT: + final float floatValue = Float.parseFloat(s); + if (Float.isFinite(floatValue)) { // don't accept NaN and Infinity + return FloatBinaryTag.floatBinaryTag(floatValue); + } + break; + case Tokens.TYPE_DOUBLE: + final double doubleValue = Double.parseDouble(s); + if (Double.isFinite(doubleValue)) { // don't accept NaN and Infinity + return DoubleBinaryTag.doubleBinaryTag(doubleValue); + } + break; + } + return null; + } + private byte parseByte(final String s, final int radix, final boolean signed) { if (signed) { return Byte.parseByte(s, radix); From ae39e6ad4d1f14a6f7f045cbb9ca6507618e9d1e Mon Sep 17 00:00:00 2001 From: GliczDev <67753196+GliczDev@users.noreply.github.com> Date: Tue, 11 Mar 2025 11:08:41 +0100 Subject: [PATCH 03/86] Add NumberBinaryTag#numberValue --- .../java/net/kyori/adventure/nbt/ByteBinaryTagImpl.java | 5 +++++ .../java/net/kyori/adventure/nbt/DoubleBinaryTagImpl.java | 5 +++++ .../java/net/kyori/adventure/nbt/FloatBinaryTagImpl.java | 5 +++++ .../java/net/kyori/adventure/nbt/IntBinaryTagImpl.java | 5 +++++ .../java/net/kyori/adventure/nbt/LongBinaryTagImpl.java | 5 +++++ .../java/net/kyori/adventure/nbt/NumberBinaryTag.java | 8 ++++++++ .../java/net/kyori/adventure/nbt/ShortBinaryTagImpl.java | 5 +++++ 7 files changed, 38 insertions(+) diff --git a/nbt/src/main/java/net/kyori/adventure/nbt/ByteBinaryTagImpl.java b/nbt/src/main/java/net/kyori/adventure/nbt/ByteBinaryTagImpl.java index 24020633e6..459bf6a3a0 100644 --- a/nbt/src/main/java/net/kyori/adventure/nbt/ByteBinaryTagImpl.java +++ b/nbt/src/main/java/net/kyori/adventure/nbt/ByteBinaryTagImpl.java @@ -72,6 +72,11 @@ public short shortValue() { return this.value; } + @Override + public Number numberValue() { + return this.value; + } + @Override public boolean equals(final @Nullable Object other) { if (this == other) return true; diff --git a/nbt/src/main/java/net/kyori/adventure/nbt/DoubleBinaryTagImpl.java b/nbt/src/main/java/net/kyori/adventure/nbt/DoubleBinaryTagImpl.java index 7e9093cf1d..b68b95a196 100644 --- a/nbt/src/main/java/net/kyori/adventure/nbt/DoubleBinaryTagImpl.java +++ b/nbt/src/main/java/net/kyori/adventure/nbt/DoubleBinaryTagImpl.java @@ -72,6 +72,11 @@ public short shortValue() { return (short) (ShadyPines.floor(this.value) & 0xffff); } + @Override + public Number numberValue() { + return this.value; + } + @Override public boolean equals(final @Nullable Object other) { if (this == other) return true; diff --git a/nbt/src/main/java/net/kyori/adventure/nbt/FloatBinaryTagImpl.java b/nbt/src/main/java/net/kyori/adventure/nbt/FloatBinaryTagImpl.java index 364f0753b9..7346aa99bd 100644 --- a/nbt/src/main/java/net/kyori/adventure/nbt/FloatBinaryTagImpl.java +++ b/nbt/src/main/java/net/kyori/adventure/nbt/FloatBinaryTagImpl.java @@ -72,6 +72,11 @@ public short shortValue() { return (short) (ShadyPines.floor(this.value) & 0xffff); } + @Override + public Number numberValue() { + return this.value; + } + @Override public boolean equals(final @Nullable Object other) { if (this == other) return true; diff --git a/nbt/src/main/java/net/kyori/adventure/nbt/IntBinaryTagImpl.java b/nbt/src/main/java/net/kyori/adventure/nbt/IntBinaryTagImpl.java index 81d5495606..a972f6e565 100644 --- a/nbt/src/main/java/net/kyori/adventure/nbt/IntBinaryTagImpl.java +++ b/nbt/src/main/java/net/kyori/adventure/nbt/IntBinaryTagImpl.java @@ -72,6 +72,11 @@ public short shortValue() { return (short) (this.value & 0xffff); } + @Override + public Number numberValue() { + return this.value; + } + @Override public boolean equals(final @Nullable Object other) { if (this == other) return true; diff --git a/nbt/src/main/java/net/kyori/adventure/nbt/LongBinaryTagImpl.java b/nbt/src/main/java/net/kyori/adventure/nbt/LongBinaryTagImpl.java index e78255da58..39ace84eab 100644 --- a/nbt/src/main/java/net/kyori/adventure/nbt/LongBinaryTagImpl.java +++ b/nbt/src/main/java/net/kyori/adventure/nbt/LongBinaryTagImpl.java @@ -72,6 +72,11 @@ public short shortValue() { return (short) (this.value & 0xffff); } + @Override + public Number numberValue() { + return this.value; + } + @Override public boolean equals(final @Nullable Object other) { if (this == other) return true; diff --git a/nbt/src/main/java/net/kyori/adventure/nbt/NumberBinaryTag.java b/nbt/src/main/java/net/kyori/adventure/nbt/NumberBinaryTag.java index 7bda8925ad..47c2af9425 100644 --- a/nbt/src/main/java/net/kyori/adventure/nbt/NumberBinaryTag.java +++ b/nbt/src/main/java/net/kyori/adventure/nbt/NumberBinaryTag.java @@ -81,4 +81,12 @@ public interface NumberBinaryTag extends BinaryTag { * @since 4.0.0 */ short shortValue(); + + /** + * Gets the value as a {@code Number} + * + * @return the value as a {@code Number} + * @since 4.20.0 + */ + Number numberValue(); } diff --git a/nbt/src/main/java/net/kyori/adventure/nbt/ShortBinaryTagImpl.java b/nbt/src/main/java/net/kyori/adventure/nbt/ShortBinaryTagImpl.java index 3db6d8b737..75f0ef5449 100644 --- a/nbt/src/main/java/net/kyori/adventure/nbt/ShortBinaryTagImpl.java +++ b/nbt/src/main/java/net/kyori/adventure/nbt/ShortBinaryTagImpl.java @@ -72,6 +72,11 @@ public short shortValue() { return this.value; } + @Override + public Number numberValue() { + return this.value; + } + @Override public boolean equals(final @Nullable Object other) { if (this == other) return true; From 293483fc6bfba3caab914eaeb87b9ebaa23d190f Mon Sep 17 00:00:00 2001 From: GliczDev <67753196+GliczDev@users.noreply.github.com> Date: Tue, 11 Mar 2025 12:47:22 +0100 Subject: [PATCH 04/86] Fix NumberBinaryTag#numberValue javadoc --- nbt/src/main/java/net/kyori/adventure/nbt/NumberBinaryTag.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nbt/src/main/java/net/kyori/adventure/nbt/NumberBinaryTag.java b/nbt/src/main/java/net/kyori/adventure/nbt/NumberBinaryTag.java index 47c2af9425..20e1eb635a 100644 --- a/nbt/src/main/java/net/kyori/adventure/nbt/NumberBinaryTag.java +++ b/nbt/src/main/java/net/kyori/adventure/nbt/NumberBinaryTag.java @@ -83,7 +83,7 @@ public interface NumberBinaryTag extends BinaryTag { short shortValue(); /** - * Gets the value as a {@code Number} + * Gets the value as a {@code Number}. * * @return the value as a {@code Number} * @since 4.20.0 From 33c0bb9ddfc72dfba2f644a05e72441bf7c65674 Mon Sep 17 00:00:00 2001 From: GliczDev <67753196+GliczDev@users.noreply.github.com> Date: Thu, 13 Mar 2025 16:49:17 +0100 Subject: [PATCH 05/86] Add missing `@NotNull` annotation and slightly modify NumberBinaryTag#numberValue javadoc --- .../java/net/kyori/adventure/nbt/ByteBinaryTagImpl.java | 2 +- .../java/net/kyori/adventure/nbt/DoubleBinaryTagImpl.java | 2 +- .../java/net/kyori/adventure/nbt/FloatBinaryTagImpl.java | 2 +- .../main/java/net/kyori/adventure/nbt/IntBinaryTagImpl.java | 2 +- .../java/net/kyori/adventure/nbt/LongBinaryTagImpl.java | 2 +- .../main/java/net/kyori/adventure/nbt/NumberBinaryTag.java | 6 +++--- .../java/net/kyori/adventure/nbt/ShortBinaryTagImpl.java | 2 +- 7 files changed, 9 insertions(+), 9 deletions(-) diff --git a/nbt/src/main/java/net/kyori/adventure/nbt/ByteBinaryTagImpl.java b/nbt/src/main/java/net/kyori/adventure/nbt/ByteBinaryTagImpl.java index 459bf6a3a0..5d6702dbe2 100644 --- a/nbt/src/main/java/net/kyori/adventure/nbt/ByteBinaryTagImpl.java +++ b/nbt/src/main/java/net/kyori/adventure/nbt/ByteBinaryTagImpl.java @@ -73,7 +73,7 @@ public short shortValue() { } @Override - public Number numberValue() { + public @NotNull Number numberValue() { return this.value; } diff --git a/nbt/src/main/java/net/kyori/adventure/nbt/DoubleBinaryTagImpl.java b/nbt/src/main/java/net/kyori/adventure/nbt/DoubleBinaryTagImpl.java index b68b95a196..cdf834a980 100644 --- a/nbt/src/main/java/net/kyori/adventure/nbt/DoubleBinaryTagImpl.java +++ b/nbt/src/main/java/net/kyori/adventure/nbt/DoubleBinaryTagImpl.java @@ -73,7 +73,7 @@ public short shortValue() { } @Override - public Number numberValue() { + public @NotNull Number numberValue() { return this.value; } diff --git a/nbt/src/main/java/net/kyori/adventure/nbt/FloatBinaryTagImpl.java b/nbt/src/main/java/net/kyori/adventure/nbt/FloatBinaryTagImpl.java index 7346aa99bd..245eb93dd2 100644 --- a/nbt/src/main/java/net/kyori/adventure/nbt/FloatBinaryTagImpl.java +++ b/nbt/src/main/java/net/kyori/adventure/nbt/FloatBinaryTagImpl.java @@ -73,7 +73,7 @@ public short shortValue() { } @Override - public Number numberValue() { + public @NotNull Number numberValue() { return this.value; } diff --git a/nbt/src/main/java/net/kyori/adventure/nbt/IntBinaryTagImpl.java b/nbt/src/main/java/net/kyori/adventure/nbt/IntBinaryTagImpl.java index a972f6e565..53290cd0bc 100644 --- a/nbt/src/main/java/net/kyori/adventure/nbt/IntBinaryTagImpl.java +++ b/nbt/src/main/java/net/kyori/adventure/nbt/IntBinaryTagImpl.java @@ -73,7 +73,7 @@ public short shortValue() { } @Override - public Number numberValue() { + public @NotNull Number numberValue() { return this.value; } diff --git a/nbt/src/main/java/net/kyori/adventure/nbt/LongBinaryTagImpl.java b/nbt/src/main/java/net/kyori/adventure/nbt/LongBinaryTagImpl.java index 39ace84eab..2288b7880a 100644 --- a/nbt/src/main/java/net/kyori/adventure/nbt/LongBinaryTagImpl.java +++ b/nbt/src/main/java/net/kyori/adventure/nbt/LongBinaryTagImpl.java @@ -73,7 +73,7 @@ public short shortValue() { } @Override - public Number numberValue() { + public @NotNull Number numberValue() { return this.value; } diff --git a/nbt/src/main/java/net/kyori/adventure/nbt/NumberBinaryTag.java b/nbt/src/main/java/net/kyori/adventure/nbt/NumberBinaryTag.java index 20e1eb635a..9843ff7a74 100644 --- a/nbt/src/main/java/net/kyori/adventure/nbt/NumberBinaryTag.java +++ b/nbt/src/main/java/net/kyori/adventure/nbt/NumberBinaryTag.java @@ -83,10 +83,10 @@ public interface NumberBinaryTag extends BinaryTag { short shortValue(); /** - * Gets the value as a {@code Number}. + * Gets the value as a {@link Number}. * - * @return the value as a {@code Number} + * @return the value as a {@link Number} * @since 4.20.0 */ - Number numberValue(); + @NotNull Number numberValue(); } diff --git a/nbt/src/main/java/net/kyori/adventure/nbt/ShortBinaryTagImpl.java b/nbt/src/main/java/net/kyori/adventure/nbt/ShortBinaryTagImpl.java index 75f0ef5449..6d10fe8133 100644 --- a/nbt/src/main/java/net/kyori/adventure/nbt/ShortBinaryTagImpl.java +++ b/nbt/src/main/java/net/kyori/adventure/nbt/ShortBinaryTagImpl.java @@ -73,7 +73,7 @@ public short shortValue() { } @Override - public Number numberValue() { + public @NotNull Number numberValue() { return this.value; } From 0ca88b7c25398bfbd333b496b2451c2291d27683 Mon Sep 17 00:00:00 2001 From: zml Date: Sat, 5 Apr 2025 12:53:03 -0700 Subject: [PATCH 06/86] release: version 4.20.0 --- gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index 67d798b9d0..416172b69d 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,5 +1,5 @@ group=net.kyori -version=4.20.0-SNAPSHOT +version=4.20.0 description=A user-interface library for Minecraft: Java Edition. org.gradle.parallel=true From 95cea20632b50b581bc86e11f6c5394286ab6a32 Mon Sep 17 00:00:00 2001 From: zml Date: Sat, 5 Apr 2025 13:09:22 -0700 Subject: [PATCH 07/86] release: prepare for further development on 4.21.0 --- gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index 416172b69d..5104638a41 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,5 +1,5 @@ group=net.kyori -version=4.20.0 +version=4.21.0-SNAPSHOT description=A user-interface library for Minecraft: Java Edition. org.gradle.parallel=true From a7917ab45ad545f25e7f0c4bd9a8849a234e8a75 Mon Sep 17 00:00:00 2001 From: zml Date: Sun, 6 Apr 2025 15:28:53 -0700 Subject: [PATCH 08/86] feat(nbt): Add stream API for compound tags --- .../java/net/kyori/adventure/nbt/CompoundBinaryTag.java | 9 +++++++++ .../net/kyori/adventure/nbt/CompoundBinaryTagImpl.java | 6 ++++++ 2 files changed, 15 insertions(+) diff --git a/nbt/src/main/java/net/kyori/adventure/nbt/CompoundBinaryTag.java b/nbt/src/main/java/net/kyori/adventure/nbt/CompoundBinaryTag.java index 77c5d14baf..15794be93e 100644 --- a/nbt/src/main/java/net/kyori/adventure/nbt/CompoundBinaryTag.java +++ b/nbt/src/main/java/net/kyori/adventure/nbt/CompoundBinaryTag.java @@ -26,6 +26,7 @@ import java.util.HashMap; import java.util.Map; import java.util.Set; +import java.util.stream.Stream; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -435,6 +436,14 @@ default double getDouble(final @NotNull String key) { */ long@NotNull[] getLongArray(final @NotNull String key, final long@NotNull[] defaultValue); + /** + * Gets a stream of entries in this compound tag. + * + * @return a new entry stream + * @since 4.21.0 + */ + Stream> stream(); + /** * A compound tag builder. * diff --git a/nbt/src/main/java/net/kyori/adventure/nbt/CompoundBinaryTagImpl.java b/nbt/src/main/java/net/kyori/adventure/nbt/CompoundBinaryTagImpl.java index 3d70892cc3..f3a6d3d908 100644 --- a/nbt/src/main/java/net/kyori/adventure/nbt/CompoundBinaryTagImpl.java +++ b/nbt/src/main/java/net/kyori/adventure/nbt/CompoundBinaryTagImpl.java @@ -236,6 +236,12 @@ public double getDouble(final @NotNull String key, final double defaultValue) { return defaultValue; } + @Override + @SuppressWarnings({"unchecked", "rawtypes"}) + public Stream> stream() { + return (Stream) this.tags.entrySet().stream(); + } + private CompoundBinaryTag edit(final Consumer> consumer) { final Map tags = new HashMap<>(this.tags); consumer.accept(tags); From c87c4dc5e9010184b04d65640d6d024c72221e9f Mon Sep 17 00:00:00 2001 From: zml Date: Sun, 6 Apr 2025 15:45:52 -0700 Subject: [PATCH 09/86] feat(nbt): Add collectors for list and compound tags --- .../adventure/nbt/CompoundBinaryTag.java | 79 +++++++++++++++++++ .../kyori/adventure/nbt/ListBinaryTag.java | 29 +++++++ 2 files changed, 108 insertions(+) diff --git a/nbt/src/main/java/net/kyori/adventure/nbt/CompoundBinaryTag.java b/nbt/src/main/java/net/kyori/adventure/nbt/CompoundBinaryTag.java index 15794be93e..78e1afa45a 100644 --- a/nbt/src/main/java/net/kyori/adventure/nbt/CompoundBinaryTag.java +++ b/nbt/src/main/java/net/kyori/adventure/nbt/CompoundBinaryTag.java @@ -26,10 +26,14 @@ import java.util.HashMap; import java.util.Map; import java.util.Set; +import java.util.function.Function; +import java.util.stream.Collector; import java.util.stream.Stream; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import static java.util.Objects.requireNonNull; + /** * Binary tag holding a mapping of string keys to {@link BinaryTag} values. * @@ -60,6 +64,81 @@ public interface CompoundBinaryTag extends BinaryTag, CompoundTagSetter(tags)); // explicitly copy } + /** + * Create a {@link Collector} to consume streams of map entries. + * + *

In the event of duplicate entries, the last seen entry will be preserved.

+ * + * @return a collector for map entries + * @since 4.21.0 + */ + static @NotNull Collector, ?, CompoundBinaryTag> toCompoundTag() { + return toCompoundTag(Map.Entry::getKey, Map.Entry::getValue); + } + + /** + * Create a {@link Collector} to consume streams of a user-chosen type. + * + *

In the event of duplicate keys, the last seen entry will be preserved.

+ * + * @param the stream value type + * @param keyLens a function to extract a key from the target object + * @param valueLens a function to extract a tag value from the target object + * @return a collector creating compound tags + * @since 4.21.0 + */ + static @NotNull Collector toCompoundTag(final @NotNull Function keyLens, final @NotNull Function valueLens) { + requireNonNull(keyLens, "keyLens"); + requireNonNull(valueLens, "valueLens"); + + return Collector.of( + CompoundBinaryTag::builder, + (b, ent) -> b.put(keyLens.apply(ent), valueLens.apply(ent)), + (l, r) -> l.put(r.build()), + CompoundBinaryTag.Builder::build, + Collector.Characteristics.UNORDERED + ); + } + + /** + * Create a {@link Collector} to consume streams of map entries, with initial contents. + * + *

In the event of duplicate entries, the last seen entry will be preserved.

+ * + * @param initial an existing tag that will initialize the builder + * @return a collector for map entries + * @since 4.21.0 + */ + static @NotNull Collector, ?, CompoundBinaryTag> toCompoundTag(final @NotNull CompoundBinaryTag initial) { + return toCompoundTag(initial, Map.Entry::getKey, Map.Entry::getValue); + } + + /** + * Create a {@link Collector} to consume streams of a user-chosen type, with initial contents. + * + *

In the event of duplicate keys, the last seen entry will be preserved.

+ * + * @param the stream value type + * @param initial an existing tag that will initialize the builder + * @param keyLens a function to extract a key from the target object + * @param valueLens a function to extract a tag value from the target object + * @return a collector creating compound tags + * @since 4.21.0 + */ + static @NotNull Collector toCompoundTag(final @NotNull CompoundBinaryTag initial, final @NotNull Function keyLens, final @NotNull Function valueLens) { + requireNonNull(initial, "initial"); + requireNonNull(keyLens, "keyLens"); + requireNonNull(valueLens, "valueLens"); + + return Collector.of( + () -> CompoundBinaryTag.builder().put(initial), + (b, ent) -> b.put(keyLens.apply(ent), valueLens.apply(ent)), + (l, r) -> l.put(r.build()), + CompoundBinaryTag.Builder::build, + Collector.Characteristics.UNORDERED + ); + } + /** * Creates a builder. * diff --git a/nbt/src/main/java/net/kyori/adventure/nbt/ListBinaryTag.java b/nbt/src/main/java/net/kyori/adventure/nbt/ListBinaryTag.java index 1b781b915b..bdb11269a4 100644 --- a/nbt/src/main/java/net/kyori/adventure/nbt/ListBinaryTag.java +++ b/nbt/src/main/java/net/kyori/adventure/nbt/ListBinaryTag.java @@ -26,6 +26,7 @@ import java.util.ArrayList; import java.util.List; import java.util.function.Consumer; +import java.util.stream.Collector; import java.util.stream.Stream; import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.NotNull; @@ -121,6 +122,34 @@ public interface ListBinaryTag extends ListTagSetter, return listBinaryTag(type, tags); } + /** + * Create a {@link Collector} to consume streams of list tags. + * + * @return a collector of tags + * @since 4.21.0 + */ + static @NotNull Collector toListTag() { + return toListTag(null); + } + + /** + * Create a {@link Collector} to consume streams of map entries, with initial contents. + * + *

In the event of duplicate entries, the last seen entry will be preserved.

+ * + * @param initial an existing tag that will initialize the builder + * @return a collector for map entries + * @since 4.21.0 + */ + static @NotNull Collector toListTag(final @Nullable ListBinaryTag initial) { + return Collector.of( + initial == null ? ListBinaryTag::builder : () -> ListBinaryTag.builder().add((Iterable) initial), + ListTagSetter::add, + (l, r) -> l.add((Iterable) r.build()), + ListBinaryTag.Builder::build + ); + } + @Override default @NotNull BinaryTagType type() { return BinaryTagTypes.LIST; From c11184f042b0cc391f47a63eff56fe149e263cec Mon Sep 17 00:00:00 2001 From: TonytheMacaroni Date: Mon, 7 Apr 2025 16:09:34 -0400 Subject: [PATCH 10/86] Use DecorationMap for style builder --- .../net/kyori/adventure/text/format/StyleImpl.java | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/api/src/main/java/net/kyori/adventure/text/format/StyleImpl.java b/api/src/main/java/net/kyori/adventure/text/format/StyleImpl.java index 4379005a41..c68427d9d7 100644 --- a/api/src/main/java/net/kyori/adventure/text/format/StyleImpl.java +++ b/api/src/main/java/net/kyori/adventure/text/format/StyleImpl.java @@ -23,7 +23,6 @@ */ package net.kyori.adventure.text.format; -import java.util.EnumMap; import java.util.Map; import java.util.Objects; import java.util.Set; @@ -317,19 +316,19 @@ static final class BuilderImpl implements Builder { @Nullable Key font; @Nullable TextColor color; @Nullable ShadowColor shadowColor; - final Map decorations; + DecorationMap decorations; @Nullable ClickEvent clickEvent; @Nullable HoverEvent hoverEvent; @Nullable String insertion; BuilderImpl() { - this.decorations = new EnumMap<>(DecorationMap.EMPTY); + this.decorations = DecorationMap.EMPTY; } BuilderImpl(final @NotNull StyleImpl style) { this.color = style.color; this.shadowColor = style.shadowColor; - this.decorations = new EnumMap<>(style.decorations); + this.decorations = style.decorations; this.clickEvent = style.clickEvent; this.hoverEvent = style.hoverEvent; this.insertion = style.insertion; @@ -374,7 +373,7 @@ static final class BuilderImpl implements Builder { public @NotNull Builder decoration(final @NotNull TextDecoration decoration, final TextDecoration.@NotNull State state) { requireNonNull(state, "state"); requireNonNull(decoration, "decoration"); - this.decorations.put(decoration, state); + this.decorations = this.decorations.with(decoration, state); return this; } @@ -383,7 +382,7 @@ static final class BuilderImpl implements Builder { requireNonNull(state, "state"); final TextDecoration.@Nullable State oldState = this.decorations.get(decoration); if (oldState == TextDecoration.State.NOT_SET) { - this.decorations.put(decoration, state); + this.decorations = this.decorations.with(decoration, state); } if (oldState != null) { return this; @@ -499,7 +498,7 @@ static final class BuilderImpl implements Builder { private boolean isEmpty() { return this.color == null && this.shadowColor == null - && this.decorations.values().stream().allMatch(state -> state == TextDecoration.State.NOT_SET) + && this.decorations == DecorationMap.EMPTY && this.clickEvent == null && this.hoverEvent == null && this.insertion == null From 6613a8775eeaab9572b44a9eadec85be1dfa3b11 Mon Sep 17 00:00:00 2001 From: tjalp <60233996+tjalp@users.noreply.github.com> Date: Thu, 10 Apr 2025 23:48:18 +0200 Subject: [PATCH 11/86] fix: wrong method name --- .../kyori/adventure/text/minimessage/translation/Argument.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/text-minimessage/src/main/java/net/kyori/adventure/text/minimessage/translation/Argument.java b/text-minimessage/src/main/java/net/kyori/adventure/text/minimessage/translation/Argument.java index 05f94aa225..8b20be0031 100644 --- a/text-minimessage/src/main/java/net/kyori/adventure/text/minimessage/translation/Argument.java +++ b/text-minimessage/src/main/java/net/kyori/adventure/text/minimessage/translation/Argument.java @@ -89,7 +89,7 @@ private Argument() { * @return the named argument * @since 4.20.0 */ - public static @NotNull ComponentLike numeric(final @TagPattern @NotNull String name, final @NotNull String value) { + public static @NotNull ComponentLike string(final @TagPattern @NotNull String name, final @NotNull String value) { return argument(name, TranslationArgument.component(Component.text(value))); } From 8ef2bba974b15e826e6c30efa43c2acfedd24bc2 Mon Sep 17 00:00:00 2001 From: Glicz <67753196+GliczDev@users.noreply.github.com> Date: Fri, 11 Apr 2025 13:58:21 +0200 Subject: [PATCH 12/86] Update version in `@since` tag --- nbt/src/main/java/net/kyori/adventure/nbt/NumberBinaryTag.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nbt/src/main/java/net/kyori/adventure/nbt/NumberBinaryTag.java b/nbt/src/main/java/net/kyori/adventure/nbt/NumberBinaryTag.java index 9843ff7a74..35e1592df8 100644 --- a/nbt/src/main/java/net/kyori/adventure/nbt/NumberBinaryTag.java +++ b/nbt/src/main/java/net/kyori/adventure/nbt/NumberBinaryTag.java @@ -86,7 +86,7 @@ public interface NumberBinaryTag extends BinaryTag { * Gets the value as a {@link Number}. * * @return the value as a {@link Number} - * @since 4.20.0 + * @since 4.21.0 */ @NotNull Number numberValue(); } From 5346fc54d9f802b7a8eacb7e3f479db5f7769d4d Mon Sep 17 00:00:00 2001 From: Gero Date: Fri, 28 Feb 2025 14:23:20 +0100 Subject: [PATCH 13/86] 1.21.5 component changes Co-authored-by: MiniDigger | Martin --- .../configurate4/StyleSerializer.java | 12 +- .../HoverEventSerializersTest.java | 10 +- .../configurate4/StyleSerializerTest.java | 2 +- .../commons/ComponentTreeConstants.java | 15 +- .../gson/GsonComponentSerializer.java | 2 +- .../serializer/gson/SerializerFactory.java | 2 +- .../serializer/gson/ShowEntitySerializer.java | 20 ++- .../text/serializer/gson/StyleSerializer.java | 170 ++++++++++++++---- .../json/JSONComponentConstants.java | 4 +- .../text/serializer/json/JSONOptions.java | 94 +++++++++- .../json/JSONComponentSerializerTest.java | 4 +- .../json/ShowEntitySerializerTest.java | 24 ++- .../json/ShowItemSerializerTest.java | 40 ++--- .../text/serializer/json/StyleTest.java | 28 ++- .../serializer/json/TextComponentTest.java | 4 +- .../json/TranslatableComponentTest.java | 26 ++- 16 files changed, 315 insertions(+), 142 deletions(-) diff --git a/serializer-configurate4/src/main/java/net/kyori/adventure/serializer/configurate4/StyleSerializer.java b/serializer-configurate4/src/main/java/net/kyori/adventure/serializer/configurate4/StyleSerializer.java index 4895e31c79..e748b23b54 100644 --- a/serializer-configurate4/src/main/java/net/kyori/adventure/serializer/configurate4/StyleSerializer.java +++ b/serializer-configurate4/src/main/java/net/kyori/adventure/serializer/configurate4/StyleSerializer.java @@ -40,13 +40,13 @@ import org.spongepowered.configurate.serialize.SerializationException; import org.spongepowered.configurate.serialize.TypeSerializer; -import static net.kyori.adventure.text.serializer.commons.ComponentTreeConstants.CLICK_EVENT; import static net.kyori.adventure.text.serializer.commons.ComponentTreeConstants.CLICK_EVENT_ACTION; +import static net.kyori.adventure.text.serializer.commons.ComponentTreeConstants.CLICK_EVENT_CAMEL; import static net.kyori.adventure.text.serializer.commons.ComponentTreeConstants.CLICK_EVENT_VALUE; import static net.kyori.adventure.text.serializer.commons.ComponentTreeConstants.COLOR; import static net.kyori.adventure.text.serializer.commons.ComponentTreeConstants.FONT; -import static net.kyori.adventure.text.serializer.commons.ComponentTreeConstants.HOVER_EVENT; import static net.kyori.adventure.text.serializer.commons.ComponentTreeConstants.HOVER_EVENT_ACTION; +import static net.kyori.adventure.text.serializer.commons.ComponentTreeConstants.HOVER_EVENT_CAMEL; import static net.kyori.adventure.text.serializer.commons.ComponentTreeConstants.HOVER_EVENT_CONTENTS; import static net.kyori.adventure.text.serializer.commons.ComponentTreeConstants.HOVER_EVENT_VALUE; import static net.kyori.adventure.text.serializer.commons.ComponentTreeConstants.INSERTION; @@ -95,13 +95,13 @@ private StyleSerializer() { builder.insertion(insertion); } - final ConfigurationNode clickEvent = value.node(CLICK_EVENT); + final ConfigurationNode clickEvent = value.node(CLICK_EVENT_CAMEL); if (!clickEvent.virtual()) { final ClickEvent.Action action = nonNull(clickEvent.node(CLICK_EVENT_ACTION).get(ClickEvent.Action.class), "click event action"); builder.clickEvent(ClickEvent.clickEvent(action, nonNull(clickEvent.node(CLICK_EVENT_VALUE).getString(), "click event value"))); } - final ConfigurationNode hoverEvent = value.node(HOVER_EVENT); + final ConfigurationNode hoverEvent = value.node(HOVER_EVENT_CAMEL); if (!hoverEvent.virtual()) { final HoverEvent.Action action = hoverEvent.node(HOVER_EVENT_ACTION).get(HOVER_EVENT_ACTION_TYPE); final ConfigurationNode contents = hoverEvent.node(HOVER_EVENT_CONTENTS); @@ -151,7 +151,7 @@ public void serialize(final @NotNull Type type, @Nullable Style obj, final @NotN } value.node(INSERTION).set(obj.insertion()); - final ConfigurationNode clickNode = value.node(CLICK_EVENT); + final ConfigurationNode clickNode = value.node(CLICK_EVENT_CAMEL); final ClickEvent clickEvent = obj.clickEvent(); if (clickEvent == null) { clickNode.set(null); @@ -160,7 +160,7 @@ public void serialize(final @NotNull Type type, @Nullable Style obj, final @NotN clickNode.node(CLICK_EVENT_VALUE).set(clickEvent.value()); } - final ConfigurationNode hoverNode = value.node(HOVER_EVENT); + final ConfigurationNode hoverNode = value.node(HOVER_EVENT_CAMEL); if (obj.hoverEvent() == null) { hoverNode.set(null); } else { diff --git a/serializer-configurate4/src/test/java/net/kyori/adventure/serializer/configurate4/HoverEventSerializersTest.java b/serializer-configurate4/src/test/java/net/kyori/adventure/serializer/configurate4/HoverEventSerializersTest.java index 3a1de3af94..e6c4cebdee 100644 --- a/serializer-configurate4/src/test/java/net/kyori/adventure/serializer/configurate4/HoverEventSerializersTest.java +++ b/serializer-configurate4/src/test/java/net/kyori/adventure/serializer/configurate4/HoverEventSerializersTest.java @@ -39,7 +39,7 @@ class HoverEventSerializersTest implements ConfigurateTestBase { void testShowEntity() { final ConfigurationNode node = this.node(n -> { n.node(ComponentTreeConstants.TEXT).raw("kashike"); - n.node(ComponentTreeConstants.HOVER_EVENT).act(event -> { + n.node(ComponentTreeConstants.HOVER_EVENT_CAMEL).act(event -> { event.node(ComponentTreeConstants.HOVER_EVENT_ACTION).raw("show_entity"); event.node(ComponentTreeConstants.HOVER_EVENT_CONTENTS).act(entity -> { entity.node(ComponentTreeConstants.SHOW_ENTITY_TYPE).raw("minecraft:cat"); @@ -58,7 +58,7 @@ void testShowEntity() { void testShowEntityCustomName() { final ConfigurationNode node = this.node(n -> { n.node(ComponentTreeConstants.TEXT).raw("kashike"); - n.node(ComponentTreeConstants.HOVER_EVENT).act(event -> { + n.node(ComponentTreeConstants.HOVER_EVENT_CAMEL).act(event -> { event.node(ComponentTreeConstants.HOVER_EVENT_ACTION).raw("show_entity"); event.node(ComponentTreeConstants.HOVER_EVENT_CONTENTS).act(entity -> { entity.node(ComponentTreeConstants.SHOW_ENTITY_TYPE).raw("minecraft:cat"); @@ -83,7 +83,7 @@ void testShowItem() { extra.appendListNode().node(ComponentTreeConstants.TRANSLATE).raw("item.minecraft.purple_wool"); extra.appendListNode().node(ComponentTreeConstants.TEXT).raw("]"); }); - n.node(ComponentTreeConstants.HOVER_EVENT).act(hover -> { + n.node(ComponentTreeConstants.HOVER_EVENT_CAMEL).act(hover -> { hover.node(ComponentTreeConstants.HOVER_EVENT_ACTION).raw("show_item"); hover.node(ComponentTreeConstants.HOVER_EVENT_CONTENTS).act(action -> { action.node(ComponentTreeConstants.SHOW_ITEM_ID).raw("minecraft:purple_wool"); @@ -111,7 +111,7 @@ void testShowItemNoTag() { extra.appendListNode().node(ComponentTreeConstants.TRANSLATE).raw("item.minecraft.purple_wool"); extra.appendListNode().node(ComponentTreeConstants.TEXT).raw("]"); }); - n.node(ComponentTreeConstants.HOVER_EVENT).act(hover -> { + n.node(ComponentTreeConstants.HOVER_EVENT_CAMEL).act(hover -> { hover.node(ComponentTreeConstants.HOVER_EVENT_ACTION).raw("show_item"); hover.node(ComponentTreeConstants.HOVER_EVENT_CONTENTS).act(action -> { action.node(ComponentTreeConstants.SHOW_ITEM_ID).raw("minecraft:purple_wool"); @@ -132,7 +132,7 @@ void testShowItemNoTag() { @Test void testShowText() { final ConfigurationNode node = this.node(n -> { - n.node(ComponentTreeConstants.HOVER_EVENT).act(event -> { + n.node(ComponentTreeConstants.HOVER_EVENT_CAMEL).act(event -> { event.node(ComponentTreeConstants.HOVER_EVENT_ACTION).raw("show_text"); event.node(ComponentTreeConstants.HOVER_EVENT_CONTENTS, ComponentTreeConstants.TEXT).raw("i'm hovering"); }); diff --git a/serializer-configurate4/src/test/java/net/kyori/adventure/serializer/configurate4/StyleSerializerTest.java b/serializer-configurate4/src/test/java/net/kyori/adventure/serializer/configurate4/StyleSerializerTest.java index 6b7b4c9572..8eecf91956 100644 --- a/serializer-configurate4/src/test/java/net/kyori/adventure/serializer/configurate4/StyleSerializerTest.java +++ b/serializer-configurate4/src/test/java/net/kyori/adventure/serializer/configurate4/StyleSerializerTest.java @@ -111,7 +111,7 @@ void testSerializeInsertion() { @Test void testSerializeClickEvent() { final ConfigurationNode node = this.node(n -> { - n.node(ComponentTreeConstants.CLICK_EVENT).act(event -> { + n.node(ComponentTreeConstants.CLICK_EVENT_CAMEL).act(event -> { event.node(ComponentTreeConstants.CLICK_EVENT_ACTION).raw("open_url"); event.node(ComponentTreeConstants.CLICK_EVENT_VALUE).raw("https://kyori.net"); }); diff --git a/text-serializer-commons/src/main/java/net/kyori/adventure/text/serializer/commons/ComponentTreeConstants.java b/text-serializer-commons/src/main/java/net/kyori/adventure/text/serializer/commons/ComponentTreeConstants.java index d995f7789d..4c7e77ecec 100644 --- a/text-serializer-commons/src/main/java/net/kyori/adventure/text/serializer/commons/ComponentTreeConstants.java +++ b/text-serializer-commons/src/main/java/net/kyori/adventure/text/serializer/commons/ComponentTreeConstants.java @@ -51,16 +51,27 @@ public final class ComponentTreeConstants { public static final String COLOR = "color"; public static final String SHADOW_COLOR = "shadow_color"; public static final String INSERTION = "insertion"; - public static final String CLICK_EVENT = "clickEvent"; + @Deprecated + public static final String CLICK_EVENT_CAMEL = "clickEvent"; + public static final String CLICK_EVENT_SNAKE = "click_event"; public static final String CLICK_EVENT_ACTION = "action"; public static final String CLICK_EVENT_VALUE = "value"; - public static final String HOVER_EVENT = "hoverEvent"; + public static final String CLICK_EVENT_URL = "url"; + public static final String CLICK_EVENT_PATH = "path"; + public static final String CLICK_EVENT_COMMAND = "command"; + public static final String CLICK_EVENT_PAGE = "page"; + @Deprecated + public static final String HOVER_EVENT_CAMEL = "hoverEvent"; + public static final String HOVER_EVENT_SNAKE = "hover_event"; public static final String HOVER_EVENT_ACTION = "action"; + @Deprecated public static final String HOVER_EVENT_CONTENTS = "contents"; @Deprecated public static final String HOVER_EVENT_VALUE = "value"; + @Deprecated public static final String SHOW_ENTITY_TYPE = "type"; public static final String SHOW_ENTITY_ID = "id"; + public static final String SHOW_ENTITY_UUID = "uuid"; public static final String SHOW_ENTITY_NAME = "name"; public static final String SHOW_ITEM_ID = "id"; public static final String SHOW_ITEM_COUNT = "count"; diff --git a/text-serializer-gson/src/main/java/net/kyori/adventure/text/serializer/gson/GsonComponentSerializer.java b/text-serializer-gson/src/main/java/net/kyori/adventure/text/serializer/gson/GsonComponentSerializer.java index a2350423c0..9a88e6d93d 100644 --- a/text-serializer-gson/src/main/java/net/kyori/adventure/text/serializer/gson/GsonComponentSerializer.java +++ b/text-serializer-gson/src/main/java/net/kyori/adventure/text/serializer/gson/GsonComponentSerializer.java @@ -167,7 +167,7 @@ interface Builder extends AbstractBuilder, Buildable.Bu @Deprecated @Override default @NotNull Builder emitLegacyHoverEvent() { - return this.editOptions(b -> b.value(JSONOptions.EMIT_HOVER_EVENT_TYPE, JSONOptions.HoverEventValueMode.BOTH)); + return this.editOptions(b -> b.value(JSONOptions.EMIT_HOVER_EVENT_TYPE, JSONOptions.HoverEventValueMode.ALL)); } /** diff --git a/text-serializer-gson/src/main/java/net/kyori/adventure/text/serializer/gson/SerializerFactory.java b/text-serializer-gson/src/main/java/net/kyori/adventure/text/serializer/gson/SerializerFactory.java index 29fa2f3a38..69ff0842d1 100644 --- a/text-serializer-gson/src/main/java/net/kyori/adventure/text/serializer/gson/SerializerFactory.java +++ b/text-serializer-gson/src/main/java/net/kyori/adventure/text/serializer/gson/SerializerFactory.java @@ -84,7 +84,7 @@ public TypeAdapter create(final Gson gson, final TypeToken type) { } else if (SHOW_ITEM_TYPE.isAssignableFrom(rawType)) { return (TypeAdapter) ShowItemSerializer.create(gson, this.features); } else if (SHOW_ENTITY_TYPE.isAssignableFrom(rawType)) { - return (TypeAdapter) ShowEntitySerializer.create(gson); + return (TypeAdapter) ShowEntitySerializer.create(gson, this.features); } else if (COLOR_WRAPPER_TYPE.isAssignableFrom(rawType)) { return (TypeAdapter) TextColorWrapper.Serializer.INSTANCE; } else if (COLOR_TYPE.isAssignableFrom(rawType)) { diff --git a/text-serializer-gson/src/main/java/net/kyori/adventure/text/serializer/gson/ShowEntitySerializer.java b/text-serializer-gson/src/main/java/net/kyori/adventure/text/serializer/gson/ShowEntitySerializer.java index d47e00bb0c..0fc94d42e0 100644 --- a/text-serializer-gson/src/main/java/net/kyori/adventure/text/serializer/gson/ShowEntitySerializer.java +++ b/text-serializer-gson/src/main/java/net/kyori/adventure/text/serializer/gson/ShowEntitySerializer.java @@ -33,21 +33,26 @@ import net.kyori.adventure.key.Key; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.event.HoverEvent; +import net.kyori.adventure.text.serializer.json.JSONOptions; +import net.kyori.option.OptionState; import org.jetbrains.annotations.Nullable; import static net.kyori.adventure.text.serializer.commons.ComponentTreeConstants.SHOW_ENTITY_ID; import static net.kyori.adventure.text.serializer.commons.ComponentTreeConstants.SHOW_ENTITY_NAME; import static net.kyori.adventure.text.serializer.commons.ComponentTreeConstants.SHOW_ENTITY_TYPE; +import static net.kyori.adventure.text.serializer.commons.ComponentTreeConstants.SHOW_ENTITY_UUID; final class ShowEntitySerializer extends TypeAdapter { - static TypeAdapter create(final Gson gson) { - return new ShowEntitySerializer(gson).nullSafe(); + static TypeAdapter create(final Gson gson, final OptionState opt) { + return new ShowEntitySerializer(gson, opt.value(JSONOptions.EMIT_HOVER_SHOW_ENTITY_KEY_AS_TYPE_AND_UUID_AS_ID)).nullSafe(); } private final Gson gson; + private final boolean emitKeyAsTypeAndUuidAsId; - private ShowEntitySerializer(final Gson gson) { + private ShowEntitySerializer(final Gson gson, final boolean emitKeyAsTypeAndUuidAsId) { this.gson = gson; + this.emitKeyAsTypeAndUuidAsId = emitKeyAsTypeAndUuidAsId; } @Override @@ -60,9 +65,10 @@ public HoverEvent.ShowEntity read(final JsonReader in) throws IOException { while (in.hasNext()) { final String fieldName = in.nextName(); - if (fieldName.equals(SHOW_ENTITY_TYPE)) { + // TODO make this more flexible + if ((!this.emitKeyAsTypeAndUuidAsId && fieldName.equals(SHOW_ENTITY_ID)) || fieldName.equals(SHOW_ENTITY_TYPE)) { type = this.gson.fromJson(in, SerializerFactory.KEY_TYPE); - } else if (fieldName.equals(SHOW_ENTITY_ID)) { + } else if (fieldName.equals(SHOW_ENTITY_UUID) || (this.emitKeyAsTypeAndUuidAsId && fieldName.equals(SHOW_ENTITY_ID))) { id = this.gson.fromJson(in, SerializerFactory.UUID_TYPE); } else if (fieldName.equals(SHOW_ENTITY_NAME)) { name = this.gson.fromJson(in, SerializerFactory.COMPONENT_TYPE); @@ -83,10 +89,10 @@ public HoverEvent.ShowEntity read(final JsonReader in) throws IOException { public void write(final JsonWriter out, final HoverEvent.ShowEntity value) throws IOException { out.beginObject(); - out.name(SHOW_ENTITY_TYPE); + out.name(this.emitKeyAsTypeAndUuidAsId ? SHOW_ENTITY_TYPE : SHOW_ENTITY_ID); this.gson.toJson(value.type(), SerializerFactory.KEY_TYPE, out); - out.name(SHOW_ENTITY_ID); + out.name(this.emitKeyAsTypeAndUuidAsId ? SHOW_ENTITY_ID : SHOW_ENTITY_UUID); this.gson.toJson(value.id(), SerializerFactory.UUID_TYPE, out); final @Nullable Component name = value.name(); diff --git a/text-serializer-gson/src/main/java/net/kyori/adventure/text/serializer/gson/StyleSerializer.java b/text-serializer-gson/src/main/java/net/kyori/adventure/text/serializer/gson/StyleSerializer.java index bcd11a821f..9627b30d18 100644 --- a/text-serializer-gson/src/main/java/net/kyori/adventure/text/serializer/gson/StyleSerializer.java +++ b/text-serializer-gson/src/main/java/net/kyori/adventure/text/serializer/gson/StyleSerializer.java @@ -35,6 +35,7 @@ import com.google.gson.stream.JsonWriter; import java.io.IOException; import java.util.EnumSet; +import java.util.Map; import java.util.Set; import net.kyori.adventure.key.Key; import net.kyori.adventure.text.Component; @@ -49,14 +50,20 @@ import net.kyori.option.OptionState; import org.jetbrains.annotations.Nullable; -import static net.kyori.adventure.text.serializer.commons.ComponentTreeConstants.CLICK_EVENT; import static net.kyori.adventure.text.serializer.commons.ComponentTreeConstants.CLICK_EVENT_ACTION; +import static net.kyori.adventure.text.serializer.commons.ComponentTreeConstants.CLICK_EVENT_CAMEL; +import static net.kyori.adventure.text.serializer.commons.ComponentTreeConstants.CLICK_EVENT_COMMAND; +import static net.kyori.adventure.text.serializer.commons.ComponentTreeConstants.CLICK_EVENT_PAGE; +import static net.kyori.adventure.text.serializer.commons.ComponentTreeConstants.CLICK_EVENT_PATH; +import static net.kyori.adventure.text.serializer.commons.ComponentTreeConstants.CLICK_EVENT_SNAKE; +import static net.kyori.adventure.text.serializer.commons.ComponentTreeConstants.CLICK_EVENT_URL; import static net.kyori.adventure.text.serializer.commons.ComponentTreeConstants.CLICK_EVENT_VALUE; import static net.kyori.adventure.text.serializer.commons.ComponentTreeConstants.COLOR; import static net.kyori.adventure.text.serializer.commons.ComponentTreeConstants.FONT; -import static net.kyori.adventure.text.serializer.commons.ComponentTreeConstants.HOVER_EVENT; import static net.kyori.adventure.text.serializer.commons.ComponentTreeConstants.HOVER_EVENT_ACTION; +import static net.kyori.adventure.text.serializer.commons.ComponentTreeConstants.HOVER_EVENT_CAMEL; import static net.kyori.adventure.text.serializer.commons.ComponentTreeConstants.HOVER_EVENT_CONTENTS; +import static net.kyori.adventure.text.serializer.commons.ComponentTreeConstants.HOVER_EVENT_SNAKE; import static net.kyori.adventure.text.serializer.commons.ComponentTreeConstants.HOVER_EVENT_VALUE; import static net.kyori.adventure.text.serializer.commons.ComponentTreeConstants.INSERTION; import static net.kyori.adventure.text.serializer.commons.ComponentTreeConstants.SHADOW_COLOR; @@ -86,10 +93,14 @@ final class StyleSerializer extends TypeAdapter