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;
+ }
}