Skip to content

Commit 00c0e7c

Browse files
authored
Merge pull request #1239 from kermandev/feat/nbt-stringio
feat(nbt): TagStringIO Enhancements
2 parents 535e7d6 + 9343458 commit 00c0e7c

File tree

3 files changed

+152
-9
lines changed

3 files changed

+152
-9
lines changed

nbt/src/main/java/net/kyori/adventure/nbt/CharBuffer.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,18 @@ public CharSequence takeUntil(char until) throws StringTagParseException {
9696
return result;
9797
}
9898

99+
/**
100+
* Takes the remaining characters and advances the index to the end of the buffer.
101+
*
102+
* @return the remaining string from the current index to the length of the sequence.
103+
*/
104+
public CharSequence takeRest() {
105+
final int length = this.sequence.length();
106+
final CharSequence result = this.sequence.subSequence(this.index, length);
107+
this.index = length;
108+
return result;
109+
}
110+
99111
/**
100112
* Assert that the next non-whitespace character is the provided parameter.
101113
*

nbt/src/main/java/net/kyori/adventure/nbt/TagStringIO.java

Lines changed: 115 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
import java.io.IOException;
2727
import java.io.Writer;
2828
import java.util.Arrays;
29+
import java.util.Objects;
2930
import org.jetbrains.annotations.NotNull;
3031

3132
/**
@@ -37,17 +38,29 @@ public final class TagStringIO {
3738
private static final TagStringIO INSTANCE = new TagStringIO(new Builder());
3839

3940
/**
40-
* Get an instance of {@link TagStringIO} that creates reads and writes using standard options.
41+
* Get an instance of {@link TagStringIO} that reads and writes using standard options.
4142
*
4243
* @return the basic instance
4344
* @since 4.0.0
45+
* @deprecated For removal since 4.22.0, use {@link #tagStringIO()} instead
4446
*/
47+
@Deprecated
4548
public static @NotNull TagStringIO get() {
49+
return tagStringIO();
50+
}
51+
52+
/**
53+
* Gets an instance of {@link TagStringIO} that reads and writes using standard options.
54+
*
55+
* @return the basic instance
56+
* @since 4.22.0
57+
*/
58+
public static @NotNull TagStringIO tagStringIO() {
4659
return INSTANCE;
4760
}
4861

