From 1d5b6e43f29ad9bd134c4bf05a1c7ea33c54b9bf Mon Sep 17 00:00:00 2001 From: Nassim Jahnke Date: Sun, 31 Dec 2023 12:57:04 +0100 Subject: [PATCH] 4.0.0: Refactor NBTIO, replace clone with copy --- README.md | 7 +- pom.xml | 2 +- .../com/github/steveice10/opennbt/NBTIO.java | 515 ------------------ .../opennbt/conversion/ConverterRegistry.java | 2 +- .../steveice10/opennbt/tag/TagRegistry.java | 58 +- .../opennbt/tag/builtin/ByteArrayTag.java | 21 +- .../opennbt/tag/builtin/ByteTag.java | 13 +- .../opennbt/tag/builtin/CompoundTag.java | 63 ++- .../opennbt/tag/builtin/DoubleTag.java | 13 +- .../opennbt/tag/builtin/FloatTag.java | 13 +- .../opennbt/tag/builtin/IntArrayTag.java | 22 +- .../opennbt/tag/builtin/IntTag.java | 13 +- .../opennbt/tag/builtin/ListTag.java | 103 ++-- .../opennbt/tag/builtin/LongArrayTag.java | 22 +- .../opennbt/tag/builtin/LongTag.java | 13 +- .../opennbt/tag/builtin/ShortTag.java | 13 +- .../opennbt/tag/builtin/StringTag.java | 14 +- .../steveice10/opennbt/tag/builtin/Tag.java | 59 +- .../steveice10/opennbt/tag/io/NBTIO.java | 86 +++ .../steveice10/opennbt/tag/io/TagReader.java | 92 ++++ .../steveice10/opennbt/tag/io/TagWriter.java | 77 +++ .../opennbt/tag/limiter/NoopTagLimiter.java | 4 + .../opennbt/tag/limiter/TagLimiter.java | 5 + .../opennbt/tag/limiter/TagLimiterImpl.java | 5 + 24 files changed, 481 insertions(+), 754 deletions(-) delete mode 100644 src/main/java/com/github/steveice10/opennbt/NBTIO.java create mode 100644 src/main/java/com/github/steveice10/opennbt/tag/io/NBTIO.java create mode 100644 src/main/java/com/github/steveice10/opennbt/tag/io/TagReader.java create mode 100644 src/main/java/com/github/steveice10/opennbt/tag/io/TagWriter.java diff --git a/README.md b/README.md index e216001..d7acfc5 100644 --- a/README.md +++ b/README.md @@ -9,9 +9,8 @@ This project is derived from an earlier version of [OpenNBT](https://github.com/ * Add primitive getter methods to number types * Don't wrap values given in Tag#setValue / Tag constructors * Abstract NumberTag class for easier number handling -* Always read/write CompoundTags in NBTIO * Don't use reflection when creating tag instances -* Directly use value in clone() +* Directly use value in copy(), also replacing clone() * Implement tag specific equals() methods * Update to Java 8 @@ -32,7 +31,7 @@ This project also includes code from [adventure](https://github.com/KyoriPowered com.viaversion nbt - 3.2.0 + 4.0.0 ``` @@ -44,7 +43,7 @@ repositories { } dependencies { - implementation("com.viaversion:nbt:3.0.0") + implementation("com.viaversion:nbt:4.0.0") } ``` diff --git a/pom.xml b/pom.xml index 20404b7..8ff36b6 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ com.viaversion nbt - 3.5.0 + 4.0.0 jar ViaNBT diff --git a/src/main/java/com/github/steveice10/opennbt/NBTIO.java b/src/main/java/com/github/steveice10/opennbt/NBTIO.java deleted file mode 100644 index 1619f19..0000000 --- a/src/main/java/com/github/steveice10/opennbt/NBTIO.java +++ /dev/null @@ -1,515 +0,0 @@ -package com.github.steveice10.opennbt; - -import com.github.steveice10.opennbt.tag.builtin.CompoundTag; -import com.github.steveice10.opennbt.tag.limiter.TagLimiter; -import java.io.DataInput; -import java.io.DataInputStream; -import java.io.DataOutput; -import java.io.DataOutputStream; -import java.io.EOFException; -import java.io.File; -import java.io.FilterInputStream; -import java.io.FilterOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.nio.charset.StandardCharsets; -import java.nio.file.Files; -import java.util.zip.GZIPInputStream; -import java.util.zip.GZIPOutputStream; - -/** - * A class containing methods for reading/writing NBT tags. - */ -public final class NBTIO { - - /** - * Reads the compressed, big endian root CompoundTag from the given file. - * - * @param path Path of the file. - * @return The read compound tag. - * @throws java.io.IOException If an I/O error occurs. - */ - public static CompoundTag readFile(String path) throws IOException { - return readFile(new File(path)); - } - - /** - * Reads the compressed, big endian root CompoundTag from the given file. - * - * @param file File to read from. - * @return The read compound tag. - * @throws java.io.IOException If an I/O error occurs. - */ - public static CompoundTag readFile(File file) throws IOException { - return readFile(file, true, false); - } - - /** - * Reads the root CompoundTag from the given file. - * - * @param path Path of the file. - * @param compressed Whether the NBT file is compressed. - * @param littleEndian Whether the NBT file is little endian. - * @return The read compound tag. - * @throws java.io.IOException If an I/O error occurs. - */ - public static CompoundTag readFile(String path, boolean compressed, boolean littleEndian) throws IOException { - return readFile(new File(path), compressed, littleEndian); - } - - /** - * Reads the root CompoundTag from the given file. - * - * @param file File to read from. - * @param compressed Whether the NBT file is compressed. - * @param littleEndian Whether the NBT file is little endian. - * @return The read compound tag. - * @throws java.io.IOException If an I/O error occurs. - */ - public static CompoundTag readFile(File file, boolean compressed, boolean littleEndian) throws IOException { - InputStream in = Files.newInputStream(file.toPath()); - try { - if (compressed) { - in = new GZIPInputStream(in); - } - - return readTag(in, littleEndian); - } finally { - in.close(); - } - } - - /** - * Writes the given root CompoundTag to the given file, compressed and in big endian. - * - * @param tag Tag to write. - * @param path Path to write to. - * @throws java.io.IOException If an I/O error occurs. - */ - public static void writeFile(CompoundTag tag, String path) throws IOException { - writeFile(tag, new File(path)); - } - - /** - * Writes the given root CompoundTag to the given file, compressed and in big endian. - * - * @param tag Tag to write. - * @param file File to write to. - * @throws java.io.IOException If an I/O error occurs. - */ - public static void writeFile(CompoundTag tag, File file) throws IOException { - writeFile(tag, file, true, false); - } - - /** - * Writes the given root CompoundTag to the given file. - * - * @param tag Tag to write. - * @param path Path to write to. - * @param compressed Whether the NBT file should be compressed. - * @param littleEndian Whether to write little endian NBT. - * @throws java.io.IOException If an I/O error occurs. - */ - public static void writeFile(CompoundTag tag, String path, boolean compressed, boolean littleEndian) throws IOException { - writeFile(tag, new File(path), compressed, littleEndian); - } - - /** - * Writes the given root CompoundTag to the given file. - * - * @param tag Tag to write. - * @param file File to write to. - * @param compressed Whether the NBT file should be compressed. - * @param littleEndian Whether to write little endian NBT. - * @throws java.io.IOException If an I/O error occurs. - */ - public static void writeFile(CompoundTag tag, File file, boolean compressed, boolean littleEndian) throws IOException { - if (!file.exists()) { - if (file.getParentFile() != null) { - file.getParentFile().mkdirs(); - } - - file.createNewFile(); - } - - OutputStream out = Files.newOutputStream(file.toPath()); - try { - if (compressed) { - out = new GZIPOutputStream(out); - } - - writeTag(out, tag, littleEndian); - } finally { - out.close(); - } - } - - /** - * Reads a big endian NBT tag. - * - * @param in Input stream to read from. - * @return The read tag, or null if the tag is an end tag. - * @throws java.io.IOException If an I/O error occurs. - */ - public static CompoundTag readTag(InputStream in) throws IOException { - return readTag(in, TagLimiter.noop()); - } - - /** - * Reads a big endian NBT tag. - * - * @param in Input stream to read from. - * @return The read tag, or null if the tag is an end tag. - * @throws java.io.IOException If an I/O error occurs. - */ - public static CompoundTag readTag(InputStream in, TagLimiter tagLimiter) throws IOException { - return readTag((DataInput) new DataInputStream(in), tagLimiter); - } - - /** - * Reads an NBT tag. - * - * @param in Input stream to read from. - * @param littleEndian Whether to read little endian NBT. - * @return The read tag, or null if the tag is an end tag. - * @throws java.io.IOException If an I/O error occurs. - */ - public static CompoundTag readTag(InputStream in, boolean littleEndian) throws IOException { - return readTag((DataInput) (littleEndian ? new LittleEndianDataInputStream(in) : new DataInputStream(in))); - } - - /** - * Reads an NBT tag. - * - * @param in Data input to read from. - * @return The read tag, or null if the tag is an end tag. - * @throws java.io.IOException If an I/O error occurs. - */ - public static CompoundTag readTag(DataInput in) throws IOException { - return readTag(in, TagLimiter.noop()); - } - - /** - * Reads an NBT tag. - * - * @param in Data input to read from. - * @param tagLimiter taglimiter - * @return The read tag, or null if the tag is an end tag. - * @throws java.io.IOException If an I/O error occurs. - */ - public static CompoundTag readTag(DataInput in, TagLimiter tagLimiter) throws IOException { - int id = in.readByte(); - if (id != CompoundTag.ID) { - throw new IOException(String.format("Expected root tag to be a CompoundTag, was %s", id)); - } - - // Empty name - in.skipBytes(in.readUnsignedShort()); - - CompoundTag tag = new CompoundTag(); - tag.read(in, tagLimiter); - return tag; - } - - /** - * Writes an NBT tag in big endian. - * - * @param out Output stream to write to. - * @param tag Tag to write. - * @throws java.io.IOException If an I/O error occurs. - */ - public static void writeTag(OutputStream out, CompoundTag tag) throws IOException { - writeTag(out, tag, false); - } - - /** - * Writes an NBT tag. - * - * @param out Output stream to write to. - * @param tag Tag to write. - * @param littleEndian Whether to write little endian NBT. - * @throws java.io.IOException If an I/O error occurs. - */ - public static void writeTag(OutputStream out, CompoundTag tag, boolean littleEndian) throws IOException { - writeTag((DataOutput) (littleEndian ? new LittleEndianDataOutputStream(out) : new DataOutputStream(out)), tag); - } - - /** - * Writes an NBT tag. - * - * @param out Data output to write to. - * @param tag Tag to write. - * @throws java.io.IOException If an I/O error occurs. - */ - public static void writeTag(DataOutput out, CompoundTag tag) throws IOException { - out.writeByte(CompoundTag.ID); - out.writeUTF(""); // Empty name - tag.write(out); - } - - private static final class LittleEndianDataInputStream extends FilterInputStream implements DataInput { - - private LittleEndianDataInputStream(InputStream in) { - super(in); - } - - @Override - public int read(byte[] b) throws IOException { - return this.in.read(b, 0, b.length); - - } - - @Override - public int read(byte[] b, int off, int len) throws IOException { - return this.in.read(b, off, len); - } - - @Override - public void readFully(byte[] b) throws IOException { - this.readFully(b, 0, b.length); - } - - @Override - public void readFully(byte[] b, int off, int len) throws IOException { - if (len < 0) { - throw new IndexOutOfBoundsException(); - } else { - int read; - for (int pos = 0; pos < len; pos += read) { - read = this.in.read(b, off + pos, len - pos); - if (read < 0) { - throw new EOFException(); - } - } - } - } - - @Override - public int skipBytes(int n) throws IOException { - int total = 0; - int skipped = 0; - while (total < n && (skipped = (int) this.in.skip(n - total)) > 0) { - total += skipped; - } - - return total; - } - - @Override - public boolean readBoolean() throws IOException { - int val = this.in.read(); - if (val < 0) { - throw new EOFException(); - } - - return val != 0; - } - - @Override - public byte readByte() throws IOException { - int val = this.in.read(); - if (val < 0) { - throw new EOFException(); - } - - return (byte) val; - } - - @Override - public int readUnsignedByte() throws IOException { - int val = this.in.read(); - if (val < 0) { - throw new EOFException(); - } - - return val; - } - - @Override - public short readShort() throws IOException { - int b1 = this.in.read(); - int b2 = this.in.read(); - if ((b1 | b2) < 0) { - throw new EOFException(); - } - - return (short) (b1 | (b2 << 8)); - } - - @Override - public int readUnsignedShort() throws IOException { - int b1 = this.in.read(); - int b2 = this.in.read(); - if ((b1 | b2) < 0) { - throw new EOFException(); - } - - return b1 | (b2 << 8); - } - - @Override - public char readChar() throws IOException { - int b1 = this.in.read(); - int b2 = this.in.read(); - if ((b1 | b2) < 0) { - throw new EOFException(); - } - - return (char) (b1 | (b2 << 8)); - } - - @Override - public int readInt() throws IOException { - int b1 = this.in.read(); - int b2 = this.in.read(); - int b3 = this.in.read(); - int b4 = this.in.read(); - if ((b1 | b2 | b3 | b4) < 0) { - throw new EOFException(); - } - - return b1 | (b2 << 8) | (b3 << 16) | (b4 << 24); - } - - @Override - public long readLong() throws IOException { - long b1 = this.in.read(); - long b2 = this.in.read(); - long b3 = this.in.read(); - long b4 = this.in.read(); - long b5 = this.in.read(); - long b6 = this.in.read(); - long b7 = this.in.read(); - long b8 = this.in.read(); - if ((b1 | b2 | b3 | b4 | b5 | b6 | b7 | b8) < 0) { - throw new EOFException(); - } - - return b1 | (b2 << 8) | (b3 << 16) | (b4 << 24) | (b5 << 32) | (b6 << 40) | (b7 << 48) | (b8 << 56); - } - - @Override - public float readFloat() throws IOException { - return Float.intBitsToFloat(this.readInt()); - } - - @Override - public double readDouble() throws IOException { - return Double.longBitsToDouble(this.readLong()); - } - - @Override - public String readLine() throws IOException { - throw new UnsupportedOperationException("Use readUTF."); - } - - @Override - public String readUTF() throws IOException { - byte[] bytes = new byte[this.readUnsignedShort()]; - this.readFully(bytes); - - return new String(bytes, StandardCharsets.UTF_8); - } - } - - private static final class LittleEndianDataOutputStream extends FilterOutputStream implements DataOutput { - - private LittleEndianDataOutputStream(OutputStream out) { - super(out); - } - - @Override - public synchronized void write(int b) throws IOException { - this.out.write(b); - } - - @Override - public synchronized void write(byte[] b, int off, int len) throws IOException { - this.out.write(b, off, len); - } - - @Override - public void flush() throws IOException { - this.out.flush(); - } - - @Override - public void writeBoolean(boolean b) throws IOException { - this.out.write(b ? 1 : 0); - } - - @Override - public void writeByte(int b) throws IOException { - this.out.write(b); - } - - @Override - public void writeShort(int s) throws IOException { - this.out.write(s & 0xFF); - this.out.write((s >>> 8) & 0xFF); - } - - @Override - public void writeChar(int c) throws IOException { - this.out.write(c & 0xFF); - this.out.write((c >>> 8) & 0xFF); - } - - @Override - public void writeInt(int i) throws IOException { - this.out.write(i & 0xFF); - this.out.write((i >>> 8) & 0xFF); - this.out.write((i >>> 16) & 0xFF); - this.out.write((i >>> 24) & 0xFF); - } - - @Override - public void writeLong(long l) throws IOException { - this.out.write((int) (l & 0xFF)); - this.out.write((int) ((l >>> 8) & 0xFF)); - this.out.write((int) ((l >>> 16) & 0xFF)); - this.out.write((int) ((l >>> 24) & 0xFF)); - this.out.write((int) ((l >>> 32) & 0xFF)); - this.out.write((int) ((l >>> 40) & 0xFF)); - this.out.write((int) ((l >>> 48) & 0xFF)); - this.out.write((int) ((l >>> 56) & 0xFF)); - } - - @Override - public void writeFloat(float f) throws IOException { - this.writeInt(Float.floatToIntBits(f)); - } - - @Override - public void writeDouble(double d) throws IOException { - this.writeLong(Double.doubleToLongBits(d)); - } - - @Override - public void writeBytes(String s) throws IOException { - int len = s.length(); - for (int index = 0; index < len; index++) { - this.out.write((byte) s.charAt(index)); - } - } - - @Override - public void writeChars(String s) throws IOException { - int len = s.length(); - for (int index = 0; index < len; index++) { - char c = s.charAt(index); - this.out.write(c & 0xFF); - this.out.write((c >>> 8) & 0xFF); - } - } - - @Override - public void writeUTF(String s) throws IOException { - byte[] bytes = s.getBytes(StandardCharsets.UTF_8); - - this.writeShort(bytes.length); - this.write(bytes); - } - } -} diff --git a/src/main/java/com/github/steveice10/opennbt/conversion/ConverterRegistry.java b/src/main/java/com/github/steveice10/opennbt/conversion/ConverterRegistry.java index c6595fe..5b40544 100644 --- a/src/main/java/com/github/steveice10/opennbt/conversion/ConverterRegistry.java +++ b/src/main/java/com/github/steveice10/opennbt/conversion/ConverterRegistry.java @@ -36,7 +36,7 @@ import org.jetbrains.annotations.Nullable; /** * A registry mapping tags and value types to converters. */ -public class ConverterRegistry { +public final class ConverterRegistry { private static final Int2ObjectMap> TAG_TO_CONVERTER = new Int2ObjectOpenHashMap<>(); private static final Map, TagConverter> TYPE_TO_CONVERTER = new HashMap<>(); diff --git a/src/main/java/com/github/steveice10/opennbt/tag/TagRegistry.java b/src/main/java/com/github/steveice10/opennbt/tag/TagRegistry.java index 17d71a2..4919afd 100644 --- a/src/main/java/com/github/steveice10/opennbt/tag/TagRegistry.java +++ b/src/main/java/com/github/steveice10/opennbt/tag/TagRegistry.java @@ -13,15 +13,18 @@ import com.github.steveice10.opennbt.tag.builtin.LongTag; import com.github.steveice10.opennbt.tag.builtin.ShortTag; import com.github.steveice10.opennbt.tag.builtin.StringTag; import com.github.steveice10.opennbt.tag.builtin.Tag; +import com.github.steveice10.opennbt.tag.limiter.TagLimiter; import it.unimi.dsi.fastutil.objects.Object2IntMap; import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap; -import java.util.function.Supplier; +import java.io.DataInput; +import java.io.IOException; import org.jetbrains.annotations.Nullable; /** * A registry containing different tag classes. */ public final class TagRegistry { + public static final int END = 0; private static final int HIGHEST_ID = LongArrayTag.ID; private static final RegisteredTagType[] TAGS = new RegisteredTagType[HIGHEST_ID + 1]; private static final Object2IntMap> TAG_TO_ID = new Object2IntOpenHashMap<>(); @@ -29,18 +32,18 @@ public final class TagRegistry { static { TAG_TO_ID.defaultReturnValue(-1); - register(ByteTag.ID, ByteTag.class, ByteTag::new); - register(ShortTag.ID, ShortTag.class, ShortTag::new); - register(IntTag.ID, IntTag.class, IntTag::new); - register(LongTag.ID, LongTag.class, LongTag::new); - register(FloatTag.ID, FloatTag.class, FloatTag::new); - register(DoubleTag.ID, DoubleTag.class, DoubleTag::new); - register(ByteArrayTag.ID, ByteArrayTag.class, ByteArrayTag::new); - register(StringTag.ID, StringTag.class, StringTag::new); - register(ListTag.ID, ListTag.class, ListTag::new); - register(CompoundTag.ID, CompoundTag.class, CompoundTag::new); - register(IntArrayTag.ID, IntArrayTag.class, IntArrayTag::new); - register(LongArrayTag.ID, LongArrayTag.class, LongArrayTag::new); + register(ByteTag.ID, ByteTag.class, (in, tagLimiter, nestingLevel) -> ByteTag.read(in, tagLimiter)); + register(ShortTag.ID, ShortTag.class, (in, tagLimiter, nestingLevel) -> ShortTag.read(in, tagLimiter)); + register(IntTag.ID, IntTag.class, (in, tagLimiter, nestingLevel) -> IntTag.read(in, tagLimiter)); + register(LongTag.ID, LongTag.class, (in, tagLimiter, nestingLevel) -> LongTag.read(in, tagLimiter)); + register(FloatTag.ID, FloatTag.class, (in, tagLimiter, nestingLevel) -> FloatTag.read(in, tagLimiter)); + register(DoubleTag.ID, DoubleTag.class, (in, tagLimiter, nestingLevel) -> DoubleTag.read(in, tagLimiter)); + register(ByteArrayTag.ID, ByteArrayTag.class, (in, tagLimiter, nestingLevel) -> ByteArrayTag.read(in, tagLimiter)); + register(StringTag.ID, StringTag.class, (in, tagLimiter, nestingLevel) -> StringTag.read(in, tagLimiter)); + register(ListTag.ID, ListTag.class, ListTag::read); + register(CompoundTag.ID, CompoundTag.class, CompoundTag::read); + register(IntArrayTag.ID, IntArrayTag.class, (in, tagLimiter, nestingLevel) -> IntArrayTag.read(in, tagLimiter)); + register(LongArrayTag.ID, LongArrayTag.class, (in, tagLimiter, nestingLevel) -> LongArrayTag.read(in, tagLimiter)); } /** @@ -50,7 +53,7 @@ public final class TagRegistry { * @param tag Tag class to register. * @throws IllegalArgumentException if the id is unexpectedly out of bounds, or if the id or tag have already been registered */ - public static void register(int id, Class tag, Supplier supplier) { + public static void register(int id, Class tag, TagSupplier supplier) { if (id < 0 || id > HIGHEST_ID) { throw new IllegalArgumentException("Tag ID must be between 0 and " + HIGHEST_ID); } @@ -65,16 +68,6 @@ public final class TagRegistry { TAG_TO_ID.put(tag, id); } - /** - * Unregisters a tag class. - * - * @param id ID of the tag to unregister. - */ - public static void unregister(int id) { - TAG_TO_ID.removeInt(getClassFor(id)); - TAGS[id] = null; - } - /** * Gets the tag class with the given id. * @@ -103,23 +96,28 @@ public final class TagRegistry { * @return The created tag. * @throws IllegalArgumentException if no tags is registered over the provided id */ - public static Tag createInstance(int id) { - Supplier supplier = id > 0 && id < TAGS.length ? TAGS[id].supplier : null; + public static Tag read(int id, DataInput in, TagLimiter tagLimiter, int nestingLevel) throws IOException { + TagSupplier supplier = id > 0 && id < TAGS.length ? TAGS[id].supplier : null; if (supplier == null) { throw new IllegalArgumentException("Could not find tag with ID \"" + id + "\"."); } - - return supplier.get(); + return supplier.create(in, tagLimiter, nestingLevel); } private static final class RegisteredTagType { private final Class type; - private final Supplier supplier; + private final TagSupplier supplier; - private RegisteredTagType(final Class type, final Supplier supplier) { + private RegisteredTagType(final Class type, final TagSupplier supplier) { this.type = type; this.supplier = supplier; } } + + @FunctionalInterface + public interface TagSupplier { + + T create(DataInput in, TagLimiter tagLimiter, int nestingLevel) throws IOException; + } } diff --git a/src/main/java/com/github/steveice10/opennbt/tag/builtin/ByteArrayTag.java b/src/main/java/com/github/steveice10/opennbt/tag/builtin/ByteArrayTag.java index 0752575..1229a21 100644 --- a/src/main/java/com/github/steveice10/opennbt/tag/builtin/ByteArrayTag.java +++ b/src/main/java/com/github/steveice10/opennbt/tag/builtin/ByteArrayTag.java @@ -27,9 +27,20 @@ public class ByteArrayTag extends NumberArrayTag { * @param value The value of the tag. */ public ByteArrayTag(byte[] value) { + if (value == null) { + throw new NullPointerException("value cannot be null"); + } this.value = value; } + public static ByteArrayTag read(DataInput in, TagLimiter tagLimiter) throws IOException { + tagLimiter.countInt(); + byte[] value = new byte[in.readInt()]; + tagLimiter.countBytes(value.length); + in.readFully(value); + return new ByteArrayTag(value); + } + @Override public byte[] getValue() { return this.value; @@ -87,14 +98,6 @@ public class ByteArrayTag extends NumberArrayTag { return list; } - @Override - public void read(DataInput in, TagLimiter tagLimiter, int nestingLevel) throws IOException { - tagLimiter.countInt(); - this.value = new byte[in.readInt()]; - tagLimiter.countBytes(this.value.length); - in.readFully(this.value); - } - @Override public void write(DataOutput out) throws IOException { out.writeInt(this.value.length); @@ -115,7 +118,7 @@ public class ByteArrayTag extends NumberArrayTag { } @Override - public final ByteArrayTag clone() { + public ByteArrayTag copy() { return new ByteArrayTag(this.value); } diff --git a/src/main/java/com/github/steveice10/opennbt/tag/builtin/ByteTag.java b/src/main/java/com/github/steveice10/opennbt/tag/builtin/ByteTag.java index 6bf6b55..306e75b 100644 --- a/src/main/java/com/github/steveice10/opennbt/tag/builtin/ByteTag.java +++ b/src/main/java/com/github/steveice10/opennbt/tag/builtin/ByteTag.java @@ -28,6 +28,11 @@ public class ByteTag extends NumberTag { this.value = value; } + public static ByteTag read(DataInput in, TagLimiter tagLimiter) throws IOException { + tagLimiter.countByte(); + return new ByteTag(in.readByte()); + } + /** * @deprecated use {@link #asByte()} */ @@ -51,12 +56,6 @@ public class ByteTag extends NumberTag { this.value = value; } - @Override - public void read(DataInput in, TagLimiter tagLimiter, int nestingLevel) throws IOException { - tagLimiter.countByte(); - this.value = in.readByte(); - } - @Override public void write(DataOutput out) throws IOException { out.writeByte(this.value); @@ -76,7 +75,7 @@ public class ByteTag extends NumberTag { } @Override - public final ByteTag clone() { + public ByteTag copy() { return new ByteTag(this.value); } diff --git a/src/main/java/com/github/steveice10/opennbt/tag/builtin/CompoundTag.java b/src/main/java/com/github/steveice10/opennbt/tag/builtin/CompoundTag.java index 0ca1949..c61af11 100644 --- a/src/main/java/com/github/steveice10/opennbt/tag/builtin/CompoundTag.java +++ b/src/main/java/com/github/steveice10/opennbt/tag/builtin/CompoundTag.java @@ -4,7 +4,6 @@ import com.github.steveice10.opennbt.tag.TagRegistry; import com.github.steveice10.opennbt.tag.limiter.TagLimiter; import java.io.DataInput; import java.io.DataOutput; -import java.io.EOFException; import java.io.IOException; import java.util.Collection; import java.util.Iterator; @@ -49,11 +48,43 @@ public class CompoundTag extends Tag implements Iterable> { this.value = value; } + public static CompoundTag read(DataInput in, TagLimiter tagLimiter, int nestingLevel) throws IOException { + tagLimiter.checkLevel(nestingLevel); + int newNestingLevel = nestingLevel + 1; + int id; + + CompoundTag compoundTag = new CompoundTag(); + while (true) { + tagLimiter.countByte(); + id = in.readByte(); + if (id == TagRegistry.END) { + break; + } + + String name = in.readUTF(); + tagLimiter.countBytes(2 * name.length()); + + Tag tag; + try { + tag = TagRegistry.read(id, in, tagLimiter, newNestingLevel); + } catch (IllegalArgumentException e) { + throw new IOException("Failed to create tag.", e); + } + compoundTag.value.put(name, tag); + } + return compoundTag; + } + @Override public Map getValue() { return this.value; } + @Override + public String asRawString() { + return this.value.toString(); + } + /** * Sets the value of this tag. * @@ -218,32 +249,6 @@ public class CompoundTag extends Tag implements Iterable> { return this.value.entrySet().iterator(); } - @Override - public void read(DataInput in, TagLimiter tagLimiter, int nestingLevel) throws IOException { - try { - tagLimiter.checkLevel(nestingLevel); - int newNestingLevel = nestingLevel + 1; - int id; - while (true) { - tagLimiter.countByte(); - id = in.readByte(); - if (id == 0) { - // End tag - break; - } - - String name = in.readUTF(); - tagLimiter.countBytes(2 * name.length()); - - Tag tag = TagRegistry.createInstance(id); - tag.read(in, tagLimiter, newNestingLevel); - this.value.put(name, tag); - } - } catch (EOFException ignored) { - throw new IOException("Closing tag was not found!"); - } - } - @Override public void write(DataOutput out) throws IOException { for (Entry entry : this.value.entrySet()) { @@ -271,10 +276,10 @@ public class CompoundTag extends Tag implements Iterable> { } @Override - public final CompoundTag clone() { + public CompoundTag copy() { LinkedHashMap newMap = new LinkedHashMap<>(); for (Entry entry : this.value.entrySet()) { - newMap.put(entry.getKey(), entry.getValue().clone()); + newMap.put(entry.getKey(), entry.getValue().copy()); } return new CompoundTag(newMap); diff --git a/src/main/java/com/github/steveice10/opennbt/tag/builtin/DoubleTag.java b/src/main/java/com/github/steveice10/opennbt/tag/builtin/DoubleTag.java index 0730459..5bc3b9e 100644 --- a/src/main/java/com/github/steveice10/opennbt/tag/builtin/DoubleTag.java +++ b/src/main/java/com/github/steveice10/opennbt/tag/builtin/DoubleTag.java @@ -28,6 +28,11 @@ public class DoubleTag extends NumberTag { this.value = value; } + public static DoubleTag read(DataInput in, TagLimiter tagLimiter) throws IOException { + tagLimiter.countDouble(); + return new DoubleTag(in.readDouble()); + } + /** * @deprecated use {@link #asDouble()} */ @@ -51,12 +56,6 @@ public class DoubleTag extends NumberTag { this.value = value; } - @Override - public void read(DataInput in, TagLimiter tagLimiter, int nestingLevel) throws IOException { - tagLimiter.countDouble(); - this.value = in.readDouble(); - } - @Override public void write(DataOutput out) throws IOException { out.writeDouble(this.value); @@ -76,7 +75,7 @@ public class DoubleTag extends NumberTag { } @Override - public final DoubleTag clone() { + public DoubleTag copy() { return new DoubleTag(this.value); } diff --git a/src/main/java/com/github/steveice10/opennbt/tag/builtin/FloatTag.java b/src/main/java/com/github/steveice10/opennbt/tag/builtin/FloatTag.java index 3291f4b..09443f8 100644 --- a/src/main/java/com/github/steveice10/opennbt/tag/builtin/FloatTag.java +++ b/src/main/java/com/github/steveice10/opennbt/tag/builtin/FloatTag.java @@ -28,6 +28,11 @@ public class FloatTag extends NumberTag { this.value = value; } + public static FloatTag read(DataInput in, TagLimiter tagLimiter) throws IOException { + tagLimiter.countFloat(); + return new FloatTag(in.readFloat()); + } + /** * @deprecated use {@link #asFloat()} */ @@ -51,12 +56,6 @@ public class FloatTag extends NumberTag { this.value = value; } - @Override - public void read(DataInput in, TagLimiter tagLimiter, int nestingLevel) throws IOException { - tagLimiter.countFloat(); - this.value = in.readFloat(); - } - @Override public void write(DataOutput out) throws IOException { out.writeFloat(this.value); @@ -76,7 +75,7 @@ public class FloatTag extends NumberTag { } @Override - public final FloatTag clone() { + public FloatTag copy() { return new FloatTag(this.value); } diff --git a/src/main/java/com/github/steveice10/opennbt/tag/builtin/IntArrayTag.java b/src/main/java/com/github/steveice10/opennbt/tag/builtin/IntArrayTag.java index 5a1d21b..1906641 100644 --- a/src/main/java/com/github/steveice10/opennbt/tag/builtin/IntArrayTag.java +++ b/src/main/java/com/github/steveice10/opennbt/tag/builtin/IntArrayTag.java @@ -33,6 +33,16 @@ public class IntArrayTag extends NumberArrayTag { this.value = value; } + public static IntArrayTag read(final DataInput in, final TagLimiter tagLimiter) throws IOException { + tagLimiter.countInt(); + int[] value = new int[in.readInt()]; + tagLimiter.countBytes(Integer.BYTES * value.length); + for (int index = 0; index < value.length; index++) { + value[index] = in.readInt(); + } + return new IntArrayTag(value); + } + @Override public int[] getValue() { return this.value; @@ -89,16 +99,6 @@ public class IntArrayTag extends NumberArrayTag { return list; } - @Override - public void read(final DataInput in, final TagLimiter tagLimiter, final int nestingLevel) throws IOException { - tagLimiter.countInt(); - this.value = new int[in.readInt()]; - tagLimiter.countBytes(4 * this.value.length); - for (int index = 0; index < this.value.length; index++) { - this.value[index] = in.readInt(); - } - } - @Override public void write(final DataOutput out) throws IOException { out.writeInt(this.value.length); @@ -121,7 +121,7 @@ public class IntArrayTag extends NumberArrayTag { } @Override - public final IntArrayTag clone() { + public IntArrayTag copy() { return new IntArrayTag(this.value.clone()); } diff --git a/src/main/java/com/github/steveice10/opennbt/tag/builtin/IntTag.java b/src/main/java/com/github/steveice10/opennbt/tag/builtin/IntTag.java index 015a556..6aa884c 100644 --- a/src/main/java/com/github/steveice10/opennbt/tag/builtin/IntTag.java +++ b/src/main/java/com/github/steveice10/opennbt/tag/builtin/IntTag.java @@ -28,6 +28,11 @@ public class IntTag extends NumberTag { this.value = value; } + public static IntTag read(DataInput in, TagLimiter tagLimiter) throws IOException { + tagLimiter.countInt(); + return new IntTag(in.readInt()); + } + /** * @deprecated use {@link #asInt()} */ @@ -51,12 +56,6 @@ public class IntTag extends NumberTag { this.value = value; } - @Override - public void read(DataInput in, TagLimiter tagLimiter, int nestingLevel) throws IOException { - tagLimiter.countInt(); - this.value = in.readInt(); - } - @Override public void write(DataOutput out) throws IOException { out.writeInt(this.value); @@ -76,7 +75,7 @@ public class IntTag extends NumberTag { } @Override - public final IntTag clone() { + public IntTag copy() { return new IntTag(this.value); } diff --git a/src/main/java/com/github/steveice10/opennbt/tag/builtin/ListTag.java b/src/main/java/com/github/steveice10/opennbt/tag/builtin/ListTag.java index 087f3bd..eecc625 100644 --- a/src/main/java/com/github/steveice10/opennbt/tag/builtin/ListTag.java +++ b/src/main/java/com/github/steveice10/opennbt/tag/builtin/ListTag.java @@ -16,7 +16,7 @@ import org.jetbrains.annotations.Nullable; */ public class ListTag extends Tag implements Iterable { public static final int ID = 9; - private final List value; + private List value; private Class type; /** @@ -28,6 +28,7 @@ public class ListTag extends Tag implements Iterable { /** * Creates an empty list tag and type. + * * @param type Tag type of the list. */ public ListTag(@Nullable Class type) { @@ -43,15 +44,47 @@ public class ListTag extends Tag implements Iterable { * @throws IllegalArgumentException If all tags in the list are not of the same type. */ public ListTag(List value) throws IllegalArgumentException { - this.value = new ArrayList<>(value.size()); this.setValue(value); } + public static ListTag read(DataInput in, TagLimiter tagLimiter, int nestingLevel) throws IOException { + tagLimiter.checkLevel(nestingLevel); + tagLimiter.countBytes(Byte.BYTES + Integer.BYTES); + + int id = in.readByte(); + Class type = null; + if (id != TagRegistry.END) { + type = TagRegistry.getClassFor(id); + if (type == null) { + throw new IOException("Unknown tag ID in ListTag: " + id); + } + } + + ListTag listTag = new ListTag(type); + int count = in.readInt(); + int newNestingLevel = nestingLevel + 1; + for (int index = 0; index < count; index++) { + Tag tag; + try { + tag = TagRegistry.read(id, in, tagLimiter, newNestingLevel); + } catch (IllegalArgumentException e) { + throw new IOException("Failed to create tag.", e); + } + listTag.add(tag); + } + return listTag; + } + @Override public List getValue() { return this.value; } + @Override + public String asRawString() { + return this.value.toString(); + } + /** * Sets the value of this tag. * The list tag's type will be set to that of the first tag being added, or null if the given list is empty. @@ -60,15 +93,14 @@ public class ListTag extends Tag implements Iterable { * @throws IllegalArgumentException If all tags in the list are not of the same type. */ public void setValue(List value) throws IllegalArgumentException { - if (value == null) { - throw new NullPointerException("value cannot be null"); - } - - this.type = null; - this.value.clear(); - - for (Tag tag : value) { - this.add(tag); + this.value = new ArrayList<>(value); + if (!value.isEmpty()) { + this.type = value.get(0).getClass(); + for (int i = 1; i < value.size(); i++) { + this.checkType(value.get(i)); + } + } else { + this.type = null; } } @@ -90,20 +122,22 @@ public class ListTag extends Tag implements Iterable { * @throws IllegalArgumentException If the tag's type differs from the list tag's type. */ public boolean add(Tag tag) throws IllegalArgumentException { - if (tag == null) { - throw new NullPointerException("tag cannot be null"); - } - - // If empty list, use this as tag type. + // If currently empty, use this as the tag type if (this.type == null) { this.type = tag.getClass(); } else if (tag.getClass() != this.type) { - throw new IllegalArgumentException("Tag type " + tag.getClass().getSimpleName() + " differs from list type " + this.type.getSimpleName()); + this.checkType(tag); } return this.value.add(tag); } + private void checkType(Tag tag) throws IllegalArgumentException { + if (tag.getClass() != this.type) { + throw new IllegalArgumentException("Tag type " + tag.getClass().getSimpleName() + " differs from list type " + this.type.getSimpleName()); + } + } + /** * Removes a tag from this list tag. * @@ -143,39 +177,10 @@ public class ListTag extends Tag implements Iterable { return this.value.iterator(); } - @Override - public void read(DataInput in, TagLimiter tagLimiter, int nestingLevel) throws IOException { - this.type = null; - - tagLimiter.checkLevel(nestingLevel); - tagLimiter.countBytes(1 + 4); - int id = in.readByte(); - if (id != 0) { - this.type = TagRegistry.getClassFor(id); - if (this.type == null) { - throw new IOException("Unknown tag ID in ListTag: " + id); - } - } - - int count = in.readInt(); - int newNestingLevel = nestingLevel + 1; - for (int index = 0; index < count; index++) { - Tag tag; - try { - tag = TagRegistry.createInstance(id); - } catch (IllegalArgumentException e) { - throw new IOException("Failed to create tag.", e); - } - - tag.read(in, tagLimiter, newNestingLevel); - this.add(tag); - } - } - @Override public void write(DataOutput out) throws IOException { if (this.type == null) { - out.writeByte(0); + out.writeByte(TagRegistry.END); } else { int id = TagRegistry.getIdFor(this.type); if (id == -1) { @@ -192,10 +197,10 @@ public class ListTag extends Tag implements Iterable { } @Override - public final ListTag clone() { + public ListTag copy() { List newList = new ArrayList<>(); for (Tag value : this.value) { - newList.add(value.clone()); + newList.add(value.copy()); } return new ListTag(newList); diff --git a/src/main/java/com/github/steveice10/opennbt/tag/builtin/LongArrayTag.java b/src/main/java/com/github/steveice10/opennbt/tag/builtin/LongArrayTag.java index 3e8e53f..afd80fd 100644 --- a/src/main/java/com/github/steveice10/opennbt/tag/builtin/LongArrayTag.java +++ b/src/main/java/com/github/steveice10/opennbt/tag/builtin/LongArrayTag.java @@ -33,6 +33,16 @@ public class LongArrayTag extends NumberArrayTag { this.value = value; } + public static LongArrayTag read(DataInput in, TagLimiter tagLimiter) throws IOException { + tagLimiter.countInt(); + long[] value = new long[in.readInt()]; + tagLimiter.countBytes(Long.BYTES * value.length); + for (int index = 0; index < value.length; index++) { + value[index] = in.readLong(); + } + return new LongArrayTag(value); + } + @Override public long[] getValue() { return this.value; @@ -89,16 +99,6 @@ public class LongArrayTag extends NumberArrayTag { return list; } - @Override - public void read(DataInput in, TagLimiter tagLimiter, int nestingLevel) throws IOException { - tagLimiter.countInt(); - this.value = new long[in.readInt()]; - tagLimiter.countBytes(8 * this.value.length); - for (int index = 0; index < this.value.length; index++) { - this.value[index] = in.readLong(); - } - } - @Override public void write(DataOutput out) throws IOException { out.writeInt(this.value.length); @@ -121,7 +121,7 @@ public class LongArrayTag extends NumberArrayTag { } @Override - public final LongArrayTag clone() { + public LongArrayTag copy() { return new LongArrayTag(this.value.clone()); } diff --git a/src/main/java/com/github/steveice10/opennbt/tag/builtin/LongTag.java b/src/main/java/com/github/steveice10/opennbt/tag/builtin/LongTag.java index 691e8fc..a51d9d5 100644 --- a/src/main/java/com/github/steveice10/opennbt/tag/builtin/LongTag.java +++ b/src/main/java/com/github/steveice10/opennbt/tag/builtin/LongTag.java @@ -28,6 +28,11 @@ public class LongTag extends NumberTag { this.value = value; } + public static LongTag read(DataInput in, TagLimiter tagLimiter) throws IOException { + tagLimiter.countLong(); + return new LongTag(in.readLong()); + } + /** * @deprecated use {@link #asLong()} */ @@ -51,12 +56,6 @@ public class LongTag extends NumberTag { this.value = value; } - @Override - public void read(DataInput in, TagLimiter tagLimiter, int nestingLevel) throws IOException { - tagLimiter.countLong(); - this.value = in.readLong(); - } - @Override public void write(DataOutput out) throws IOException { out.writeLong(this.value); @@ -76,7 +75,7 @@ public class LongTag extends NumberTag { } @Override - public final LongTag clone() { + public LongTag copy() { return new LongTag(this.value); } diff --git a/src/main/java/com/github/steveice10/opennbt/tag/builtin/ShortTag.java b/src/main/java/com/github/steveice10/opennbt/tag/builtin/ShortTag.java index e59ad71..e1234ef 100644 --- a/src/main/java/com/github/steveice10/opennbt/tag/builtin/ShortTag.java +++ b/src/main/java/com/github/steveice10/opennbt/tag/builtin/ShortTag.java @@ -28,6 +28,11 @@ public class ShortTag extends NumberTag { this.value = value; } + public static ShortTag read(DataInput in, TagLimiter tagLimiter) throws IOException { + tagLimiter.countShort(); + return new ShortTag(in.readShort()); + } + /** * @deprecated use {@link #asShort()} */ @@ -51,12 +56,6 @@ public class ShortTag extends NumberTag { this.value = value; } - @Override - public void read(DataInput in, TagLimiter tagLimiter, int nestingLevel) throws IOException { - tagLimiter.countShort(); - this.value = in.readShort(); - } - @Override public void write(DataOutput out) throws IOException { out.writeShort(this.value); @@ -76,7 +75,7 @@ public class ShortTag extends NumberTag { } @Override - public final ShortTag clone() { + public ShortTag copy() { return new ShortTag(this.value); } diff --git a/src/main/java/com/github/steveice10/opennbt/tag/builtin/StringTag.java b/src/main/java/com/github/steveice10/opennbt/tag/builtin/StringTag.java index d50ff39..2cb2b32 100644 --- a/src/main/java/com/github/steveice10/opennbt/tag/builtin/StringTag.java +++ b/src/main/java/com/github/steveice10/opennbt/tag/builtin/StringTag.java @@ -31,6 +31,12 @@ public class StringTag extends Tag { this.value = value; } + public static StringTag read(DataInput in, TagLimiter tagLimiter) throws IOException { + final String value = in.readUTF(); + tagLimiter.countBytes(2 * value.length()); // More or less, ignoring the length reading + return new StringTag(value); + } + @Override public String getValue() { return this.value; @@ -53,12 +59,6 @@ public class StringTag extends Tag { this.value = value; } - @Override - public void read(DataInput in, TagLimiter tagLimiter, int nestingLevel) throws IOException { - this.value = in.readUTF(); - tagLimiter.countBytes(2 * value.length()); // More or less, ignoring the length reading - } - @Override public void write(DataOutput out) throws IOException { out.writeUTF(this.value); @@ -78,7 +78,7 @@ public class StringTag extends Tag { } @Override - public final StringTag clone() { + public StringTag copy() { return new StringTag(this.value); } diff --git a/src/main/java/com/github/steveice10/opennbt/tag/builtin/Tag.java b/src/main/java/com/github/steveice10/opennbt/tag/builtin/Tag.java index f71000f..1d5ae4d 100644 --- a/src/main/java/com/github/steveice10/opennbt/tag/builtin/Tag.java +++ b/src/main/java/com/github/steveice10/opennbt/tag/builtin/Tag.java @@ -1,8 +1,6 @@ package com.github.steveice10.opennbt.tag.builtin; import com.github.steveice10.opennbt.stringified.SNBT; -import com.github.steveice10.opennbt.tag.limiter.TagLimiter; -import java.io.DataInput; import java.io.DataOutput; import java.io.IOException; @@ -11,24 +9,22 @@ import java.io.IOException; *

* Tags should also have setter methods specific to their value types. */ -public abstract class Tag implements Cloneable { +public abstract class Tag { /** - * Gets the value of this tag. + * Returns the value of this tag. * - * @return The value of this tag. + * @return value of this tag */ public abstract Object getValue(); /** * Returns the raw string representation of the value of this tag. - * For SNBT, use {@link SNBT#serialize(Tag)} or {@link #toString()}. + * For SNBT, use {@link SNBT#serialize(Tag)}. * * @return raw string representation of the value of this tag */ - public String asRawString() { - return this.getValue().toString(); - } + public abstract String asRawString(); /** * Returns the unchecked value of this tag. @@ -40,54 +36,27 @@ public abstract class Tag implements Cloneable { return (T) getValue(); } - /** - * Reads this tag from an input stream. - * - * @param in Stream to write to. - * @throws java.io.IOException If an I/O error occurs. - */ - public final void read(DataInput in) throws IOException { - this.read(in, TagLimiter.noop(), 0); - } - - /** - * Reads this tag from an input stream. - * - * @param in Stream to write to. - * @param tagLimiter taglimiter - * @throws java.io.IOException If an I/O error occurs. - */ - public final void read(DataInput in, TagLimiter tagLimiter) throws IOException { - this.read(in, tagLimiter, 0); - } - - /** - * Reads this tag from an input stream. - * - * @param in Stream to write to. - * @param tagLimiter taglimiter - * @param nestingLevel current level of nesting - * @throws java.io.IOException If an I/O error occurs. - */ - public abstract void read(DataInput in, TagLimiter tagLimiter, int nestingLevel) throws IOException; - /** * Writes this tag to an output stream. * - * @param out Stream to write to. - * @throws java.io.IOException If an I/O error occurs. + * @param out data output to write to + * @throws IOException if an I/O error occurs */ public abstract void write(DataOutput out) throws IOException; /** * Returns the NBT tag id of this tag type, used in I/O. * - * @return Id of the tag this class represents + * @return ID of the tag this class represents */ public abstract int getTagId(); - @Override - public abstract Tag clone(); + /** + * Returns a copy of this tag. + * + * @return a copy of this tag + */ + public abstract Tag copy(); @Override public String toString() { diff --git a/src/main/java/com/github/steveice10/opennbt/tag/io/NBTIO.java b/src/main/java/com/github/steveice10/opennbt/tag/io/NBTIO.java new file mode 100644 index 0000000..1e717a4 --- /dev/null +++ b/src/main/java/com/github/steveice10/opennbt/tag/io/NBTIO.java @@ -0,0 +1,86 @@ +package com.github.steveice10.opennbt.tag.io; + +import com.github.steveice10.opennbt.tag.TagRegistry; +import com.github.steveice10.opennbt.tag.builtin.Tag; +import com.github.steveice10.opennbt.tag.limiter.TagLimiter; +import java.io.DataInput; +import java.io.DataOutput; +import java.io.IOException; +import org.jetbrains.annotations.Nullable; + +/** + * Utility to read and write NBT tags. + */ +public final class NBTIO { + + private NBTIO() { + } + + /** + * Returns an NBT tag reader. If a tag limiter is set, the reader is reusable, but not thread-safe. + * + * @return NBT tag reader + */ + public static TagReader reader() { + return new TagReader<>(null); + } + + /** + * Returns an NBT tag reader to read an expected tag type. If a tag limiter is set, the reader is reusable, but not thread-safe. + * + * @param expectedTagType the expected tag type, or null if any is accepted + * @param the expected tag type + * @return NBT tag reader + */ + public static TagReader reader(final Class expectedTagType) { + return new TagReader<>(expectedTagType); + } + + /** + * Returns a reusable NBT tag writer. + * + * @return reusable NBT tag writer + */ + public static TagWriter writer() { + return new TagWriter(); + } + + /** + * Reads a named NBT tag from a data input. + * + * @param in input stream to read from + * @param tagLimiter tag limiter to use + * @param named whether the tag is named + * @param expectedTagType the expected tag type, or null if any is accepted + * @return the read tag + * @throws IOException if an I/O error occurs + */ + public static T readTag(final DataInput in, final TagLimiter tagLimiter, final boolean named, @Nullable final Class expectedTagType) throws IOException { + final int id = in.readByte(); + if (expectedTagType != null && expectedTagType != TagRegistry.getClassFor(id)) { + throw new IOException("Expected tag type " + expectedTagType.getSimpleName() + " but got " + TagRegistry.getClassFor(id).getSimpleName()); + } + + if (named) { + in.skipBytes(in.readUnsignedShort()); // Skip name + } + + //noinspection unchecked + return (T) TagRegistry.read(id, in, tagLimiter, 0); + } + + /** + * Writes a named NBT tag to a data output. + * + * @param out output stream to write to + * @param tag tag to write + * @throws IOException if an I/O error occurs + */ + public static void writeTag(final DataOutput out, final Tag tag, final boolean named) throws IOException { + out.writeByte(tag.getTagId()); + if (named) { + out.writeUTF(""); // Empty name + } + tag.write(out); + } +} diff --git a/src/main/java/com/github/steveice10/opennbt/tag/io/TagReader.java b/src/main/java/com/github/steveice10/opennbt/tag/io/TagReader.java new file mode 100644 index 0000000..59ea84e --- /dev/null +++ b/src/main/java/com/github/steveice10/opennbt/tag/io/TagReader.java @@ -0,0 +1,92 @@ +package com.github.steveice10.opennbt.tag.io; + +import com.github.steveice10.opennbt.tag.builtin.Tag; +import com.github.steveice10.opennbt.tag.limiter.TagLimiter; +import it.unimi.dsi.fastutil.io.FastBufferedInputStream; +import java.io.DataInput; +import java.io.DataInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.zip.GZIPInputStream; +import org.jetbrains.annotations.Nullable; + +/** + * NBT tag reader. + * + * @param the expected tag type + * @see NBTIO#reader() + */ +public final class TagReader { + private final Class expectedTagType; + private TagLimiter tagLimiter = TagLimiter.noop(); + private boolean named; + + TagReader(@Nullable final Class expectedTagType) { + this.expectedTagType = expectedTagType; + } + + /** + * Sets the tag limiter to use per read tag, making the reader no longer thread-safe. + * + * @param tagLimiter the tag limiter to use + * @return self + */ + public TagReader tagLimiter(final TagLimiter tagLimiter) { + this.tagLimiter = tagLimiter; + return this; + } + + /** + * Sets this reader to read a named tag. + * + * @return self + */ + public TagReader named() { + this.named = true; + return this; + } + + /** + * Reads the tag from the given data output. + * + * @param in data input to read from + * @throws IOException if an I/O error occurs + */ + public T read(final DataInput in) throws IOException { + this.tagLimiter.reset(); + return NBTIO.readTag(in, this.tagLimiter, this.named, this.expectedTagType); + } + + /** + * Reads a tag from the given input stream. + * + * @param in input stream to read from + * @return the read tag + * @throws IOException if an I/O error occurs + */ + public T read(final InputStream in) throws IOException { + final DataInput dataInput = new DataInputStream(in); + return this.read(dataInput); + } + + /** + * Reads a tag from the given path. At least so far, the standard format is always named, so make sure to call {@link #named()}. + * + * @param path path to read from + * @param compressed whether the file is compressed + * @throws IOException if an I/O error occurs + */ + public T read(final Path path, final boolean compressed) throws IOException { + InputStream in = new FastBufferedInputStream(Files.newInputStream(path)); + try { + if (compressed) { + in = new GZIPInputStream(in); + } + return this.read(in); + } finally { + in.close(); + } + } +} \ No newline at end of file diff --git a/src/main/java/com/github/steveice10/opennbt/tag/io/TagWriter.java b/src/main/java/com/github/steveice10/opennbt/tag/io/TagWriter.java new file mode 100644 index 0000000..a3a8ab6 --- /dev/null +++ b/src/main/java/com/github/steveice10/opennbt/tag/io/TagWriter.java @@ -0,0 +1,77 @@ +package com.github.steveice10.opennbt.tag.io; + +import com.github.steveice10.opennbt.tag.builtin.Tag; +import it.unimi.dsi.fastutil.io.FastBufferedOutputStream; +import java.io.DataOutput; +import java.io.DataOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.zip.GZIPOutputStream; + +/** + * Reusable NBT tag writer. + * + * @see NBTIO#writer() + */ +public final class TagWriter { + private boolean named; + + /** + * Sets this writer to write a named tag. + * + * @return self + */ + public TagWriter named() { + this.named = true; + return this; + } + + /** + * Writes the tag to the given data output. + * + * @param out output stream to write to + * @param tag tag to write + * @throws IOException if an I/O error occurs + */ + public void write(final DataOutput out, final Tag tag) throws IOException { + NBTIO.writeTag(out, tag, this.named); + } + + /** + * Writes the tag to the given output stream. + * + * @param out output stream to write to + * @param tag tag to write + * @throws IOException if an I/O error occurs + */ + public void write(final OutputStream out, final Tag tag) throws IOException { + NBTIO.writeTag(new DataOutputStream(out), tag, this.named); + } + + /** + * Writes the tag to the given path. At least so far, the standard format is always named, so make sure to call {@link #named()}. + * + * @param path path to write to + * @param tag tag to write + * @param compressed whether to compress the file + * @throws IOException if an I/O error occurs + */ + public void write(final Path path, final Tag tag, final boolean compressed) throws IOException { + if (!Files.exists(path)) { + Files.createDirectories(path.getParent()); + Files.createFile(path); + } + + OutputStream out = new FastBufferedOutputStream(Files.newOutputStream(path)); + try { + if (compressed) { + out = new GZIPOutputStream(out); + } + this.write(out, tag); + } finally { + out.close(); + } + } +} \ No newline at end of file diff --git a/src/main/java/com/github/steveice10/opennbt/tag/limiter/NoopTagLimiter.java b/src/main/java/com/github/steveice10/opennbt/tag/limiter/NoopTagLimiter.java index 4b33278..d96feae 100644 --- a/src/main/java/com/github/steveice10/opennbt/tag/limiter/NoopTagLimiter.java +++ b/src/main/java/com/github/steveice10/opennbt/tag/limiter/NoopTagLimiter.java @@ -26,4 +26,8 @@ final class NoopTagLimiter implements TagLimiter { public int bytes() { return 0; } + + @Override + public void reset() { + } } diff --git a/src/main/java/com/github/steveice10/opennbt/tag/limiter/TagLimiter.java b/src/main/java/com/github/steveice10/opennbt/tag/limiter/TagLimiter.java index f109b0d..79f32b6 100644 --- a/src/main/java/com/github/steveice10/opennbt/tag/limiter/TagLimiter.java +++ b/src/main/java/com/github/steveice10/opennbt/tag/limiter/TagLimiter.java @@ -82,4 +82,9 @@ public interface TagLimiter { * @return currently read bytes */ int bytes(); + + /** + * Resets the current byte count. + */ + void reset(); } diff --git a/src/main/java/com/github/steveice10/opennbt/tag/limiter/TagLimiterImpl.java b/src/main/java/com/github/steveice10/opennbt/tag/limiter/TagLimiterImpl.java index 46597b2..48f6130 100644 --- a/src/main/java/com/github/steveice10/opennbt/tag/limiter/TagLimiterImpl.java +++ b/src/main/java/com/github/steveice10/opennbt/tag/limiter/TagLimiterImpl.java @@ -40,4 +40,9 @@ final class TagLimiterImpl implements TagLimiter { public int bytes() { return bytes; } + + @Override + public void reset() { + this.bytes = 0; + } }