Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(nbt): Implement nameless binary serialization #968

Merged
merged 3 commits into from
Dec 18, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
170 changes: 170 additions & 0 deletions nbt/src/main/java/net/kyori/adventure/nbt/BinaryTagIO.java
Original file line number Diff line number Diff line change
Expand Up @@ -250,6 +250,8 @@ public interface Reader {
*
* <p>This is the equivalent of passing {@code Compression#NONE} as the second parameter to {@link #read(Path, Compression)}.</p>
*
* <p>The root name field is discarded.</p>
*
* @param path the path
* @return a binary tag
* @throws IOException if an exception was encountered while reading the tag
Expand All @@ -262,6 +264,8 @@ public interface Reader {
/**
* Reads a binary tag from {@code path} with a {@code compression} type.
*
* <p>The root name field is discarded.</p>
*
* @param path the path
* @param compression the compression type
* @return a binary tag
Expand All @@ -275,6 +279,8 @@ public interface Reader {
*
* <p>This is the equivalent of passing {@code Compression#NONE} as the second parameter to {@link #read(InputStream, Compression)}.</p>
*
* <p>The root name field is discarded.</p>
*
* @param input the input stream
* @return a binary tag
* @throws IOException if an exception was encountered while reading the tag
Expand All @@ -287,6 +293,8 @@ public interface Reader {
/**
* Reads a binary tag from {@code input} with a {@code compression} type.
*
* <p>The root name field is discarded.</p>
*
* @param input the input stream
* @param compression the compression type
* @return a binary tag
Expand All @@ -298,13 +306,90 @@ public interface Reader {
/**
* Reads a binary tag from {@code input}.
*
* <p>The root name field is discarded.</p>
*
* @param input the input stream
* @return a binary tag
* @throws IOException if an exception was encountered while reading the tag
* @since 4.4.0
*/
@NotNull CompoundBinaryTag read(final @NotNull DataInput input) throws IOException;

/**
* Reads a binary tag from {@code path}.
*
* <p>This is the equivalent of passing {@code Compression#NONE} as the second parameter to {@link #read(Path, Compression)}.</p>
*
* <p>Doesn't read a root name from the {@link Path} at all, to match the wire protocol in modern game versions.</p>
*
* @param path the path
* @return a binary tag
* @throws IOException if an exception was encountered while reading the tag
* @since 4.15.0
* @sinceMinecraft 1.20.2
*/
default @NotNull CompoundBinaryTag readNameless(final @NotNull Path path) throws IOException {
return this.readNameless(path, Compression.NONE);
}

/**
* Reads a binary tag from {@code path} with a {@code compression} type.
*
* <p>Doesn't read a root name from the {@link Path} at all, to match the wire protocol in modern game versions.</p>
*
* @param path the path
* @param compression the compression type
* @return a binary tag
* @throws IOException if an exception was encountered while reading the tag
* @since 4.15.0
* @sinceMinecraft 1.20.2
*/
@NotNull CompoundBinaryTag readNameless(final @NotNull Path path, final @NotNull Compression compression) throws IOException;

/**
* Reads a binary tag from {@code input}.
*
* <p>This is the equivalent of passing {@code Compression#NONE} as the second parameter to {@link #read(InputStream, Compression)}.</p>
*
* <p>Doesn't read a root name from the {@link InputStream} at all, to match the wire protocol in modern game versions.</p>
*
* @param input the input stream
* @return a binary tag
* @throws IOException if an exception was encountered while reading the tag
* @since 4.15.0
* @sinceMinecraft 1.20.2
*/
default @NotNull CompoundBinaryTag readNameless(final @NotNull InputStream input) throws IOException {
return this.readNameless(input, Compression.NONE);
}

/**
* Reads a binary tag from {@code input} with a {@code compression} type.
*
* <p>Doesn't read a root name from the {@link InputStream} at all, to match the wire protocol in modern game versions.</p>
*
* @param input the input stream
* @param compression the compression type
* @return a binary tag
* @throws IOException if an exception was encountered while reading the tag
* @since 4.15.0
* @sinceMinecraft 1.20.2
*/
@NotNull CompoundBinaryTag readNameless(final @NotNull InputStream input, final @NotNull Compression compression) throws IOException;

/**
* Reads a binary tag from {@code input}.
*
* <p>Doesn't read a root name from the {@link DataInput} at all, to match the wire protocol in modern game versions.</p>
*
* @param input the input stream
* @return a binary tag
* @throws IOException if an exception was encountered while reading the tag
* @since 4.15.0
* @sinceMinecraft 1.20.2
*/
@NotNull CompoundBinaryTag readNameless(final @NotNull DataInput input) throws IOException;

/**
* Reads a binary tag, with a name, from {@code path}.
*
Expand Down Expand Up @@ -377,6 +462,8 @@ public interface Writer {
*
* <p>This is the equivalent of passing {@code Compression#NONE} as the second parameter to {@link #write(CompoundBinaryTag, Path, Compression)}.</p>
*
* <p>An empty root name is written.</p>
*
* @param tag the tag to write
* @param path the path
* @throws IOException if an exception was encountered while reading the tag
Expand All @@ -389,6 +476,8 @@ default void write(final @NotNull CompoundBinaryTag tag, final @NotNull Path pat
/**
* Writes a binary tag to {@code path} with a {@code compression} type.
*
* <p>An empty root name is written.</p>
*
* @param tag the tag to write
* @param path the path
* @param compression the compression type
Expand All @@ -402,6 +491,8 @@ default void write(final @NotNull CompoundBinaryTag tag, final @NotNull Path pat
*
* <p>This is the equivalent of passing {@link Compression#NONE} as the second parameter to {@link #write(CompoundBinaryTag, OutputStream, Compression)}.</p>
*
* <p>An empty root name is written.</p>
*
* @param tag the tag to write
* @param output the output stream
* @throws IOException if an exception was encountered while reading the tag
Expand All @@ -414,6 +505,8 @@ default void write(final @NotNull CompoundBinaryTag tag, final @NotNull OutputSt
/**
* Writes a binary tag to {@code output} with a {@code compression} type.
*
* <p>An empty root name is written.</p>
*
* @param tag the tag to write
* @param output the output stream
* @param compression the compression type
Expand All @@ -425,13 +518,90 @@ default void write(final @NotNull CompoundBinaryTag tag, final @NotNull OutputSt
/**
* Writes a binary tag to {@code output}.
*
* <p>An empty root name is written.</p>
*
* @param tag the tag to write
* @param output the output
* @throws IOException if an exception was encountered while reading the tag
* @since 4.4.0
*/
void write(final @NotNull CompoundBinaryTag tag, final @NotNull DataOutput output) throws IOException;

/**
* Writes a binary tag to {@code path} with a {@code compression} type.
*
* <p>This is the equivalent of passing {@code Compression#NONE} as the second parameter to {@link #write(CompoundBinaryTag, Path, Compression)}.</p>
*
* <p>Doesn't write a root name to the {@link Path} at all, to match the wire protocol in modern game versions.</p>
*
* @param tag the tag to write
* @param path the path
* @throws IOException if an exception was encountered while reading the tag
* @since 4.15.0
* @sinceMinecraft 1.20.2
*/
default void writeNameless(final @NotNull CompoundBinaryTag tag, final @NotNull Path path) throws IOException {
this.writeNameless(tag, path, Compression.NONE);
}

/**
* Writes a binary tag to {@code path} with a {@code compression} type.
*
* <p>Doesn't write a root name to the {@link Path} at all, to match the wire protocol in modern game versions.</p>
*
* @param tag the tag to write
* @param path the path
* @param compression the compression type
* @throws IOException if an exception was encountered while reading the tag
* @since 4.15.0
* @sinceMinecraft 1.20.2
*/
void writeNameless(final @NotNull CompoundBinaryTag tag, final @NotNull Path path, final @NotNull Compression compression) throws IOException;

/**
* Writes a binary tag to {@code output}.
*
* <p>This is the equivalent of passing {@link Compression#NONE} as the second parameter to {@link #write(CompoundBinaryTag, OutputStream, Compression)}.</p>
*
* <p>Doesn't write a root name to the {@link OutputStream} at all, to match the wire protocol in modern game versions.</p>
*
* @param tag the tag to write
* @param output the output stream
* @throws IOException if an exception was encountered while reading the tag
* @since 4.15.0
* @sinceMinecraft 1.20.2
*/
default void writeNameless(final @NotNull CompoundBinaryTag tag, final @NotNull OutputStream output) throws IOException {
this.writeNameless(tag, output, Compression.NONE);
}

/**
* Writes a binary tag to {@code output} with a {@code compression} type.
*
* <p>Doesn't write a root name to the {@link OutputStream} at all, to match the wire protocol in modern game versions.</p>
*
* @param tag the tag to write
* @param output the output stream
* @param compression the compression type
* @throws IOException if an exception was encountered while reading the tag
* @since 4.15.0
* @sinceMinecraft 1.20.2
*/
void writeNameless(final @NotNull CompoundBinaryTag tag, final @NotNull OutputStream output, final @NotNull Compression compression) throws IOException;

/**
* Writes a binary tag to {@code output}.
*
* <p>Doesn't write a root name to the {@link DataOutput} at all, to match the wire protocol in modern game versions.</p>
*
* @param tag the tag to write
* @param output the output
* @throws IOException if an exception was encountered while reading the tag
* @since 4.15.0
* @sinceMinecraft 1.20.2
*/
void writeNameless(final @NotNull CompoundBinaryTag tag, final @NotNull DataOutput output) throws IOException;

/**
* Writes a binary tag, with a name, to {@code path}.
*
Expand Down
29 changes: 27 additions & 2 deletions nbt/src/main/java/net/kyori/adventure/nbt/BinaryTagReaderImpl.java
Original file line number Diff line number Diff line change
Expand Up @@ -61,17 +61,42 @@ final class BinaryTagReaderImpl implements BinaryTagIO.Reader {
}

@Override
public @NotNull CompoundBinaryTag read(@NotNull DataInput input) throws IOException {
public @NotNull CompoundBinaryTag read(final @NotNull DataInput input) throws IOException {
return this.read(input, true);
}

private @NotNull CompoundBinaryTag read(@NotNull DataInput input, final boolean named) throws IOException {
if (!(input instanceof TrackingDataInput)) {
input = new TrackingDataInput(input, this.maxBytes);
}

final BinaryTagType<? extends BinaryTag> type = BinaryTagType.binaryTagType(input.readByte());
requireCompound(type);
input.skipBytes(input.readUnsignedShort()); // read empty name
if (named) {
input.skipBytes(input.readUnsignedShort()); // read empty name
}
return BinaryTagTypes.COMPOUND.read(input);
}

@Override
public @NotNull CompoundBinaryTag readNameless(final @NotNull Path path, final BinaryTagIO.@NotNull Compression compression) throws IOException {
try (final InputStream is = Files.newInputStream(path)) {
return this.readNameless(is, compression);
}
}

@Override
public @NotNull CompoundBinaryTag readNameless(final @NotNull InputStream input, final BinaryTagIO.@NotNull Compression compression) throws IOException {
try (final DataInputStream dis = new DataInputStream(new BufferedInputStream(compression.decompress(closeShield(input))))) {
return this.readNameless((DataInput) dis);
}
}

@Override
public @NotNull CompoundBinaryTag readNameless(final @NotNull DataInput input) throws IOException {
return this.read(input, false);
}

@Override
public Map.@NotNull Entry<String, CompoundBinaryTag> readNamed(final @NotNull Path path, final BinaryTagIO.@NotNull Compression compression) throws IOException {
try (final InputStream is = Files.newInputStream(path)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,11 +54,36 @@ public void write(final @NotNull CompoundBinaryTag tag, final @NotNull OutputStr

@Override
public void write(final @NotNull CompoundBinaryTag tag, final @NotNull DataOutput output) throws IOException {
this.write(tag, output, true);
}

private void write(final @NotNull CompoundBinaryTag tag, final @NotNull DataOutput output, final boolean named) throws IOException {
output.writeByte(BinaryTagTypes.COMPOUND.id());
output.writeUTF(""); // write empty name
if (named) {
output.writeUTF(""); // write empty name
}
BinaryTagTypes.COMPOUND.write(tag, output);
}

@Override
public void writeNameless(final @NotNull CompoundBinaryTag tag, final @NotNull Path path, final BinaryTagIO.@NotNull Compression compression) throws IOException {
try (final OutputStream os = Files.newOutputStream(path)) {
this.writeNameless(tag, os, compression);
}
}

@Override
public void writeNameless(final @NotNull CompoundBinaryTag tag, final @NotNull OutputStream output, final BinaryTagIO.@NotNull Compression compression) throws IOException {
try (final DataOutputStream dos = new DataOutputStream(new BufferedOutputStream(compression.compress(closeShield(output))))) {
this.writeNameless(tag, (DataOutput) dos);
}
}

@Override
public void writeNameless(final @NotNull CompoundBinaryTag tag, final @NotNull DataOutput output) throws IOException {
this.write(tag, output, false);
}

@Override
public void writeNamed(final Map.@NotNull Entry<String, CompoundBinaryTag> tag, final @NotNull Path path, final BinaryTagIO.@NotNull Compression compression) throws IOException {
try (final OutputStream os = Files.newOutputStream(path)) {
Expand Down
10 changes: 10 additions & 0 deletions nbt/src/test/java/net/kyori/adventure/nbt/BinaryTagIOTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -60,4 +60,14 @@ void testWriteAndReadZLIBCompression() throws IOException {
BinaryTagIO.writer().write(tag, output, BinaryTagIO.Compression.ZLIB);
assertEquals(tag, BinaryTagIO.reader().read(new ByteArrayInputStream(output.toByteArray()), BinaryTagIO.Compression.ZLIB));
}

@Test
void testNamelessWriteAndReadNoCompression() throws IOException {
final CompoundBinaryTag tag = CompoundBinaryTag.builder()
.putString("name", "test")
.build();
final ByteArrayOutputStream output = new ByteArrayOutputStream();
BinaryTagIO.writer().writeNameless(tag, output);
assertEquals(tag, BinaryTagIO.reader().readNameless(new ByteArrayInputStream(output.toByteArray())));
}
}