diff --git a/README.md b/README.md index 6de60cbf..004c51a2 100644 --- a/README.md +++ b/README.md @@ -42,6 +42,16 @@ ListTag fl = new ListTag<>(FloatTag.class); fl.add(new FloatTag(1.234f); fl.addFloat(5.678f); ``` + +#### Nesting +All methods serializing instances or deserializing data track the nesting levels to prevent circular references or malicious data which could, when deserialized, result in thousands of instances causing a denial of service. + +These methods have a parameter for the maximum nesting depth they are allowed to traverse. A value of `0` means that only the object itself, but no nested object may be processed. + +If an instance is nested further than allowed, a [MaxDepthReachedException](src/main/java/net/querz/nbt/MaxDepthReachedException.java) will be thrown. A negative maximum depth will cause an `IllegalArgumentException`. + +Some methods do not provide a parameter to specify the maximum depth, but instead use `Tag.DEFAULT_MAX_DEPTH` (`512`) which is also the maximum used in Minecraft. + --- ### Utility There are several utility methods to make your life easier if you use this library. @@ -124,3 +134,8 @@ To be able to use a custom tag with deserialization, a `Supplier` and the custom ```java TagFactory.registerCustomTag(90, ObjectTag::new, ObjectTag.class); ``` + +#### Nesting +As mentioned before, serialization and deserialization methods are provided with a parameter indicating the maximum processing depth of the structure. This is not guaranteed when using custom tags, it is the responsibility of the creator of that custom tag to call `Tag#decrementMaxDepth(int)` to correctly update the nesting depth. + +It is also highly encouraged to document the custom tag behaviour when it does so to make users aware of the possible exceptions thrown by `Tag#decrementMaxDepth(int)`. \ No newline at end of file diff --git a/build.gradle b/build.gradle index 52784b49..aee3f244 100644 --- a/build.gradle +++ b/build.gradle @@ -9,7 +9,7 @@ apply plugin: 'jacoco' group = 'net.querz.nbt' archivesBaseName = 'nbt' -version = '3.0' +version = '4.0' sourceCompatibility = '1.8' targetCompatibility = '1.8' compileJava.options.encoding = 'UTF-8' diff --git a/src/main/java/net/querz/nbt/ArrayTag.java b/src/main/java/net/querz/nbt/ArrayTag.java index 46016a78..b08d9fc8 100644 --- a/src/main/java/net/querz/nbt/ArrayTag.java +++ b/src/main/java/net/querz/nbt/ArrayTag.java @@ -10,10 +10,10 @@ public abstract class ArrayTag extends Tag { public ArrayTag(T value) { + super(value); if (!value.getClass().isArray()) { throw new UnsupportedOperationException("type of array tag must be an array"); } - setValue(value); } public int length() { @@ -27,22 +27,14 @@ public T getValue() { @Override public void setValue(T value) { - super.setValue(checkNull(value)); + super.setValue(value); } @Override - public String valueToString(int depth) { + public String valueToString(int maxDepth) { return arrayToString("", ""); } - @Override - public int compareTo(Tag other) { - if (!(other instanceof ArrayTag) || this.getClass() != other.getClass()) { - throw new IllegalArgumentException("array types are incompatible"); - } - return Integer.compare(Array.getLength(getValue()), Array.getLength(other.getValue())); - } - protected String arrayToString(String prefix, String suffix) { StringBuilder sb = new StringBuilder("[").append(prefix).append("".equals(prefix) ? "" : ";"); for (int i = 0; i < length(); i++) { diff --git a/src/main/java/net/querz/nbt/ByteArrayTag.java b/src/main/java/net/querz/nbt/ByteArrayTag.java index 1a306659..f73aae56 100644 --- a/src/main/java/net/querz/nbt/ByteArrayTag.java +++ b/src/main/java/net/querz/nbt/ByteArrayTag.java @@ -5,10 +5,12 @@ import java.io.IOException; import java.util.Arrays; -public class ByteArrayTag extends ArrayTag { +public class ByteArrayTag extends ArrayTag implements Comparable { + + public static final byte[] ZERO_VALUE = new byte[0]; public ByteArrayTag() { - super(new byte[0]); + super(ZERO_VALUE); } public ByteArrayTag(byte[] value) { @@ -16,25 +18,20 @@ public ByteArrayTag(byte[] value) { } @Override - protected byte[] getEmptyValue() { - return new byte[0]; - } - - @Override - public void serializeValue(DataOutputStream dos, int depth) throws IOException { + public void serializeValue(DataOutputStream dos, int maxDepth) throws IOException { dos.writeInt(length()); dos.write(getValue()); } @Override - public void deserializeValue(DataInputStream dis, int depth) throws IOException { + public void deserializeValue(DataInputStream dis, int maxDepth) throws IOException { int length = dis.readInt(); setValue(new byte[length]); dis.readFully(getValue()); } @Override - public String valueToTagString(int depth) { + public String valueToTagString(int maxDepth) { return arrayToString("B", "b"); } @@ -48,6 +45,11 @@ public int hashCode() { return Arrays.hashCode(getValue()); } + @Override + public int compareTo(ByteArrayTag other) { + return Integer.compare(length(), other.length()); + } + @Override public ByteArrayTag clone() { return new ByteArrayTag(Arrays.copyOf(getValue(), length())); diff --git a/src/main/java/net/querz/nbt/ByteTag.java b/src/main/java/net/querz/nbt/ByteTag.java index 6a7a4631..5d12e558 100644 --- a/src/main/java/net/querz/nbt/ByteTag.java +++ b/src/main/java/net/querz/nbt/ByteTag.java @@ -4,9 +4,13 @@ import java.io.DataOutputStream; import java.io.IOException; -public class ByteTag extends NumberTag { +public class ByteTag extends NumberTag implements Comparable { - public ByteTag() {} + public static final byte ZERO_VALUE = 0; + + public ByteTag() { + super(ZERO_VALUE); + } public ByteTag(byte value) { super(value); @@ -16,11 +20,6 @@ public ByteTag(boolean value) { super((byte) (value ? 1 : 0)); } - @Override - protected Byte getEmptyValue() { - return 0; - } - public boolean asBoolean() { return getValue() > 0; } @@ -30,17 +29,17 @@ public void setValue(byte value) { } @Override - public void serializeValue(DataOutputStream dos, int depth) throws IOException { + public void serializeValue(DataOutputStream dos, int maxDepth) throws IOException { dos.writeByte(getValue()); } @Override - public void deserializeValue(DataInputStream dis, int depth) throws IOException { + public void deserializeValue(DataInputStream dis, int maxDepth) throws IOException { setValue(dis.readByte()); } @Override - public String valueToTagString(int depth) { + public String valueToTagString(int maxDepth) { return getValue() + "b"; } @@ -49,6 +48,11 @@ public boolean equals(Object other) { return super.equals(other) && asByte() == ((ByteTag) other).asByte(); } + @Override + public int compareTo(ByteTag other) { + return getValue().compareTo(other.getValue()); + } + @Override public ByteTag clone() { return new ByteTag(getValue()); diff --git a/src/main/java/net/querz/nbt/CompoundTag.java b/src/main/java/net/querz/nbt/CompoundTag.java index a51e5256..adbf18b1 100644 --- a/src/main/java/net/querz/nbt/CompoundTag.java +++ b/src/main/java/net/querz/nbt/CompoundTag.java @@ -3,15 +3,21 @@ import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.IOException; -import java.util.*; +import java.util.Collection; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; +import java.util.Objects; +import java.util.Set; import java.util.function.BiConsumer; -public class CompoundTag extends Tag>> implements Iterable>> { +public class CompoundTag extends Tag>> implements Iterable>>, Comparable { - public CompoundTag() {} + public CompoundTag() { + super(createEmptyValue()); + } - @Override - protected Map> getEmptyValue() { + private static Map> createEmptyValue() { return new HashMap<>(8); } @@ -123,56 +129,56 @@ public boolean getBoolean(String key) { public byte getByte(String key) { ByteTag t = getByteTag(key); - return t == null ? new ByteTag().getEmptyValue() : t.asByte(); + return t == null ? ByteTag.ZERO_VALUE : t.asByte(); } public short getShort(String key) { ShortTag t = getShortTag(key); - return t == null ? new ShortTag().getEmptyValue() : t.asShort(); + return t == null ? ShortTag.ZERO_VALUE : t.asShort(); } public int getInt(String key) { IntTag t = getIntTag(key); - return t == null ? new IntTag().getEmptyValue() : t.asInt(); + return t == null ? IntTag.ZERO_VALUE : t.asInt(); } public long getLong(String key) { LongTag t = getLongTag(key); - return t == null ? new LongTag().getEmptyValue() : t.asLong(); + return t == null ? LongTag.ZERO_VALUE : t.asLong(); } public float getFloat(String key) { FloatTag t = getFloatTag(key); - return t == null ? new FloatTag().getEmptyValue() : t.asFloat(); + return t == null ? FloatTag.ZERO_VALUE : t.asFloat(); } public double getDouble(String key) { DoubleTag t = getDoubleTag(key); - return t == null ? new DoubleTag().getEmptyValue() : t.asDouble(); + return t == null ? DoubleTag.ZERO_VALUE : t.asDouble(); } public String getString(String key) { StringTag t = getStringTag(key); - return t == null ? new StringTag().getEmptyValue() : t.getValue(); + return t == null ? StringTag.ZERO_VALUE : t.getValue(); } public byte[] getByteArray(String key) { ByteArrayTag t = getByteArrayTag(key); - return t == null ? new ByteArrayTag().getEmptyValue() : t.getValue(); + return t == null ? ByteArrayTag.ZERO_VALUE : t.getValue(); } public int[] getIntArray(String key) { IntArrayTag t = getIntArrayTag(key); - return t == null ? new IntArrayTag().getEmptyValue() : t.getValue(); + return t == null ? IntArrayTag.ZERO_VALUE : t.getValue(); } public long[] getLongArray(String key) { LongArrayTag t = getLongArrayTag(key); - return t == null ? new LongArrayTag().getEmptyValue() : t.getValue(); + return t == null ? LongArrayTag.ZERO_VALUE : t.getValue(); } public Tag put(String key, Tag tag) { - return getValue().put(checkNull(key), checkNull(tag)); + return getValue().put(Objects.requireNonNull(key), Objects.requireNonNull(tag)); } public Tag putBoolean(String key, boolean value) { @@ -204,49 +210,48 @@ public Tag putDouble(String key, double value) { } public Tag putString(String key, String value) { - return put(key, new StringTag(checkNull(value))); + return put(key, new StringTag(value)); } public Tag putByteArray(String key, byte[] value) { - return put(key, new ByteArrayTag(checkNull(value))); + return put(key, new ByteArrayTag(value)); } public Tag putIntArray(String key, int[] value) { - return put(key, new IntArrayTag(checkNull(value))); + return put(key, new IntArrayTag(value)); } public Tag putLongArray(String key, long[] value) { - return put(key, new LongArrayTag(checkNull(value))); + return put(key, new LongArrayTag(value)); } @Override - public void serializeValue(DataOutputStream dos, int depth) throws IOException { + public void serializeValue(DataOutputStream dos, int maxDepth) throws IOException { for (Map.Entry> e : getValue().entrySet()) { - dos.writeByte(e.getValue().getID()); - dos.writeUTF(e.getKey()); - e.getValue().serializeValue(dos, incrementDepth(depth)); + e.getValue().serialize(dos, e.getKey(), decrementMaxDepth(maxDepth)); } - EndTag.INSTANCE.serialize(dos, depth); + EndTag.INSTANCE.serialize(dos, maxDepth); } @Override - public void deserializeValue(DataInputStream dis, int depth) throws IOException { + public void deserializeValue(DataInputStream dis, int maxDepth) throws IOException { + clear(); for (int id = dis.readByte() & 0xFF; id != 0; id = dis.readByte() & 0xFF) { Tag tag = TagFactory.fromID(id); String name = dis.readUTF(); - tag.deserializeValue(dis, incrementDepth(depth)); + tag.deserializeValue(dis, decrementMaxDepth(maxDepth)); put(name, tag); } } @Override - public String valueToString(int depth) { + public String valueToString(int maxDepth) { StringBuilder sb = new StringBuilder("{"); boolean first = true; for (Map.Entry> e : getValue().entrySet()) { sb.append(first ? "" : ",") .append(escapeString(e.getKey(), false)).append(":") - .append(e.getValue().toString(incrementDepth(depth))); + .append(e.getValue().toString(decrementMaxDepth(maxDepth))); first = false; } sb.append("}"); @@ -254,13 +259,13 @@ public String valueToString(int depth) { } @Override - public String valueToTagString(int depth) { + public String valueToTagString(int maxDepth) { StringBuilder sb = new StringBuilder("{"); boolean first = true; for (Map.Entry> e : getValue().entrySet()) { sb.append(first ? "" : ",") .append(escapeString(e.getKey(), true)).append(":") - .append(e.getValue().valueToTagString(incrementDepth(depth))); + .append(e.getValue().valueToTagString(decrementMaxDepth(maxDepth))); first = false; } sb.append("}"); @@ -285,10 +290,7 @@ public boolean equals(Object other) { } @Override - public int compareTo(Tag>> o) { - if (!(o instanceof CompoundTag)) { - return 0; - } + public int compareTo(CompoundTag o) { return Integer.compare(size(), o.getValue().size()); } diff --git a/src/main/java/net/querz/nbt/DoubleTag.java b/src/main/java/net/querz/nbt/DoubleTag.java index 2b9f4293..5057941a 100644 --- a/src/main/java/net/querz/nbt/DoubleTag.java +++ b/src/main/java/net/querz/nbt/DoubleTag.java @@ -4,17 +4,16 @@ import java.io.DataOutputStream; import java.io.IOException; -public class DoubleTag extends NumberTag { +public class DoubleTag extends NumberTag implements Comparable { - public DoubleTag() {} + public static final double ZERO_VALUE = 0.0D; - public DoubleTag(double value) { - super(value); + public DoubleTag() { + super(ZERO_VALUE); } - @Override - protected Double getEmptyValue() { - return 0.0d; + public DoubleTag(double value) { + super(value); } public void setValue(double value) { @@ -22,17 +21,17 @@ public void setValue(double value) { } @Override - public void serializeValue(DataOutputStream dos, int depth) throws IOException { + public void serializeValue(DataOutputStream dos, int maxDepth) throws IOException { dos.writeDouble(getValue()); } @Override - public void deserializeValue(DataInputStream dis, int depth) throws IOException { + public void deserializeValue(DataInputStream dis, int maxDepth) throws IOException { setValue(dis.readDouble()); } @Override - public String valueToTagString(int depth) { + public String valueToTagString(int maxDepth) { return getValue() + "d"; } @@ -41,6 +40,11 @@ public boolean equals(Object other) { return super.equals(other) && getValue().equals(((DoubleTag) other).getValue()); } + @Override + public int compareTo(DoubleTag other) { + return getValue().compareTo(other.getValue()); + } + @Override public DoubleTag clone() { return new DoubleTag(getValue()); diff --git a/src/main/java/net/querz/nbt/EndTag.java b/src/main/java/net/querz/nbt/EndTag.java index 7b4ade40..b07a8af4 100644 --- a/src/main/java/net/querz/nbt/EndTag.java +++ b/src/main/java/net/querz/nbt/EndTag.java @@ -7,38 +7,35 @@ public final class EndTag extends Tag { static final EndTag INSTANCE = new EndTag(); - private EndTag() {} + private EndTag() { + super(null); + } @Override - protected Void getEmptyValue() { - return null; + protected Void checkValue(Void value) { + return value; } @Override - public void serializeValue(DataOutputStream dos, int depth) { + public void serializeValue(DataOutputStream dos, int maxDepth) { //nothing to do } @Override - public void deserializeValue(DataInputStream dis, int depth) { + public void deserializeValue(DataInputStream dis, int maxDepth) { //nothing to do } @Override - public String valueToString(int depth) { + public String valueToString(int maxDepth) { return "\"end\""; } @Override - public String valueToTagString(int depth) { + public String valueToTagString(int maxDepth) { throw new UnsupportedOperationException("EndTag cannot be turned into a String"); } - @Override - public int compareTo(Tag o) { - return 0; - } - @Override public EndTag clone() { return INSTANCE; diff --git a/src/main/java/net/querz/nbt/FloatTag.java b/src/main/java/net/querz/nbt/FloatTag.java index 101a0919..ff248e3f 100644 --- a/src/main/java/net/querz/nbt/FloatTag.java +++ b/src/main/java/net/querz/nbt/FloatTag.java @@ -4,17 +4,16 @@ import java.io.DataOutputStream; import java.io.IOException; -public class FloatTag extends NumberTag { +public class FloatTag extends NumberTag implements Comparable { - public FloatTag() {} + public static final float ZERO_VALUE = 0.0F; - public FloatTag(float value) { - super(value); + public FloatTag() { + super(ZERO_VALUE); } - @Override - protected Float getEmptyValue() { - return 0.0f; + public FloatTag(float value) { + super(value); } public void setValue(float value) { @@ -22,17 +21,17 @@ public void setValue(float value) { } @Override - public void serializeValue(DataOutputStream dos, int depth) throws IOException { + public void serializeValue(DataOutputStream dos, int maxDepth) throws IOException { dos.writeFloat(getValue()); } @Override - public void deserializeValue(DataInputStream dis, int depth) throws IOException { + public void deserializeValue(DataInputStream dis, int maxDepth) throws IOException { setValue(dis.readFloat()); } @Override - public String valueToTagString(int depth) { + public String valueToTagString(int maxDepth) { return getValue() + "f"; } @@ -41,6 +40,11 @@ public boolean equals(Object other) { return super.equals(other) && getValue().equals(((FloatTag) other).getValue()); } + @Override + public int compareTo(FloatTag other) { + return getValue().compareTo(other.getValue()); + } + @Override public FloatTag clone() { return new FloatTag(getValue()); diff --git a/src/main/java/net/querz/nbt/IntArrayTag.java b/src/main/java/net/querz/nbt/IntArrayTag.java index afbfe8ba..694c3192 100644 --- a/src/main/java/net/querz/nbt/IntArrayTag.java +++ b/src/main/java/net/querz/nbt/IntArrayTag.java @@ -5,10 +5,12 @@ import java.io.IOException; import java.util.Arrays; -public class IntArrayTag extends ArrayTag { +public class IntArrayTag extends ArrayTag implements Comparable { + + public static final int[] ZERO_VALUE = new int[0]; public IntArrayTag() { - super(new int[0]); + super(ZERO_VALUE); } public IntArrayTag(int[] value) { @@ -16,12 +18,7 @@ public IntArrayTag(int[] value) { } @Override - protected int[] getEmptyValue() { - return new int[0]; - } - - @Override - public void serializeValue(DataOutputStream dos, int depth) throws IOException { + public void serializeValue(DataOutputStream dos, int maxDepth) throws IOException { dos.writeInt(length()); for (int i : getValue()) { dos.writeInt(i); @@ -29,7 +26,7 @@ public void serializeValue(DataOutputStream dos, int depth) throws IOException { } @Override - public void deserializeValue(DataInputStream dis, int depth) throws IOException { + public void deserializeValue(DataInputStream dis, int maxDepth) throws IOException { int length = dis.readInt(); setValue(new int[length]); for (int i = 0; i < length; i++) { @@ -38,7 +35,7 @@ public void deserializeValue(DataInputStream dis, int depth) throws IOException } @Override - public String valueToTagString(int depth) { + public String valueToTagString(int maxDepth) { return arrayToString("I", ""); } @@ -52,6 +49,11 @@ public int hashCode() { return Arrays.hashCode(getValue()); } + @Override + public int compareTo(IntArrayTag other) { + return Integer.compare(length(), other.length()); + } + @Override public IntArrayTag clone() { return new IntArrayTag(Arrays.copyOf(getValue(), length())); diff --git a/src/main/java/net/querz/nbt/IntTag.java b/src/main/java/net/querz/nbt/IntTag.java index a0b513bf..dcd6e8e3 100644 --- a/src/main/java/net/querz/nbt/IntTag.java +++ b/src/main/java/net/querz/nbt/IntTag.java @@ -4,17 +4,16 @@ import java.io.DataOutputStream; import java.io.IOException; -public class IntTag extends NumberTag { +public class IntTag extends NumberTag implements Comparable { - public IntTag() {} + public static final int ZERO_VALUE = 0; - public IntTag(int value) { - super(value); + public IntTag() { + super(ZERO_VALUE); } - @Override - protected Integer getEmptyValue() { - return 0; + public IntTag(int value) { + super(value); } public void setValue(int value) { @@ -22,17 +21,17 @@ public void setValue(int value) { } @Override - public void serializeValue(DataOutputStream dos, int depth) throws IOException { + public void serializeValue(DataOutputStream dos, int maxDepth) throws IOException { dos.writeInt(getValue()); } @Override - public void deserializeValue(DataInputStream dis, int depth) throws IOException { + public void deserializeValue(DataInputStream dis, int maxDepth) throws IOException { setValue(dis.readInt()); } @Override - public String valueToTagString(int depth) { + public String valueToTagString(int maxDepth) { return getValue() + ""; } @@ -41,6 +40,11 @@ public boolean equals(Object other) { return super.equals(other) && asInt() == ((IntTag) other).asInt(); } + @Override + public int compareTo(IntTag other) { + return getValue().compareTo(other.getValue()); + } + @Override public IntTag clone() { return new IntTag(getValue()); diff --git a/src/main/java/net/querz/nbt/ListTag.java b/src/main/java/net/querz/nbt/ListTag.java index 03bd5387..454b3aab 100644 --- a/src/main/java/net/querz/nbt/ListTag.java +++ b/src/main/java/net/querz/nbt/ListTag.java @@ -18,11 +18,13 @@ * The type of an empty untyped {@link ListTag} can be set by using any of the {@code add()} * methods or any of the {@code as...List()} methods. * */ -public class ListTag> extends Tag> implements Iterable { +public class ListTag> extends Tag> implements Iterable, Comparable> { private Class typeClass = null; - private ListTag() {} + private ListTag() { + super(createEmptyValue(3)); + } /** *

Creates a non-type-safe ListTag. Its element type will be set after the first @@ -37,27 +39,34 @@ protected static ListTag createUnchecked() { return new ListTag<>(); } + /** + *

Creates an empty mutable list to be used as empty value of ListTags.

+ * + * @param Type of the list elements + * @param initialCapacity The initial capacity of the returned List + * @return An instance of {@link java.util.List} with an initial capacity of 3 + * */ + private static List createEmptyValue(int initialCapacity) { + return new ArrayList<>(initialCapacity); + } + /** * @param typeClass The exact class of the elements * @throws IllegalArgumentException When {@code typeClass} is {@link EndTag}{@code .class} * @throws NullPointerException When {@code typeClass} is {@code null} */ public ListTag(Class typeClass) throws IllegalArgumentException, NullPointerException { + super(createEmptyValue(3)); if (typeClass == EndTag.class) { throw new IllegalArgumentException("cannot create ListTag with EndTag elements"); } - this.typeClass = checkNull(typeClass); + this.typeClass = Objects.requireNonNull(typeClass); } public Class getTypeClass() { return typeClass == null ? EndTag.class : typeClass; } - @Override - protected List getEmptyValue() { - return new ArrayList<>(3); - } - public int size() { return getValue().size(); } @@ -93,7 +102,7 @@ public void forEach(Consumer action) { } public T set(int index, T t) { - return getValue().set(index, checkNull(t)); + return getValue().set(index, Objects.requireNonNull(t)); } /** @@ -105,7 +114,7 @@ public void add(T t) { } public void add(int index, T t) { - checkNull(t); + Objects.requireNonNull(t); getValue().add(index, t); if (typeClass == null) { typeClass = t.getClass(); @@ -155,19 +164,19 @@ public void addDouble(double value) { } public void addString(String value) { - addUnchecked(new StringTag(checkNull(value))); + addUnchecked(new StringTag(value)); } public void addByteArray(byte[] value) { - addUnchecked(new ByteArrayTag(checkNull(value))); + addUnchecked(new ByteArrayTag(value)); } public void addIntArray(int[] value) { - addUnchecked(new IntArrayTag(checkNull(value))); + addUnchecked(new IntArrayTag(value)); } public void addLongArray(long[] value) { - addUnchecked(new LongArrayTag(checkNull(value))); + addUnchecked(new LongArrayTag(value)); } public T get(int index) { @@ -237,49 +246,50 @@ public ListTag asCompoundTagList() { } @Override - public void serializeValue(DataOutputStream dos, int depth) throws IOException { + public void serializeValue(DataOutputStream dos, int maxDepth) throws IOException { dos.writeByte(TagFactory.idFromClass(getTypeClass())); dos.writeInt(size()); if (size() != 0) { for (T t : getValue()) { - t.serializeValue(dos, incrementDepth(depth)); + t.serializeValue(dos, decrementMaxDepth(maxDepth)); } } } @SuppressWarnings("unchecked") @Override - public void deserializeValue(DataInputStream dis, int depth) throws IOException { + public void deserializeValue(DataInputStream dis, int maxDepth) throws IOException { int typeID = dis.readByte(); if (typeID != 0) { typeClass = TagFactory.classFromID(typeID); } int size = dis.readInt(); + size = size < 0 ? 0 : size; + setValue(createEmptyValue(size)); if (size != 0) { - setValue(new ArrayList<>(size)); for (int i = 0; i < size; i++) { Tag tag = TagFactory.fromID(typeID); - tag.deserializeValue(dis, incrementDepth(depth)); + tag.deserializeValue(dis, decrementMaxDepth(maxDepth)); add((T) tag); } } } @Override - public String valueToString(int depth) { + public String valueToString(int maxDepth) { StringBuilder sb = new StringBuilder("{\"type\":\"").append(getTypeClass().getSimpleName()).append("\",\"list\":["); for (int i = 0; i < size(); i++) { - sb.append(i > 0 ? "," : "").append(get(i).valueToString(incrementDepth(depth))); + sb.append(i > 0 ? "," : "").append(get(i).valueToString(decrementMaxDepth(maxDepth))); } sb.append("]}"); return sb.toString(); } @Override - public String valueToTagString(int depth) { + public String valueToTagString(int maxDepth) { StringBuilder sb = new StringBuilder("["); for (int i = 0; i < size(); i++) { - sb.append(i > 0 ? "," : "").append(get(i).valueToTagString(incrementDepth(depth))); + sb.append(i > 0 ? "," : "").append(get(i).valueToTagString(decrementMaxDepth(maxDepth))); } sb.append("]"); return sb.toString(); @@ -307,10 +317,7 @@ public int hashCode() { } @Override - public int compareTo(Tag> o) { - if (!(o instanceof ListTag)) { - return 0; - } + public int compareTo(ListTag o) { return Integer.compare(size(), o.getValue().size()); } diff --git a/src/main/java/net/querz/nbt/LongArrayTag.java b/src/main/java/net/querz/nbt/LongArrayTag.java index 39d24e57..ec5452d7 100644 --- a/src/main/java/net/querz/nbt/LongArrayTag.java +++ b/src/main/java/net/querz/nbt/LongArrayTag.java @@ -5,10 +5,12 @@ import java.io.IOException; import java.util.Arrays; -public class LongArrayTag extends ArrayTag { +public class LongArrayTag extends ArrayTag implements Comparable { + + public static final long[] ZERO_VALUE = new long[0]; public LongArrayTag() { - super(new long[0]); + super(ZERO_VALUE); } public LongArrayTag(long[] value) { @@ -16,12 +18,7 @@ public LongArrayTag(long[] value) { } @Override - protected long[] getEmptyValue() { - return new long[0]; - } - - @Override - public void serializeValue(DataOutputStream dos, int depth) throws IOException { + public void serializeValue(DataOutputStream dos, int maxDepth) throws IOException { dos.writeInt(length()); for (long i : getValue()) { dos.writeLong(i); @@ -29,7 +26,7 @@ public void serializeValue(DataOutputStream dos, int depth) throws IOException { } @Override - public void deserializeValue(DataInputStream dis, int depth) throws IOException { + public void deserializeValue(DataInputStream dis, int maxDepth) throws IOException { int length = dis.readInt(); setValue(new long[length]); for (int i = 0; i < length; i++) { @@ -38,7 +35,7 @@ public void deserializeValue(DataInputStream dis, int depth) throws IOException } @Override - public String valueToTagString(int depth) { + public String valueToTagString(int maxDepth) { return arrayToString("L", "l"); } @@ -52,6 +49,11 @@ public int hashCode() { return Arrays.hashCode(getValue()); } + @Override + public int compareTo(LongArrayTag other) { + return Integer.compare(length(), other.length()); + } + @Override public LongArrayTag clone() { return new LongArrayTag(Arrays.copyOf(getValue(), length())); diff --git a/src/main/java/net/querz/nbt/LongTag.java b/src/main/java/net/querz/nbt/LongTag.java index 125615cb..38a7e9f2 100644 --- a/src/main/java/net/querz/nbt/LongTag.java +++ b/src/main/java/net/querz/nbt/LongTag.java @@ -4,17 +4,16 @@ import java.io.DataOutputStream; import java.io.IOException; -public class LongTag extends NumberTag { +public class LongTag extends NumberTag implements Comparable { - public LongTag() {} + public static final long ZERO_VALUE = 0L; - public LongTag(long value) { - super(value); + public LongTag() { + super(ZERO_VALUE); } - @Override - protected Long getEmptyValue() { - return 0L; + public LongTag(long value) { + super(value); } public void setValue(long value) { @@ -22,17 +21,17 @@ public void setValue(long value) { } @Override - public void serializeValue(DataOutputStream dos, int depth) throws IOException { + public void serializeValue(DataOutputStream dos, int maxDepth) throws IOException { dos.writeLong(getValue()); } @Override - public void deserializeValue(DataInputStream dis, int depth) throws IOException { + public void deserializeValue(DataInputStream dis, int maxDepth) throws IOException { setValue(dis.readLong()); } @Override - public String valueToTagString(int depth) { + public String valueToTagString(int maxDepth) { return getValue() + "l"; } @@ -41,6 +40,11 @@ public boolean equals(Object other) { return super.equals(other) && asLong() == ((LongTag) other).asLong(); } + @Override + public int compareTo(LongTag other) { + return getValue().compareTo(other.getValue()); + } + @Override public LongTag clone() { return new LongTag(getValue()); diff --git a/src/main/java/net/querz/nbt/MaxDepthReachedException.java b/src/main/java/net/querz/nbt/MaxDepthReachedException.java index db827cf4..3b3dfb71 100644 --- a/src/main/java/net/querz/nbt/MaxDepthReachedException.java +++ b/src/main/java/net/querz/nbt/MaxDepthReachedException.java @@ -1,7 +1,10 @@ package net.querz.nbt; +/** + * Exception indicating that the maximum (de-)serialization depth has been reached. + */ +@SuppressWarnings("serial") public class MaxDepthReachedException extends RuntimeException { - public MaxDepthReachedException(String msg) { super(msg); } diff --git a/src/main/java/net/querz/nbt/NBTUtil.java b/src/main/java/net/querz/nbt/NBTUtil.java index 661ed42a..3779e795 100644 --- a/src/main/java/net/querz/nbt/NBTUtil.java +++ b/src/main/java/net/querz/nbt/NBTUtil.java @@ -25,8 +25,8 @@ private NBTUtil() {} * @param tag The tag to be written to the file. * @param file The file to write {@code tag} into. * @throws IOException If something during the serialization goes wrong. - * @exception NullPointerException If {@code tag}, {@code name} or {@code file} is {@code null}. - * @exception MaxDepthReachedException If the NBT structure exceeds {@link Tag#MAX_DEPTH}. + * @throws NullPointerException If {@code tag}, {@code name} or {@code file} is {@code null}. + * @throws MaxDepthReachedException If the NBT structure exceeds {@link Tag#DEFAULT_MAX_DEPTH}. */ public static void writeTag(Tag tag, String file) throws IOException { writeTag(tag, "", new File(file), true); @@ -38,8 +38,8 @@ public static void writeTag(Tag tag, String file) throws IOException { * @param tag The tag to be written to the file. * @param file The file to write {@code tag} into. * @throws IOException If something during the serialization goes wrong. - * @exception NullPointerException If {@code tag}, {@code name} or {@code file} is {@code null}. - * @exception MaxDepthReachedException If the NBT structure exceeds {@link Tag#MAX_DEPTH}. + * @throws NullPointerException If {@code tag}, {@code name} or {@code file} is {@code null}. + * @throws MaxDepthReachedException If the NBT structure exceeds {@link Tag#DEFAULT_MAX_DEPTH}. */ public static void writeTag(Tag tag, File file) throws IOException { writeTag(tag, "", file, true); @@ -52,8 +52,8 @@ public static void writeTag(Tag tag, File file) throws IOException { * @param file The file to write {@code tag} into. * @param compressed {@code true} if the file should be GZIP compressed, {@code false} if not. * @throws IOException If something during the serialization goes wrong. - * @exception NullPointerException If {@code tag}, {@code name} or {@code file} is {@code null}. - * @exception MaxDepthReachedException If the NBT structure exceeds {@link Tag#MAX_DEPTH}. + * @throws NullPointerException If {@code tag}, {@code name} or {@code file} is {@code null}. + * @throws MaxDepthReachedException If the NBT structure exceeds {@link Tag#DEFAULT_MAX_DEPTH}. */ public static void writeTag(Tag tag, String file, boolean compressed) throws IOException { writeTag(tag, "", new File(file), compressed); @@ -66,8 +66,8 @@ public static void writeTag(Tag tag, String file, boolean compressed) throws * @param file The file to write {@code tag} into. * @param compressed {@code true} if the file should be GZIP compressed, {@code false} if not. * @throws IOException If something during the serialization goes wrong. - * @exception NullPointerException If {@code tag}, {@code name} or {@code file} is {@code null}. - * @exception MaxDepthReachedException If the NBT structure exceeds {@link Tag#MAX_DEPTH}. + * @throws NullPointerException If {@code tag}, {@code name} or {@code file} is {@code null}. + * @throws MaxDepthReachedException If the NBT structure exceeds {@link Tag#DEFAULT_MAX_DEPTH}. * */ public static void writeTag(Tag tag, File file, boolean compressed) throws IOException { writeTag(tag, "", file, compressed); @@ -79,8 +79,8 @@ public static void writeTag(Tag tag, File file, boolean compressed) throws IO * @param name The name of the root tag. * @param file The file to write {@code tag} into. * @throws IOException If something during the serialization goes wrong. - * @exception NullPointerException If {@code tag}, {@code name} or {@code file} is {@code null}. - * @exception MaxDepthReachedException If the NBT structure exceeds {@link Tag#MAX_DEPTH}. + * @throws NullPointerException If {@code tag}, {@code name} or {@code file} is {@code null}. + * @throws MaxDepthReachedException If the NBT structure exceeds {@link Tag#DEFAULT_MAX_DEPTH}. */ public static void writeTag(Tag tag, String name, String file) throws IOException { writeTag(tag, name, new File(file), true); @@ -93,8 +93,8 @@ public static void writeTag(Tag tag, String name, String file) throws IOExcep * @param name The name of the root tag. * @param file The file to write {@code tag} into. * @throws IOException If something during the serialization goes wrong. - * @exception NullPointerException If {@code tag}, {@code name} or {@code file} is {@code null}. - * @exception MaxDepthReachedException If the NBT structure exceeds {@link Tag#MAX_DEPTH}. + * @throws NullPointerException If {@code tag}, {@code name} or {@code file} is {@code null}. + * @throws MaxDepthReachedException If the NBT structure exceeds {@link Tag#DEFAULT_MAX_DEPTH}. */ public static void writeTag(Tag tag, String name, File file) throws IOException { writeTag(tag, name, file, true); @@ -107,8 +107,8 @@ public static void writeTag(Tag tag, String name, File file) throws IOExcepti * @param file The file to write {@code tag} into. * @param compressed {@code true} if the file should be GZIP compressed, {@code false} if not. * @throws IOException If something during the serialization goes wrong. - * @exception NullPointerException If {@code tag}, {@code name} or {@code file} is {@code null}. - * @exception MaxDepthReachedException If the NBT structure exceeds {@link Tag#MAX_DEPTH}. + * @throws NullPointerException If {@code tag}, {@code name} or {@code file} is {@code null}. + * @throws MaxDepthReachedException If the NBT structure exceeds {@link Tag#DEFAULT_MAX_DEPTH}. */ public static void writeTag(Tag tag, String name, String file, boolean compressed) throws IOException { writeTag(tag, name, new File(file), compressed); @@ -122,15 +122,15 @@ public static void writeTag(Tag tag, String name, String file, boolean compre * @param file The file to write {@code tag} into. * @param compressed {@code true} if the file should be GZIP compressed, {@code false} if not. * @throws IOException If something during the serialization goes wrong. - * @exception NullPointerException If {@code tag}, {@code name} or {@code file} is {@code null}. - * @exception MaxDepthReachedException If the NBT structure exceeds {@link Tag#MAX_DEPTH}. + * @throws NullPointerException If {@code tag}, {@code name} or {@code file} is {@code null}. + * @throws MaxDepthReachedException If the NBT structure exceeds {@link Tag#DEFAULT_MAX_DEPTH}. */ public static void writeTag(Tag tag, String name, File file, boolean compressed) throws IOException { try ( DataOutputStream dos = new DataOutputStream( compressed ? new GZIPOutputStream(new FileOutputStream(file)) : new FileOutputStream(file)) ) { - tag.serialize(dos, name, 0); + tag.serialize(dos, name, Tag.DEFAULT_MAX_DEPTH); } } @@ -151,11 +151,11 @@ public static Tag readTag(String file) throws IOException { * @return The tag read from the file. * @throws IOException If something during deserialization goes wrong. * @throws NullPointerException If {@code file} is {@code null}. - * @exception MaxDepthReachedException If the NBT structure exceeds {@link Tag#MAX_DEPTH}. + * @throws MaxDepthReachedException If the NBT structure exceeds {@link Tag#DEFAULT_MAX_DEPTH}. * */ public static Tag readTag(File file) throws IOException { try (DataInputStream dis = new DataInputStream(applyDecompression(new FileInputStream(file)))) { - return Tag.deserialize(dis, 0); + return Tag.deserialize(dis, Tag.DEFAULT_MAX_DEPTH); } } diff --git a/src/main/java/net/querz/nbt/NumberTag.java b/src/main/java/net/querz/nbt/NumberTag.java index 01a608b9..d2fd9ec6 100644 --- a/src/main/java/net/querz/nbt/NumberTag.java +++ b/src/main/java/net/querz/nbt/NumberTag.java @@ -2,8 +2,6 @@ public abstract class NumberTag> extends Tag { - public NumberTag() {} - public NumberTag(T value) { super(value); } @@ -33,15 +31,7 @@ public double asDouble() { } @Override - public String valueToString(int depth) { + public String valueToString(int maxDepth) { return getValue().toString(); } - - @Override - public int compareTo(Tag other) { - if (!(other instanceof NumberTag) || this.getClass() != other.getClass()) { - throw new IllegalArgumentException("number types are incompatible"); - } - return getValue().compareTo(other.getValue()); - } } diff --git a/src/main/java/net/querz/nbt/ShortTag.java b/src/main/java/net/querz/nbt/ShortTag.java index 7976c208..3534b217 100644 --- a/src/main/java/net/querz/nbt/ShortTag.java +++ b/src/main/java/net/querz/nbt/ShortTag.java @@ -4,17 +4,16 @@ import java.io.DataOutputStream; import java.io.IOException; -public class ShortTag extends NumberTag { +public class ShortTag extends NumberTag implements Comparable { - public ShortTag() {} + public static final short ZERO_VALUE = 0; - public ShortTag(short value) { - super(value); + public ShortTag() { + super(ZERO_VALUE); } - @Override - protected Short getEmptyValue() { - return 0; + public ShortTag(short value) { + super(value); } public void setValue(short value) { @@ -22,17 +21,17 @@ public void setValue(short value) { } @Override - public void serializeValue(DataOutputStream dos, int depth) throws IOException { + public void serializeValue(DataOutputStream dos, int maxDepth) throws IOException { dos.writeShort(getValue()); } @Override - public void deserializeValue(DataInputStream dis, int depth) throws IOException { + public void deserializeValue(DataInputStream dis, int maxDepth) throws IOException { setValue(dis.readShort()); } @Override - public String valueToTagString(int depth) { + public String valueToTagString(int maxDepth) { return getValue() + "s"; } @@ -41,6 +40,11 @@ public boolean equals(Object other) { return super.equals(other) && asShort() == ((ShortTag) other).asShort(); } + @Override + public int compareTo(ShortTag other) { + return getValue().compareTo(other.getValue()); + } + @Override public ShortTag clone() { return new ShortTag(getValue()); diff --git a/src/main/java/net/querz/nbt/StringTag.java b/src/main/java/net/querz/nbt/StringTag.java index 6da10b78..8350f518 100644 --- a/src/main/java/net/querz/nbt/StringTag.java +++ b/src/main/java/net/querz/nbt/StringTag.java @@ -4,17 +4,16 @@ import java.io.DataOutputStream; import java.io.IOException; -public class StringTag extends Tag { +public class StringTag extends Tag implements Comparable { - public StringTag() {} + public static final String ZERO_VALUE = ""; - public StringTag(String value) { - super(value); + public StringTag() { + super(ZERO_VALUE); } - @Override - protected String getEmptyValue() { - return ""; + public StringTag(String value) { + super(value); } @Override @@ -24,26 +23,26 @@ public String getValue() { @Override public void setValue(String value) { - super.setValue(checkNull(value)); + super.setValue(value); } @Override - public void serializeValue(DataOutputStream dos, int depth) throws IOException { + public void serializeValue(DataOutputStream dos, int maxDepth) throws IOException { dos.writeUTF(getValue()); } @Override - public void deserializeValue(DataInputStream dis, int depth) throws IOException { + public void deserializeValue(DataInputStream dis, int maxDepth) throws IOException { setValue(dis.readUTF()); } @Override - public String valueToString(int depth) { + public String valueToString(int maxDepth) { return escapeString(getValue(), false); } @Override - public String valueToTagString(int depth) { + public String valueToTagString(int maxDepth) { return escapeString(getValue(), true); } @@ -53,10 +52,7 @@ public boolean equals(Object other) { } @Override - public int compareTo(Tag o) { - if (o == null || o.getClass() != getClass()) { - throw new IllegalArgumentException("incompatible string types"); - } + public int compareTo(StringTag o) { return getValue().compareTo(o.getValue()); } diff --git a/src/main/java/net/querz/nbt/Tag.java b/src/main/java/net/querz/nbt/Tag.java index a137a7f5..2a92c042 100644 --- a/src/main/java/net/querz/nbt/Tag.java +++ b/src/main/java/net/querz/nbt/Tag.java @@ -6,15 +6,40 @@ import java.util.Collections; import java.util.HashMap; import java.util.Map; +import java.util.Objects; import java.util.regex.Matcher; import java.util.regex.Pattern; -public abstract class Tag implements Comparable>, Cloneable { +/** + * Base class for all NBT tags. + * + *

Nesting

+ *

All methods serializing instances or deserializing data track the nesting levels to prevent + * circular references or malicious data which could, when deserialized, result in thousands + * of instances causing a denial of service.

+ * + *

These methods have a parameter for the maximum nesting depth they are allowed to traverse. A + * value of {@code 0} means that only the object itself, but no nested objects may be processed. + * If an instance is nested further than allowed, a {@link MaxDepthReachedException} will be thrown. + * Providing a negative maximum nesting depth will cause an {@code IllegalArgumentException} + * to be thrown.

+ * + *

Some methods do not provide a parameter to specify the maximum nesting depth, but instead use + * {@link #DEFAULT_MAX_DEPTH}, which is also the maximum used by Minecraft. This is documented for + * the respective methods.

+ * + *

If custom NBT tags contain objects other than NBT tags, which can be nested as well, then there + * is no guarantee that {@code MaxDepthReachedException}s are thrown for them. The respective class + * will document this behavior accordingly.

+ * + * @param The type of the contained value + * */ +public abstract class Tag implements Cloneable { /** - * The maximum depth of the NBT structure. + * The default maximum depth of the NBT structure. * */ - public static final int MAX_DEPTH = 512; + public static final int DEFAULT_MAX_DEPTH = 512; private static final Map ESCAPE_CHARACTERS; static { @@ -33,20 +58,12 @@ public abstract class Tag implements Comparable>, Cloneable { private T value; /** - * Initializes this Tag with a {@code null} value. - */ - public Tag() { - this(null); - } - - /** - * Initializes this Tag with some value. If the value is {@code null}, it will call - * this Tag's implementation of {@link Tag#getEmptyValue()} to set as the default value. + * Initializes this Tag with some value. If the value is {@code null}, it will + * throw a {@code NullPointerException} * @param value The value to be set for this Tag. * */ public Tag(T value) { - //the value of a tag can never be null - this.value = value == null ? getEmptyValue() : value; + setValue(value); } /** @@ -56,11 +73,6 @@ public byte getID() { return TagFactory.idFromClass(getClass()); } - /** - * @return A default value for this Tag. - * */ - protected abstract T getEmptyValue(); - /** * @return The value of this Tag. * */ @@ -71,55 +83,68 @@ protected T getValue() { /** * Sets the value for this Tag directly. * @param value The value to be set. + * @throws NullPointerException If the value is null * */ protected void setValue(T value) { - this.value = value; + this.value = checkValue(value); + } + + /** + * Checks if the value {@code value} is {@code null}. + * @param value The value to check + * @throws NullPointerException If {@code value} was {@code null} + * @return The parameter {@code value} + * */ + protected T checkValue(T value) { + return Objects.requireNonNull(value); } /** * Calls {@link Tag#serialize(DataOutputStream, String, int)} with an empty name. * @see Tag#serialize(DataOutputStream, String, int) * @param dos The DataOutputStream to write to - * @param depth The current depth of the structure - * @throws IOException If something went wrong during serialization + * @param maxDepth The maximum nesting depth + * @throws IOException If something went wrong during serialization. + * @throws NullPointerException If {@code dos} is {@code null}. + * @throws MaxDepthReachedException If the maximum nesting depth is exceeded. * */ - public final void serialize(DataOutputStream dos, int depth) throws IOException { - serialize(dos, "", depth); + public final void serialize(DataOutputStream dos, int maxDepth) throws IOException { + serialize(dos, "", maxDepth); } /** * Serializes this Tag starting at the gives depth. * @param dos The DataOutputStream to write to. * @param name The name of this Tag, if this is the root Tag. - * @param depth The current depth of the structure. + * @param maxDepth The maximum nesting depth * @throws IOException If something went wrong during serialization. - * @exception NullPointerException If {@code dos} or {@code name} is {@code null}. - * @exception MaxDepthReachedException If the structure depth exceeds {@link Tag#MAX_DEPTH}. + * @throws NullPointerException If {@code dos} or {@code name} is {@code null}. + * @throws MaxDepthReachedException If the maximum nesting depth is exceeded. * */ - public final void serialize(DataOutputStream dos, String name, int depth) throws IOException { + public final void serialize(DataOutputStream dos, String name, int maxDepth) throws IOException { dos.writeByte(getID()); if (getID() != 0) { dos.writeUTF(name); } - serializeValue(dos, depth); + serializeValue(dos, maxDepth); } /** * Deserializes this Tag starting at the given depth. * The name of the root Tag is ignored. * @param dis The DataInputStream to read from. - * @param depth The current depth of the structure. + * @param maxDepth The maximum nesting depth. * @throws IOException If something went wrong during deserialization. - * @exception NullPointerException If {@code dis} is {@code null}. - * @exception MaxDepthReachedException If the structure depth exceeds {@link Tag#MAX_DEPTH}. + * @throws NullPointerException If {@code dis} is {@code null}. + * @throws MaxDepthReachedException If the maximum nesting depth is exceeded. * @return The deserialized NBT structure. * */ - public static Tag deserialize(DataInputStream dis, int depth) throws IOException { + public static Tag deserialize(DataInputStream dis, int maxDepth) throws IOException { int id = dis.readByte() & 0xFF; Tag tag = TagFactory.fromID(id); if (id != 0) { dis.readUTF(); - tag.deserializeValue(dis, depth); + tag.deserializeValue(dis, maxDepth); } return tag; } @@ -127,75 +152,76 @@ public static Tag deserialize(DataInputStream dis, int depth) throws IOExcept /** * Serializes only the value of this Tag. * @param dos The DataOutputStream to write to. - * @param depth The current depth of the structure. + * @param maxDepth The maximum nesting depth. * @throws IOException If something went wrong during serialization. - * @exception MaxDepthReachedException If the structure depth exceeds {@link Tag#MAX_DEPTH}. + * @throws MaxDepthReachedException If the maximum nesting depth is exceeded. * */ - public abstract void serializeValue(DataOutputStream dos, int depth) throws IOException; + public abstract void serializeValue(DataOutputStream dos, int maxDepth) throws IOException; /** * Deserializes only the value of this Tag. * @param dis The DataInputStream to read from. - * @param depth The current depth of the structure. + * @param maxDepth The maximum nesting depth. * @throws IOException If something went wrong during deserialization. - * @exception MaxDepthReachedException If the structure depth exceeds {@link Tag#MAX_DEPTH}. + * @throws MaxDepthReachedException If the maximum nesting depth is exceeded * */ - public abstract void deserializeValue(DataInputStream dis, int depth) throws IOException; + public abstract void deserializeValue(DataInputStream dis, int maxDepth) throws IOException; /** * Calls {@link Tag#toString(int)} with an initial depth of {@code 0}. * @see Tag#toString(int) + * @throws MaxDepthReachedException If the maximum nesting depth is exceeded. * */ @Override - public String toString() { - return toString(0); + public final String toString() { + return toString(DEFAULT_MAX_DEPTH); } /** * Creates a string representation of this Tag in a valid JSON format. - * @param depth The current depth of the structure. + * @param maxDepth The maximum nesting depth. * @return The string representation of this Tag. - * @exception MaxDepthReachedException If the structure depth exceeds {@link Tag#MAX_DEPTH}. + * @throws MaxDepthReachedException If the maximum nesting depth is exceeded. * */ - public String toString(int depth) { + public String toString(int maxDepth) { return "{\"type\":\""+ getClass().getSimpleName() + "\"," + - "\"value\":" + valueToString(depth) + "}"; + "\"value\":" + valueToString(maxDepth) + "}"; } /** * Returns a JSON representation of the value of this Tag. - * @param depth The current depth of the structure. + * @param maxDepth The maximum nesting depth. * @return The string representation of the value of this Tag. - * @exception MaxDepthReachedException If the structure depth exceeds {@link Tag#MAX_DEPTH}. + * @throws MaxDepthReachedException If the maximum nesting depth is exceeded. * */ - public abstract String valueToString(int depth); + public abstract String valueToString(int maxDepth); /** - * Calls {@link Tag#toTagString(int)} with an initial depth of {@code 0}. + * Calls {@link Tag#toTagString(int)} with {@link #DEFAULT_MAX_DEPTH}. * @see Tag#toTagString(int) * @return The JSON-like string representation of this Tag. * */ - public String toTagString() { - return toTagString(0); + public final String toTagString() { + return toTagString(DEFAULT_MAX_DEPTH); } /** * Returns a JSON-like representation of the value of this Tag, usually used for Minecraft commands. - * @param depth The current depth of the structure. + * @param maxDepth The maximum nesting depth. * @return The JSON-like string representation of this Tag. - * @exception MaxDepthReachedException If the structure depth exceeds {@link Tag#MAX_DEPTH}. + * @throws MaxDepthReachedException If the maximum nesting depth is exceeded. * */ - public String toTagString(int depth) { - return valueToTagString(depth); + public String toTagString(int maxDepth) { + return valueToTagString(maxDepth); } /** * Returns a JSON-like representation of the value of this Tag. - * @param depth The current depth of the structure. + * @param maxDepth The maximum nesting depth. * @return The JSON-like string representation of the value of this Tag. - * @exception MaxDepthReachedException If the structure depth exceeds {@link Tag#MAX_DEPTH}. + * @throws MaxDepthReachedException If the maximum nesting depth is exceeded. * */ - public abstract String valueToTagString(int depth); + public abstract String valueToTagString(int maxDepth); /** * Returns whether this Tag and some other Tag are equal. @@ -228,34 +254,22 @@ public int hashCode() { public abstract Tag clone(); /** - * A utility method to check if some value is {@code null}. - * @param Any type that should be checked for being {@code null} - * @param v The value to check. - * @return {@code v}, if it's not {@code null}. - * @exception NullPointerException If {@code v} is {@code null}. - * */ - protected V checkNull(V v) { - if (v == null) { - throw new NullPointerException(getClass().getSimpleName() + " does not allow setting null"); - } - return v; - } - - /** - * Increments {@code depth} by {@code 1}. - * @param depth The value to increment. - * @return The incremented value. - * @exception MaxDepthReachedException If {@code depth} is {@code >=} {@link Tag#MAX_DEPTH}. - * @exception IllegalArgumentException If {@code depth} is {@code <} {@code 0} + * Decrements {@code maxDepth} by {@code 1}. This method has to be used when a tag is + * (de-)serialized and contains nested tags. Their respective methods are then called + * with {@code decrementMaxDepth(maxDepth)} as maximum nesting depth. + * + * @param maxDepth The value to decrement. + * @return The decremented value. + * @throws MaxDepthReachedException If {@code maxDepth == 0}. + * @throws IllegalArgumentException If {@code maxDepth < 0}. * */ - protected int incrementDepth(int depth) { - if (depth >= MAX_DEPTH) { - throw new MaxDepthReachedException("reached maximum depth (" + Tag.MAX_DEPTH + ") of NBT structure"); - } - if (depth < 0) { - throw new IllegalArgumentException("initial depth cannot be negative"); + protected int decrementMaxDepth(int maxDepth) { + if (maxDepth < 0) { + throw new IllegalArgumentException("negative maximum depth is not allowed"); + } else if (maxDepth == 0) { + throw new MaxDepthReachedException("reached maximum depth of NBT structure"); } - return ++depth; + return --maxDepth; } /** diff --git a/src/main/java/net/querz/nbt/custom/CharTag.java b/src/main/java/net/querz/nbt/custom/CharTag.java index 9adaa4cf..cba9654c 100644 --- a/src/main/java/net/querz/nbt/custom/CharTag.java +++ b/src/main/java/net/querz/nbt/custom/CharTag.java @@ -6,23 +6,22 @@ import java.io.DataOutputStream; import java.io.IOException; -public class CharTag extends Tag { +public class CharTag extends Tag implements Comparable { + + public static final char ZERO_VALUE = '\u0000'; public static void register() { TagFactory.registerCustomTag(110, CharTag::new, CharTag.class); } - public CharTag() {} + public CharTag() { + super(ZERO_VALUE); + } public CharTag(char value) { super(value); } - @Override - protected Character getEmptyValue() { - return 0; - } - @Override public Character getValue() { return super.getValue(); @@ -33,22 +32,22 @@ public void setValue(char value) { } @Override - public void serializeValue(DataOutputStream dos, int depth) throws IOException { + public void serializeValue(DataOutputStream dos, int maxDepth) throws IOException { dos.writeChar(getValue()); } @Override - public void deserializeValue(DataInputStream dis, int depth) throws IOException { + public void deserializeValue(DataInputStream dis, int maxDepth) throws IOException { setValue(dis.readChar()); } @Override - public String valueToString(int depth) { + public String valueToString(int maxDepth) { return escapeString(getValue() + "", false); } @Override - public String valueToTagString(int depth) { + public String valueToTagString(int maxDepth) { return escapeString(getValue() + "", true); } @@ -58,8 +57,8 @@ public boolean equals(Object other) { } @Override - public int compareTo(Tag o) { - return Character.compare(getValue(), ((CharTag) o).getValue()); + public int compareTo(CharTag o) { + return Character.compare(getValue(), o.getValue()); } @Override diff --git a/src/main/java/net/querz/nbt/custom/ObjectTag.java b/src/main/java/net/querz/nbt/custom/ObjectTag.java index a31d76e6..92ea5624 100644 --- a/src/main/java/net/querz/nbt/custom/ObjectTag.java +++ b/src/main/java/net/querz/nbt/custom/ObjectTag.java @@ -2,26 +2,33 @@ import net.querz.nbt.Tag; import net.querz.nbt.TagFactory; - -import java.io.*; +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; +import java.io.InvalidClassException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.Serializable; import java.lang.reflect.InvocationTargetException; import java.util.Objects; -public class ObjectTag extends Tag { +public class ObjectTag extends Tag implements Comparable> { public static void register() { TagFactory.registerCustomTag(90, ObjectTag::new, ObjectTag.class); } - public ObjectTag() {} + public ObjectTag() { + super(null); + } public ObjectTag(T value) { super(value); } @Override - protected T getEmptyValue() { - return null; + protected T checkValue(T value) { + return value; } @Override @@ -41,13 +48,13 @@ public ObjectTag asTypedObjectTag(Class type) { } @Override - public void serializeValue(DataOutputStream dos, int depth) throws IOException { + public void serializeValue(DataOutputStream dos, int maxDepth) throws IOException { new ObjectOutputStream(dos).writeObject(getValue()); } @SuppressWarnings("unchecked") @Override - public void deserializeValue(DataInputStream dis, int depth) throws IOException { + public void deserializeValue(DataInputStream dis, int maxDepth) throws IOException { try { setValue((T) new ObjectInputStream(dis).readObject()); } catch (InvalidClassException | ClassNotFoundException e) { @@ -56,12 +63,12 @@ public void deserializeValue(DataInputStream dis, int depth) throws IOException } @Override - public String valueToString(int depth) { + public String valueToString(int maxDepth) { return getValue() == null ? "null" : escapeString(getValue().toString(), false); } @Override - public String valueToTagString(int depth) { + public String valueToTagString(int maxDepth) { return getValue() == null ? "null" : escapeString(getValue().toString(), true); } @@ -72,15 +79,24 @@ public boolean equals(Object other) { @Override public int hashCode() { + if (getValue() == null) { + return 0; + } return getValue().hashCode(); } @SuppressWarnings("unchecked") @Override - public int compareTo(Tag o) { - ObjectTag oo = (ObjectTag) o; - if (oo.getValue() instanceof Comparable && getValue() instanceof Comparable) { - return ((Comparable) getValue()).compareTo(oo.getValue()); + public int compareTo(ObjectTag o) { + if (o.getValue() instanceof Comparable && getValue() instanceof Comparable) { + return ((Comparable) getValue()).compareTo(o.getValue()); + } else if (o.getValue() == getValue()) { + return 0; + } else if (getValue() == null) { + // sort a null value to the end + return 1; + } else if (o.getValue() == null) { + return -1; } return 0; } @@ -88,9 +104,12 @@ public int compareTo(Tag o) { @SuppressWarnings("unchecked") @Override public ObjectTag clone() { + if (getValue() == null) { + return new ObjectTag<>(); + } try { return new ObjectTag<>((T) getValue().getClass().getMethod("clone").invoke(getValue())); - } catch (NullPointerException | IllegalAccessException | InvocationTargetException | NoSuchMethodException e) { + } catch (IllegalAccessException | InvocationTargetException | NoSuchMethodException e) { return new ObjectTag<>(getValue()); } } diff --git a/src/main/java/net/querz/nbt/custom/ShortArrayTag.java b/src/main/java/net/querz/nbt/custom/ShortArrayTag.java index 7c98bf60..ba46d33e 100644 --- a/src/main/java/net/querz/nbt/custom/ShortArrayTag.java +++ b/src/main/java/net/querz/nbt/custom/ShortArrayTag.java @@ -7,14 +7,16 @@ import java.io.IOException; import java.util.Arrays; -public class ShortArrayTag extends ArrayTag { +public class ShortArrayTag extends ArrayTag implements Comparable { + + public static final short[] ZERO_VALUE = new short[0]; public static void register() { TagFactory.registerCustomTag(100, ShortArrayTag::new, ShortArrayTag.class); } public ShortArrayTag() { - super(new short[0]); + super(ZERO_VALUE); } public ShortArrayTag(short[] value) { @@ -22,12 +24,7 @@ public ShortArrayTag(short[] value) { } @Override - protected short[] getEmptyValue() { - return new short[0]; - } - - @Override - public void serializeValue(DataOutputStream dos, int depth) throws IOException { + public void serializeValue(DataOutputStream dos, int maxDepth) throws IOException { dos.writeInt(length()); for (int i : getValue()) { dos.writeShort(i); @@ -35,7 +32,7 @@ public void serializeValue(DataOutputStream dos, int depth) throws IOException { } @Override - public void deserializeValue(DataInputStream dis, int depth) throws IOException { + public void deserializeValue(DataInputStream dis, int maxDepth) throws IOException { int length = dis.readInt(); setValue(new short[length]); for (int i = 0; i < length; i++) { @@ -44,7 +41,7 @@ public void deserializeValue(DataInputStream dis, int depth) throws IOException } @Override - public String valueToTagString(int depth) { + public String valueToTagString(int maxDepth) { return arrayToString("S", "s"); } @@ -61,6 +58,11 @@ public int hashCode() { return Arrays.hashCode(getValue()); } + @Override + public int compareTo(ShortArrayTag other) { + return Integer.compare(length(), other.length()); + } + @Override public ShortArrayTag clone() { return new ShortArrayTag(Arrays.copyOf(getValue(), length())); diff --git a/src/main/java/net/querz/nbt/custom/StructTag.java b/src/main/java/net/querz/nbt/custom/StructTag.java index 27a5a200..47c7215c 100644 --- a/src/main/java/net/querz/nbt/custom/StructTag.java +++ b/src/main/java/net/querz/nbt/custom/StructTag.java @@ -17,19 +17,24 @@ import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.IOException; -import java.util.*; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Iterator; +import java.util.List; +import java.util.Objects; import java.util.function.Consumer; -public class StructTag extends Tag>> implements Iterable> { +public class StructTag extends Tag>> implements Iterable>, Comparable { public static void register() { TagFactory.registerCustomTag(120, StructTag::new, StructTag.class); } - public StructTag() {} + public StructTag() { + super(createEmptyValue()); + } - @Override - protected List> getEmptyValue() { + private static List> createEmptyValue() { return new ArrayList<>(3); } @@ -170,15 +175,15 @@ public long[] getLongArray(int index) { } public Tag set(int index, Tag tag) { - return getValue().set(index, checkNull(tag)); + return getValue().set(index, Objects.requireNonNull(tag)); } public void add(Tag tag) { - getValue().add(checkNull(tag)); + getValue().add(Objects.requireNonNull(tag)); } public void add(int index, Tag tag) { - getValue().add(index, checkNull(tag)); + getValue().add(index, Objects.requireNonNull(tag)); } public void addBoolean(boolean value) { @@ -210,56 +215,57 @@ public void addDouble(double value) { } public void addString(String value) { - add(new StringTag(checkNull(value))); + add(new StringTag(value)); } public void addByteArray(byte[] value) { - add(new ByteArrayTag(checkNull(value))); + add(new ByteArrayTag(value)); } public void addIntArray(int[] value) { - add(new IntArrayTag(checkNull(value))); + add(new IntArrayTag(value)); } public void addLongArray(long[] value) { - add(new LongArrayTag(checkNull(value))); + add(new LongArrayTag(value)); } @Override - public void serializeValue(DataOutputStream dos, int depth) throws IOException { + public void serializeValue(DataOutputStream dos, int maxDepth) throws IOException { dos.writeInt(size()); for (Tag tag : getValue()) { dos.writeByte(tag.getID()); - tag.serializeValue(dos, incrementDepth(depth)); + tag.serializeValue(dos, decrementMaxDepth(maxDepth)); } } @Override - public void deserializeValue(DataInputStream dis, int depth) throws IOException { + public void deserializeValue(DataInputStream dis, int maxDepth) throws IOException { int size = dis.readInt(); + size = size < 0 ? 0 : size; setValue(new ArrayList<>(size)); for (int i = 0; i < size; i++) { Tag tag = TagFactory.fromID(dis.readByte()); - tag.deserializeValue(dis, incrementDepth(depth)); + tag.deserializeValue(dis, decrementMaxDepth(maxDepth)); add(tag); } } @Override - public String valueToString(int depth) { + public String valueToString(int maxDepth) { StringBuilder sb = new StringBuilder("["); for (int i = 0; i < size(); i++) { - sb.append(i > 0 ? "," : "").append(get(i).toString(incrementDepth(depth))); + sb.append(i > 0 ? "," : "").append(get(i).toString(decrementMaxDepth(maxDepth))); } sb.append("]"); return sb.toString(); } @Override - public String valueToTagString(int depth) { + public String valueToTagString(int maxDepth) { StringBuilder sb = new StringBuilder("["); for (int i = 0; i < size(); i++) { - sb.append(i > 0 ? "," : "").append(get(i).valueToTagString(incrementDepth(depth))); + sb.append(i > 0 ? "," : "").append(get(i).valueToTagString(decrementMaxDepth(maxDepth))); } sb.append("]"); return sb.toString(); @@ -284,11 +290,8 @@ public int hashCode() { } @Override - public int compareTo(Tag>> o) { - if (!(o instanceof StructTag)) { - return 0; - } - return Integer.compare(size(), ((StructTag) o).size()); + public int compareTo(StructTag o) { + return Integer.compare(size(), o.size()); } @Override diff --git a/src/main/java/net/querz/nbt/mca/Chunk.java b/src/main/java/net/querz/nbt/mca/Chunk.java index b56a7a57..3f0ae94d 100644 --- a/src/main/java/net/querz/nbt/mca/Chunk.java +++ b/src/main/java/net/querz/nbt/mca/Chunk.java @@ -81,7 +81,7 @@ private void initReferences() { public int serialize(RandomAccessFile raf, int xPos, int zPos) throws IOException { ByteArrayOutputStream baos = new ByteArrayOutputStream(4096); try (DataOutputStream nbtOut = new DataOutputStream(new BufferedOutputStream(CompressionType.ZLIB.compress(baos)))) { - updateHandle(xPos, zPos).serialize(nbtOut, 0); + updateHandle(xPos, zPos).serialize(nbtOut, Tag.DEFAULT_MAX_DEPTH); } byte[] rawData = baos.toByteArray(); raf.writeInt(rawData.length); @@ -97,7 +97,7 @@ public void deserialize(RandomAccessFile raf) throws IOException { throw new IOException("invalid compression type " + compressionTypeByte); } DataInputStream dis = new DataInputStream(new BufferedInputStream(compressionType.decompress(new FileInputStream(raf.getFD())))); - Tag tag = Tag.deserialize(dis, 0); + Tag tag = Tag.deserialize(dis, Tag.DEFAULT_MAX_DEPTH); if (tag instanceof CompoundTag) { data = (CompoundTag) tag; initReferences(); diff --git a/src/test/java/net/querz/nbt/ByteArrayTagTest.java b/src/test/java/net/querz/nbt/ByteArrayTagTest.java index 157ba3cb..8de2f590 100644 --- a/src/test/java/net/querz/nbt/ByteArrayTagTest.java +++ b/src/test/java/net/querz/nbt/ByteArrayTagTest.java @@ -47,7 +47,7 @@ public void testCompareTo() { assertEquals(0, t.compareTo(t3)); assertTrue(0 < t.compareTo(t4)); assertTrue(0 > t4.compareTo(t)); - assertThrowsRuntimeException(() -> t.compareTo(null), IllegalArgumentException.class); + assertThrowsRuntimeException(() -> t.compareTo(null), NullPointerException.class); } public void testInvalidType() { @@ -65,11 +65,6 @@ public NotAnArrayTag(String value) { super(value); } - @Override - protected String getEmptyValue() { - return ""; - } - @Override public void serializeValue(DataOutputStream dos, int depth) { throw new UnsupportedOperationException("goddammit, this is a test class, you don't want to save it."); diff --git a/src/test/java/net/querz/nbt/ByteTagTest.java b/src/test/java/net/querz/nbt/ByteTagTest.java index e25d2b7a..de8b604a 100644 --- a/src/test/java/net/querz/nbt/ByteTagTest.java +++ b/src/test/java/net/querz/nbt/ByteTagTest.java @@ -42,7 +42,7 @@ public void testCompareTo() { assertEquals(0, new ByteTag((byte) 5).compareTo(new ByteTag((byte) 5))); assertTrue(0 < new ByteTag((byte) 7).compareTo(new ByteTag((byte) 5))); assertTrue(0 > new ByteTag((byte) 5).compareTo(new ByteTag((byte) 7))); - assertThrowsRuntimeException(() -> new ByteTag((byte) 5).compareTo(null), IllegalArgumentException.class); + assertThrowsRuntimeException(() -> new ByteTag((byte) 5).compareTo(null), NullPointerException.class); } public void testBoolean() { diff --git a/src/test/java/net/querz/nbt/CompoundTagTest.java b/src/test/java/net/querz/nbt/CompoundTagTest.java index f28d06cc..b511a335 100644 --- a/src/test/java/net/querz/nbt/CompoundTagTest.java +++ b/src/test/java/net/querz/nbt/CompoundTagTest.java @@ -262,20 +262,22 @@ public void testCompareTo() { co.remove("five"); co.remove("four"); assertEquals(1, ci.compareTo(co)); - assertEquals(0, ci.compareTo(null)); + assertThrowsRuntimeException(() -> ci.compareTo(null), NullPointerException.class); } public void testMaxDepth() { CompoundTag root = new CompoundTag(); CompoundTag rec = root; - for (int i = 0; i < Tag.MAX_DEPTH + 1; i++) { + for (int i = 0; i < Tag.DEFAULT_MAX_DEPTH + 1; i++) { CompoundTag c = new CompoundTag(); rec.put("c" + i, c); rec = c; } assertThrowsRuntimeException(() -> serialize(root), MaxDepthReachedException.class); assertThrowsRuntimeException(() -> deserializeFromFile("max_depth_reached.dat"), MaxDepthReachedException.class); + assertThrowsNoRuntimeException(() -> root.toString(Tag.DEFAULT_MAX_DEPTH + 1)); assertThrowsRuntimeException(root::toString, MaxDepthReachedException.class); + assertThrowsNoRuntimeException(() -> root.toTagString(Tag.DEFAULT_MAX_DEPTH + 1)); assertThrowsRuntimeException(root::toTagString, MaxDepthReachedException.class); assertThrowsRuntimeException(() -> root.valueToString(-1), IllegalArgumentException.class); assertThrowsRuntimeException(() -> root.valueToTagString(-1), IllegalArgumentException.class); diff --git a/src/test/java/net/querz/nbt/DoubleTagTest.java b/src/test/java/net/querz/nbt/DoubleTagTest.java index c4a68bfb..2444aebc 100644 --- a/src/test/java/net/querz/nbt/DoubleTagTest.java +++ b/src/test/java/net/querz/nbt/DoubleTagTest.java @@ -39,6 +39,6 @@ public void testCompareTo() { assertEquals(0, new DoubleTag(5).compareTo(new DoubleTag(5))); assertTrue(0 < new DoubleTag(7).compareTo(new DoubleTag(5))); assertTrue(0 > new DoubleTag(5).compareTo(new DoubleTag(7))); - assertThrowsRuntimeException(() -> new DoubleTag(5).compareTo(null), IllegalArgumentException.class); + assertThrowsRuntimeException(() -> new DoubleTag(5).compareTo(null), NullPointerException.class); } } diff --git a/src/test/java/net/querz/nbt/EndTagTest.java b/src/test/java/net/querz/nbt/EndTagTest.java index e537f2f5..72c538b5 100644 --- a/src/test/java/net/querz/nbt/EndTagTest.java +++ b/src/test/java/net/querz/nbt/EndTagTest.java @@ -18,9 +18,4 @@ public void testSerializeDeserialize() { assertThrowsNoRuntimeException(() -> EndTag.INSTANCE.serializeValue(null, 0)); assertThrowsNoRuntimeException(() -> EndTag.INSTANCE.deserializeValue(null, 0)); } - - public void testCompareTo() { - assertEquals(0, EndTag.INSTANCE.compareTo(EndTag.INSTANCE.clone())); - assertEquals(0, EndTag.INSTANCE.compareTo(null)); - } } diff --git a/src/test/java/net/querz/nbt/FloatTagTest.java b/src/test/java/net/querz/nbt/FloatTagTest.java index 92b47a09..1d6e8172 100644 --- a/src/test/java/net/querz/nbt/FloatTagTest.java +++ b/src/test/java/net/querz/nbt/FloatTagTest.java @@ -39,6 +39,6 @@ public void testCompareTo() { assertEquals(0, new FloatTag(5).compareTo(new FloatTag(5))); assertTrue(0 < new FloatTag(7).compareTo(new FloatTag(5))); assertTrue(0 > new FloatTag(5).compareTo(new FloatTag(7))); - assertThrowsRuntimeException(() -> new FloatTag(5).compareTo(null), IllegalArgumentException.class); + assertThrowsRuntimeException(() -> new FloatTag(5).compareTo(null), NullPointerException.class); } } diff --git a/src/test/java/net/querz/nbt/IntArrayTagTest.java b/src/test/java/net/querz/nbt/IntArrayTagTest.java index e369896e..e02c686e 100644 --- a/src/test/java/net/querz/nbt/IntArrayTagTest.java +++ b/src/test/java/net/querz/nbt/IntArrayTagTest.java @@ -45,6 +45,6 @@ public void testCompareTo() { assertEquals(0, t.compareTo(t3)); assertTrue(0 < t.compareTo(t4)); assertTrue(0 > t4.compareTo(t)); - assertThrowsRuntimeException(() -> t.compareTo(null), IllegalArgumentException.class); + assertThrowsRuntimeException(() -> t.compareTo(null), NullPointerException.class); } } diff --git a/src/test/java/net/querz/nbt/IntTagTest.java b/src/test/java/net/querz/nbt/IntTagTest.java index 305f7670..d62ad42e 100644 --- a/src/test/java/net/querz/nbt/IntTagTest.java +++ b/src/test/java/net/querz/nbt/IntTagTest.java @@ -40,6 +40,6 @@ public void testCompareTo() { assertEquals(0, new IntTag(5).compareTo(new IntTag(5))); assertTrue(0 < new IntTag(7).compareTo(new IntTag(5))); assertTrue(0 > new IntTag(5).compareTo(new IntTag(7))); - assertThrowsRuntimeException(() -> new IntTag(5).compareTo(null), IllegalArgumentException.class); + assertThrowsRuntimeException(() -> new IntTag(5).compareTo(null), NullPointerException.class); } } diff --git a/src/test/java/net/querz/nbt/ListTagTest.java b/src/test/java/net/querz/nbt/ListTagTest.java index 3f8a340f..40602c13 100644 --- a/src/test/java/net/querz/nbt/ListTagTest.java +++ b/src/test/java/net/querz/nbt/ListTagTest.java @@ -218,13 +218,13 @@ public void testCompareTo() { lo.remove(2); lo.remove(1); assertEquals(1, li.compareTo(lo)); - assertEquals(0, li.compareTo(null)); + assertThrowsRuntimeException(() -> li.compareTo(null), NullPointerException.class); } public void testMaxDepth() { ListTag> root = new ListTag<>(ListTag.class); ListTag> rec = root; - for (int i = 0; i < Tag.MAX_DEPTH + 1; i++) { + for (int i = 0; i < Tag.DEFAULT_MAX_DEPTH + 1; i++) { ListTag> l = new ListTag<>(ListTag.class); rec.add(l); rec = l; diff --git a/src/test/java/net/querz/nbt/LongArrayTagTest.java b/src/test/java/net/querz/nbt/LongArrayTagTest.java index 8d8e92bc..a2fe39c4 100644 --- a/src/test/java/net/querz/nbt/LongArrayTagTest.java +++ b/src/test/java/net/querz/nbt/LongArrayTagTest.java @@ -45,6 +45,6 @@ public void testCompareTo() { assertEquals(0, t.compareTo(t3)); assertTrue(0 < t.compareTo(t4)); assertTrue(0 > t4.compareTo(t)); - assertThrowsRuntimeException(() -> t.compareTo(null), IllegalArgumentException.class); + assertThrowsRuntimeException(() -> t.compareTo(null), NullPointerException.class); } } diff --git a/src/test/java/net/querz/nbt/LongTagTest.java b/src/test/java/net/querz/nbt/LongTagTest.java index 3d2ccef4..6d9ea1e1 100644 --- a/src/test/java/net/querz/nbt/LongTagTest.java +++ b/src/test/java/net/querz/nbt/LongTagTest.java @@ -39,6 +39,6 @@ public void testCompareTo() { assertEquals(0, new LongTag(5).compareTo(new LongTag(5))); assertTrue(0 < new LongTag(7).compareTo(new LongTag(5))); assertTrue(0 > new LongTag(5).compareTo(new LongTag(7))); - assertThrowsRuntimeException(() -> new LongTag(5).compareTo(null), IllegalArgumentException.class); + assertThrowsRuntimeException(() -> new LongTag(5).compareTo(null), NullPointerException.class); } } diff --git a/src/test/java/net/querz/nbt/NBTTestCase.java b/src/test/java/net/querz/nbt/NBTTestCase.java index 33582b50..52d00aa5 100644 --- a/src/test/java/net/querz/nbt/NBTTestCase.java +++ b/src/test/java/net/querz/nbt/NBTTestCase.java @@ -34,7 +34,7 @@ public void tearDown() throws Exception { protected byte[] serialize(Tag tag) { ByteArrayOutputStream baos = new ByteArrayOutputStream(); try (DataOutputStream dos = new DataOutputStream(baos)) { - tag.serialize(dos, 0); + tag.serialize(dos, Tag.DEFAULT_MAX_DEPTH); } catch (IOException ex) { ex.printStackTrace(); fail(ex.getMessage()); @@ -44,7 +44,7 @@ protected byte[] serialize(Tag tag) { protected Tag deserialize(byte[] data) { try (DataInputStream dis = new DataInputStream(new ByteArrayInputStream(data))) { - return Tag.deserialize(dis, 0); + return Tag.deserialize(dis, Tag.DEFAULT_MAX_DEPTH); } catch (IOException ex) { ex.printStackTrace(); fail(ex.getMessage()); @@ -60,7 +60,7 @@ protected File getResourceFile(String name) { protected Tag deserializeFromFile(String f) { try (DataInputStream dis = new DataInputStream(new FileInputStream(getResourceFile(f)))) { - return Tag.deserialize(dis, 0); + return Tag.deserialize(dis, Tag.DEFAULT_MAX_DEPTH); } catch (IOException ex) { ex.printStackTrace(); fail(ex.getMessage()); diff --git a/src/test/java/net/querz/nbt/ShortTagTest.java b/src/test/java/net/querz/nbt/ShortTagTest.java index 20017a0b..ec039a7d 100644 --- a/src/test/java/net/querz/nbt/ShortTagTest.java +++ b/src/test/java/net/querz/nbt/ShortTagTest.java @@ -41,6 +41,6 @@ public void testCompareTo() { assertEquals(0, new ShortTag((short) 5).compareTo(new ShortTag((short) 5))); assertTrue(0 < new ShortTag((short) 7).compareTo(new ShortTag((short) 5))); assertTrue(0 > new ShortTag((short) 5).compareTo(new ShortTag((short) 7))); - assertThrowsRuntimeException(() -> new ShortTag((short) 5).compareTo(null), IllegalArgumentException.class); + assertThrowsRuntimeException(() -> new ShortTag((short) 5).compareTo(null), NullPointerException.class); } } diff --git a/src/test/java/net/querz/nbt/StringTagTest.java b/src/test/java/net/querz/nbt/StringTagTest.java index caddbf7a..da6b39fb 100644 --- a/src/test/java/net/querz/nbt/StringTagTest.java +++ b/src/test/java/net/querz/nbt/StringTagTest.java @@ -56,6 +56,6 @@ public void testCompareTo() { assertEquals(0, t.compareTo(t2)); assertTrue(0 > t.compareTo(t3)); assertTrue(0 < t3.compareTo(t)); - assertThrowsRuntimeException(() -> t.compareTo(null), IllegalArgumentException.class); + assertThrowsRuntimeException(() -> t.compareTo(null), NullPointerException.class); } } diff --git a/src/test/java/net/querz/nbt/custom/ObjectTagTest.java b/src/test/java/net/querz/nbt/custom/ObjectTagTest.java index 2cda492b..e069d88c 100644 --- a/src/test/java/net/querz/nbt/custom/ObjectTagTest.java +++ b/src/test/java/net/querz/nbt/custom/ObjectTagTest.java @@ -6,6 +6,9 @@ import static org.junit.Assert.assertNotEquals; import java.io.IOException; import java.io.Serializable; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; import java.util.Objects; import java.util.Random; @@ -38,6 +41,7 @@ public void testHashCode() { ObjectTag o2 = new ObjectTag<>(d2); assertNotEquals(o.hashCode(), o2.hashCode()); assertEquals(o.hashCode(), o.clone().hashCode()); + assertEquals(0, new ObjectTag().hashCode()); } public void testClone() { @@ -112,6 +116,28 @@ public void testCompareTo() { ObjectTag d4 = new ObjectTag<>("abd"); assertTrue(0 > d3.compareTo(d4)); assertTrue(0 < d4.compareTo(d3)); + + ObjectTag d5 = new ObjectTag<>("abc"); + assertEquals(0, d3.compareTo(d5)); + + DummyObject o = new DummyObject(); + ObjectTag d6 = new ObjectTag<>(o); + ObjectTag d7 = new ObjectTag<>(o); + assertEquals(0, d6.compareTo(d7)); + + ObjectTag d8 = new ObjectTag<>(); + assertEquals(1, d8.compareTo(d7)); + assertEquals(-1, d7.compareTo(d8)); + + ObjectTag d9 = new ObjectTag<>(); + assertEquals(0, d8.compareTo(d9)); + + List> l = new ArrayList<>(); + l.add(d); + l.add(d9); + l.add(d2); + l.sort(Comparator.naturalOrder()); + assertEquals(d9, l.get(2)); } public void testUnknownObject() { diff --git a/src/test/java/net/querz/nbt/custom/ShortArrayTagTest.java b/src/test/java/net/querz/nbt/custom/ShortArrayTagTest.java index 924104ee..b4a7fd5b 100644 --- a/src/test/java/net/querz/nbt/custom/ShortArrayTagTest.java +++ b/src/test/java/net/querz/nbt/custom/ShortArrayTagTest.java @@ -56,6 +56,6 @@ public void testCompareTo() { assertEquals(0, t.compareTo(t3)); assertTrue(0 < t.compareTo(t4)); assertTrue(0 > t4.compareTo(t)); - assertThrowsRuntimeException(() -> t.compareTo(null), IllegalArgumentException.class); + assertThrowsRuntimeException(() -> t.compareTo(null), NullPointerException.class); } } diff --git a/src/test/java/net/querz/nbt/custom/StructTagTest.java b/src/test/java/net/querz/nbt/custom/StructTagTest.java index f1c03855..77c8cfbc 100644 --- a/src/test/java/net/querz/nbt/custom/StructTagTest.java +++ b/src/test/java/net/querz/nbt/custom/StructTagTest.java @@ -86,7 +86,7 @@ public void testCompareTo() { so.remove(2); so.remove(1); assertEquals(1, st.compareTo(so)); - assertEquals(0, st.compareTo(null)); + assertThrowsRuntimeException(() -> st.compareTo(null), NullPointerException.class); } public void testContains() {