4962
/**
50-
* Create an new builder to configure IO.
63+
* Create a new builder to configure IO.
5164
*
5265
* @return a builder
5366
* @since 4.0.0
@@ -77,7 +90,8 @@ private TagStringIO(final @NotNull Builder builder) {
7790
* @throws IOException on any syntax errors
7891
* @since 4.0.0
7992
*/
80-
public CompoundBinaryTag asCompound(final String input) throws IOException {
93+
public @NotNull CompoundBinaryTag asCompound(final @NotNull String input) throws IOException {
94+
Objects.requireNonNull(input, "input");
8195
try {
8296
final CharBuffer buffer = new CharBuffer(input);
8397
final TagStringReader parser = new TagStringReader(buffer);
@@ -92,6 +106,81 @@ public CompoundBinaryTag asCompound(final String input) throws IOException {
92106
}
93107
}
94108

109+
/**
110+
* Read the string into a tag.
111+
*
112+
* <p>When working with untrusted input (such as from the network), users should be careful
113+
* to validate that the {@code input} string is of a reasonable size.</p>
114+
*
115+
* @param input Input data
116+
* @return the parsed tag
117+
* @throws IOException on any syntax errors
118+
* @since 4.22.0
119+
*/
120+
public @NotNull BinaryTag asTag(final @NotNull String input) throws IOException {
121+
Objects.requireNonNull(input, "input");
122+
try {
123+
final CharBuffer buffer = new CharBuffer(input);
124+
final TagStringReader parser = new TagStringReader(buffer);
125+
parser.legacy(this.acceptLegacy);
126+
final BinaryTag tag = parser.tag();
127+
if (buffer.skipWhitespace().hasMore()) {
128+
throw new IOException("Document had trailing content after first Tag");
129+
}
130+
return tag;
131+
} catch (final StringTagParseException ex) {
132+
throw new IOException(ex);
133+
}
134+
}
135+
136+
/**
137+
* Read the string into an embedded compound tag, returning the remainder of the input.
138+
*
139+
* @param input the input string
140+
* @param remainder the appendable to write the remainder to
141+
* @return the parsed tag with the remainder
142+
* @throws IOException on any syntax errors
143+
* @since 4.22.0
144+
*/
145+
public @NotNull CompoundBinaryTag asCompound(final @NotNull String input, final @NotNull Appendable remainder) throws IOException {
146+
Objects.requireNonNull(input, "input");
147+
Objects.requireNonNull(remainder, "remainder");
148+
try {
149+
final CharBuffer buffer = new CharBuffer(input);
150+
final TagStringReader parser = new TagStringReader(buffer);
151+
parser.legacy(this.acceptLegacy);
152+
final CompoundBinaryTag tag = parser.compound();
153+
remainder.append(buffer.takeRest());
154+
return tag;
155+
} catch (final StringTagParseException ex) {
156+
throw new IOException(ex);
157+
}
158+
}
159+
160+
/**
161+
* Read the string into an embedded tag, returning the remainder of the input.
162+
*
163+
* @param input the input string
164+
* @param remainder the appendable to write the remainder to
165+
* @return the parsed tag with the remainder
166+
* @throws IOException on any syntax errors
167+
* @since 4.22.0
168+
*/
169+
public @NotNull BinaryTag asTag(final @NotNull String input, final @NotNull Appendable remainder) throws IOException {
170+
Objects.requireNonNull(input, "input");
171+
Objects.requireNonNull(remainder, "remainder");
172+
try {
173+
final CharBuffer buffer = new CharBuffer(input);
174+
final TagStringReader parser = new TagStringReader(buffer);
175+
parser.legacy(this.acceptLegacy);
176+
final BinaryTag tag = parser.tag();
177+
remainder.append(buffer.takeRest());
178+
return tag;
179+
} catch (final StringTagParseException ex) {
180+
throw new IOException(ex);
181+
}
182+
}
183+
95184
/**
96185
* Get a string representation of the provided tag.
97186
*
@@ -100,7 +189,7 @@ public CompoundBinaryTag asCompound(final String input) throws IOException {
100189
* @throws IOException if any errors occur writing to string
101190
* @since 4.0.0
102191
*/
103-
public String asString(final CompoundBinaryTag input) throws IOException {
192+
public @NotNull String asString(final @NotNull CompoundBinaryTag input) throws IOException {
104193
return this.asString((BinaryTag) input);
105194
}
106195

@@ -112,7 +201,8 @@ public String asString(final CompoundBinaryTag input) throws IOException {
112201
* @throws IOException if any errors occur writing to string
113202
* @since 4.20.0
114203
*/
115-
public String asString(final BinaryTag input) throws IOException {
204+
public @NotNull String asString(final @NotNull BinaryTag input) throws IOException {
205+
Objects.requireNonNull(input, "input");
116206
final StringBuilder sb = new StringBuilder();
117207
try (final TagStringWriter emit = new TagStringWriter(sb, this.indent)) {
118208
emit.legacy(this.emitLegacy);
@@ -122,7 +212,7 @@ public String asString(final BinaryTag input) throws IOException {
122212
}
123213

124214
/**
125-
* Writes a tag to in string format.
215+
* Writes a compound tag to in string format.
126216
*
127217
* <p>The provided {@link Writer} will remain open after reading a tag.</p>
128218
*
@@ -131,7 +221,23 @@ public String asString(final BinaryTag input) throws IOException {
131221
* @throws IOException if any IO or syntax errors occur while parsing
132222
* @since 4.0.0
133223
*/
134-
public void toWriter(final CompoundBinaryTag input, final Writer dest) throws IOException {
224+
public void toWriter(final @NotNull CompoundBinaryTag input, final @NotNull Writer dest) throws IOException {
225+
this.toWriter((BinaryTag) input, dest);
226+
}
227+
228+
/**
229+
* Writes a tag to in string format.
230+
*
231+
* <p>The provided {@link Writer} will remain open after reading a tag.</p>
232+
*
233+
* @param input Tag to write
234+
* @param dest Writer to write to
235+
* @throws IOException if any IO or syntax errors occur while parsing
236+
* @since 4.22.0
237+
*/
238+
public void toWriter(final @NotNull BinaryTag input, final @NotNull Writer dest) throws IOException {
239+
Objects.requireNonNull(input, "input");
240+
Objects.requireNonNull(dest, "dest");
135241
try (final TagStringWriter emit = new TagStringWriter(dest, this.indent)) {
136242
emit.legacy(this.emitLegacy);
137243
emit.writeTag(input);
@@ -163,7 +269,7 @@ public static class Builder {
163269
public @NotNull Builder indent(final int spaces) {
164270
if (spaces == 0) {
165271
this.indent = "";
166-
} else if ((this.indent.length() > 0 && this.indent.charAt(0) != ' ') || spaces != this.indent.length()) {
272+
} else if ((!this.indent.isEmpty() && this.indent.charAt(0) != ' ') || spaces != this.indent.length()) {
167273
final char[] indent = new char[spaces];
168274
Arrays.fill(indent, ' ');
169275
this.indent = String.copyValueOf(indent);
@@ -183,7 +289,7 @@ public static class Builder {
183289
public @NotNull Builder indentTab(final int tabs) {
184290
if (tabs == 0) {
185291
this.indent = "";
186-
} else if ((this.indent.length() > 0 && this.indent.charAt(0) != '\t') || tabs != this.indent.length()) {
292+
} else if ((!this.indent.isEmpty() && this.indent.charAt(0) != '\t') || tabs != this.indent.length()) {
187293
final char[] indent = new char[tabs];
188294
Arrays.fill(indent, '\t');
189295
this.indent = String.copyValueOf(indent);

nbt/src/test/java/net/kyori/adventure/nbt/StringIOTest.java

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -313,6 +313,31 @@ void testTrailingComma() throws IOException {
313313
assertEquals(ListBinaryTag.builder().add(StringBinaryTag.stringBinaryTag("hello")).build(), this.stringToTag("[\"hello\",]"));
314314
}
315315

316+
@Test
317+
void testReadingEmbeddedCompound() throws IOException {
318+
final String input = "{test: \"hello\"} extra content";
319+
final StringBuilder remainderBuilder = new StringBuilder();
320+
final CompoundBinaryTag tag = TagStringIO.get().asCompound(input, remainderBuilder);
321+
assertEquals(CompoundBinaryTag.builder().putString("test", "hello").build(), tag);
322+
assertEquals(" extra content", remainderBuilder.toString());
323+
}
324+
325+
@Test
326+
void testReadingEmbedded() throws IOException {
327+
final String input = "[1, 1, 1] extra content";
328+
final StringBuilder remainderBuilder = new StringBuilder();
329+
final BinaryTag tag = TagStringIO.get().asTag(input, remainderBuilder);
330+
assertEquals(ListBinaryTag.builder().add(IntBinaryTag.intBinaryTag(1)).add(IntBinaryTag.intBinaryTag(1)).add(IntBinaryTag.intBinaryTag(1)).build(), tag);
331+
assertEquals(" extra content", remainderBuilder.toString());
332+
}
333+
334+
@Test
335+
void testReadingInvalidEmbeddedTag() throws IOException {
336+
final String input = "{test: \"hello\" extra content";
337+
final StringBuilder remainderBuilder = new StringBuilder();
338+
assertThrows(IOException.class, () -> TagStringIO.get().asTag(input, remainderBuilder));
339+
}
340+
316341
private String tagToString(final BinaryTag tag) throws IOException {
317342
final StringWriter writer = new StringWriter();
318343
try (final TagStringWriter emitter = new TagStringWriter(writer, "")) {

0 commit comments

Comments
 (0)