From 5ea8b922c2d3dc5d306acfbf388d6db6418b2e85 Mon Sep 17 00:00:00 2001 From: artfable Date: Sat, 5 Oct 2019 16:47:35 +0200 Subject: [PATCH 01/51] fix bug, now works with Vanilla servers --- build.gradle | 16 ++++++++++++++++ src/main/java/net/querz/nbt/ListTag.java | 2 +- src/main/java/net/querz/nbt/mca/Chunk.java | 16 +++++----------- src/main/java/net/querz/nbt/mca/MCAFile.java | 3 ++- src/main/java/net/querz/nbt/mca/Section.java | 15 ++++++++++++--- 5 files changed, 36 insertions(+), 16 deletions(-) diff --git a/build.gradle b/build.gradle index c992faa4..4a37cb5e 100644 --- a/build.gradle +++ b/build.gradle @@ -1,5 +1,6 @@ plugins { id 'com.github.kt3k.coveralls' version '2.4.0' + id 'maven-publish' } apply plugin: 'java' @@ -59,3 +60,18 @@ jar { attributes('Automatic-Module-Name': 'net.querz.nbt') } } + +// for convenience, for pushing to local +publishing { + publications { + maven(MavenPublication) { + groupId = group + artifactId = archivesBaseName + version = version + artifact sourcesJar + artifact javadocJar + + from components.java + } + } +} diff --git a/src/main/java/net/querz/nbt/ListTag.java b/src/main/java/net/querz/nbt/ListTag.java index 321c53f4..a0fc096d 100644 --- a/src/main/java/net/querz/nbt/ListTag.java +++ b/src/main/java/net/querz/nbt/ListTag.java @@ -253,7 +253,7 @@ public ListTag asCompoundTagList() { @Override public void serializeValue(DataOutputStream dos, int maxDepth) throws IOException { - dos.writeByte(TagFactory.idFromClass(getTypeClass())); + dos.writeByte(size() != 0 ? TagFactory.idFromClass(getTypeClass()) : 0); // paylod type should be 0 in empty case dos.writeInt(size()); if (size() != 0) { for (T t : getValue()) { diff --git a/src/main/java/net/querz/nbt/mca/Chunk.java b/src/main/java/net/querz/nbt/mca/Chunk.java index 5ab0fe51..f299a107 100644 --- a/src/main/java/net/querz/nbt/mca/Chunk.java +++ b/src/main/java/net/querz/nbt/mca/Chunk.java @@ -3,14 +3,8 @@ import net.querz.nbt.CompoundTag; import net.querz.nbt.ListTag; import net.querz.nbt.Tag; -import java.io.BufferedInputStream; -import java.io.BufferedOutputStream; -import java.io.ByteArrayOutputStream; -import java.io.DataInputStream; -import java.io.DataOutputStream; -import java.io.FileInputStream; -import java.io.IOException; -import java.io.RandomAccessFile; + +import java.io.*; public class Chunk { @@ -63,7 +57,7 @@ private void initReferences() { this.inhabitedTime = level.getLong("InhabitedTime"); this.lastUpdate = level.getLong("LastUpdate"); this.biomes = level.getIntArray("Biomes"); - this.heightMaps = level.getCompoundTag("HeightMaps"); + this.heightMaps = level.getCompoundTag("Heightmaps"); this.carvingMasks = level.getCompoundTag("CarvingMasks"); this.entities = level.containsKey("Entities") ? level.getListTag("Entities").asCompoundTagList() : null; this.tileEntities = level.containsKey("TileEntities") ? level.getListTag("TileEntities").asCompoundTagList() : null; @@ -104,7 +98,7 @@ public int serialize(RandomAccessFile raf, int xPos, int zPos) throws IOExceptio updateHandle(xPos, zPos).serialize(nbtOut, Tag.DEFAULT_MAX_DEPTH); } byte[] rawData = baos.toByteArray(); - raf.writeInt(rawData.length); + raf.writeInt(rawData.length + 1); raf.writeByte(CompressionType.ZLIB.getID()); raf.write(rawData); return rawData.length + 5; @@ -507,7 +501,7 @@ public CompoundTag updateHandle(int xPos, int zPos) { level.putLong("LastUpdate", lastUpdate); level.putLong("InhabitedTime", inhabitedTime); if (biomes != null && biomes.length == 256) level.putIntArray("Biomes", biomes); - if (heightMaps != null) level.put("HeightMaps", heightMaps); + if (heightMaps != null) level.put("Heightmaps", heightMaps); if (carvingMasks != null) level.put("CarvingMasks", carvingMasks); if (entities != null) level.put("Entities", entities); if (tileEntities != null) level.put("TileEntities", tileEntities); diff --git a/src/main/java/net/querz/nbt/mca/MCAFile.java b/src/main/java/net/querz/nbt/mca/MCAFile.java index 2ed23de4..8710756a 100644 --- a/src/main/java/net/querz/nbt/mca/MCAFile.java +++ b/src/main/java/net/querz/nbt/mca/MCAFile.java @@ -100,7 +100,8 @@ public int serialize(RandomAccessFile raf, boolean changeLastUpdate) throws IOEx chunksWritten++; - int sectors = (lastWritten >> 12) + 1; +// we shouldn't add 1 in case of lastWritten % 4096 == 0 (see docs) + int sectors = (lastWritten >> 12) + (lastWritten % 4096 == 0 ? 0 : 1); raf.seek(index * 4); raf.writeByte(globalOffset >>> 16); diff --git a/src/main/java/net/querz/nbt/mca/Section.java b/src/main/java/net/querz/nbt/mca/Section.java index 649a40d3..2858539e 100644 --- a/src/main/java/net/querz/nbt/mca/Section.java +++ b/src/main/java/net/querz/nbt/mca/Section.java @@ -1,7 +1,10 @@ package net.querz.nbt.mca; +import net.querz.nbt.ByteArrayTag; import net.querz.nbt.CompoundTag; import net.querz.nbt.ListTag; +import net.querz.nbt.LongArrayTag; + import java.util.ArrayList; import java.util.HashMap; import java.util.List; @@ -30,9 +33,15 @@ public Section(CompoundTag sectionRoot) { CompoundTag data = palette.get(i); putValueIndexedPalette(data, i); } - blockLight = sectionRoot.getByteArray("BlockLight"); - blockStates = sectionRoot.getLongArray("BlockStates"); - skyLight = sectionRoot.getByteArray("SkyLight"); + +// we need null in case if there's no such byteArrays. TODO: add getOrNull + ByteArrayTag blockLightTag = sectionRoot.getByteArrayTag("BlockLight"); + LongArrayTag blockStatesTag = sectionRoot.getLongArrayTag("BlockStates"); + ByteArrayTag skyLightTag = sectionRoot.getByteArrayTag("SkyLight"); + + this.blockLight = blockLightTag != null ? blockLightTag.getValue() : null; + this.blockStates = blockStatesTag != null ? blockStatesTag.getValue() : null; + this.skyLight = skyLightTag != null ? skyLightTag.getValue() : null; data = sectionRoot; } From a742008f41291c0e68930180dc09e7b28f7d57cf Mon Sep 17 00:00:00 2001 From: artfable Date: Sat, 5 Oct 2019 23:31:33 +0200 Subject: [PATCH 02/51] fix testcase --- src/test/java/net/querz/nbt/ListTagTest.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/test/java/net/querz/nbt/ListTagTest.java b/src/test/java/net/querz/nbt/ListTagTest.java index 243db1c1..7e2d02cc 100644 --- a/src/test/java/net/querz/nbt/ListTagTest.java +++ b/src/test/java/net/querz/nbt/ListTagTest.java @@ -114,10 +114,12 @@ public void testSerializeDeserialize() { public void testSerializeDeserializeEmptyList() { ListTag empty = new ListTag<>(IntTag.class); byte[] data = serialize(empty); - assertTrue(Arrays.equals(new byte[]{9, 0, 0, 3, 0, 0, 0, 0}, data)); +// empty list can't have type + assertTrue(Arrays.equals(new byte[]{9, 0, 0, 0, 0, 0, 0, 0}, data)); ListTag et = (ListTag) deserialize(data); assertNotNull(et); - assertThrowsRuntimeException(et::asByteTagList, ClassCastException.class); +// doesn't make sense as there's no type +// assertThrowsRuntimeException(et::asByteTagList, ClassCastException.class); } public void testCasting() { From 4252314cb304dc2226723d7b1c281bbf91d4f648 Mon Sep 17 00:00:00 2001 From: Querz Date: Tue, 5 Mar 2019 17:41:12 +0100 Subject: [PATCH 03/51] MSON deserializer prototype --- src/main/java/net/querz/nbt/ListTag.java | 5 +- .../java/net/querz/nbt/io/Deserializer.java | 8 + .../net/querz/nbt/io/MSONDeserializer.java | 176 ++++++++++++++++++ .../java/net/querz/nbt/io/MSONSerializer.java | 9 + .../java/net/querz/nbt/io/MaxDepthIO.java | 15 ++ .../java/net/querz/nbt/io/ParseException.java | 23 +++ .../java/net/querz/nbt/io/Serializer.java | 6 + .../java/net/querz/nbt/io/StringPointer.java | 114 ++++++++++++ .../querz/nbt/io/StringDeserializerTest.java | 12 ++ 9 files changed, 366 insertions(+), 2 deletions(-) create mode 100644 src/main/java/net/querz/nbt/io/Deserializer.java create mode 100644 src/main/java/net/querz/nbt/io/MSONDeserializer.java create mode 100644 src/main/java/net/querz/nbt/io/MSONSerializer.java create mode 100644 src/main/java/net/querz/nbt/io/MaxDepthIO.java create mode 100644 src/main/java/net/querz/nbt/io/ParseException.java create mode 100644 src/main/java/net/querz/nbt/io/Serializer.java create mode 100644 src/main/java/net/querz/nbt/io/StringPointer.java create mode 100644 src/test/java/net/querz/nbt/io/StringDeserializerTest.java diff --git a/src/main/java/net/querz/nbt/ListTag.java b/src/main/java/net/querz/nbt/ListTag.java index a0fc096d..f9a4ea7b 100644 --- a/src/main/java/net/querz/nbt/ListTag.java +++ b/src/main/java/net/querz/nbt/ListTag.java @@ -35,7 +35,7 @@ private ListTag() { * * @return A new non-type-safe ListTag */ - protected static ListTag createUnchecked() { + public static ListTag createUnchecked() { return new ListTag<>(); } @@ -339,8 +339,9 @@ public ListTag clone() { return copy; } + //TODO: make private @SuppressWarnings("unchecked") - private void addUnchecked(Tag tag) { + public void addUnchecked(Tag tag) { if (typeClass != null && typeClass != tag.getClass()) { throw new IllegalArgumentException(String.format( "cannot add %s to ListTag<%s>", diff --git a/src/main/java/net/querz/nbt/io/Deserializer.java b/src/main/java/net/querz/nbt/io/Deserializer.java new file mode 100644 index 00000000..0d664a1d --- /dev/null +++ b/src/main/java/net/querz/nbt/io/Deserializer.java @@ -0,0 +1,8 @@ +package net.querz.nbt.io; + +import net.querz.nbt.Tag; + +public interface Deserializer extends MaxDepthIO { + + Tag read(int maxDepth); +} diff --git a/src/main/java/net/querz/nbt/io/MSONDeserializer.java b/src/main/java/net/querz/nbt/io/MSONDeserializer.java new file mode 100644 index 00000000..1550f4f9 --- /dev/null +++ b/src/main/java/net/querz/nbt/io/MSONDeserializer.java @@ -0,0 +1,176 @@ +package net.querz.nbt.io; + +import net.querz.nbt.ArrayTag; +import net.querz.nbt.ByteArrayTag; +import net.querz.nbt.ByteTag; +import net.querz.nbt.CompoundTag; +import net.querz.nbt.DoubleTag; +import net.querz.nbt.FloatTag; +import net.querz.nbt.IntArrayTag; +import net.querz.nbt.IntTag; +import net.querz.nbt.ListTag; +import net.querz.nbt.LongArrayTag; +import net.querz.nbt.LongTag; +import net.querz.nbt.ShortTag; +import net.querz.nbt.StringTag; +import net.querz.nbt.Tag; +import java.util.ArrayList; +import java.util.List; +import java.util.function.Function; +import java.util.regex.Pattern; + +public final class MSONDeserializer implements Deserializer { + + private static final Pattern + FLOAT_PATTERN = Pattern.compile("^[-+]?(?:\\d+\\.?|\\d*\\.\\d+)(?:e[-+]?\\d+)?f$", Pattern.CASE_INSENSITIVE), + DOUBLE_PATTERN = Pattern.compile("^[-+]?(?:\\d+\\.?|\\d*\\.\\d+)(?:e[-+]?\\d+)?d$", Pattern.CASE_INSENSITIVE), + DOUBLE_NO_SUFFIX_PATTERN = Pattern.compile("^[-+]?(?:\\d+\\.|\\d*\\.\\d+)(?:e[-+]?\\d+)?$", Pattern.CASE_INSENSITIVE), + BYTE_PATTERN = Pattern.compile("^[-+]?\\d+b$", Pattern.CASE_INSENSITIVE), + SHORT_PATTERN = Pattern.compile("^[-+]?\\d+s$", Pattern.CASE_INSENSITIVE), + INT_PATTERN = Pattern.compile("^[-+]?\\d+$", Pattern.CASE_INSENSITIVE), + LONG_PATTERN = Pattern.compile("^[-+]?\\d+l$", Pattern.CASE_INSENSITIVE); + + private StringPointer stream; + + public MSONDeserializer(String string) { + this.stream = new StringPointer(string); + } + + @Override + public Tag read(int maxDepth) { + stream.skipWhitespace(); + switch (stream.currentChar()) { + case '{': + return parseCompoundTag(maxDepth); + case '[': + if (stream.hasCharsLeft(2) && stream.lookAhead(1) != '"' && stream.lookAhead(2) == ';') { + return parseNumArray(); + } + return parseListTag(maxDepth); + } + return parseStringOrLiteral(); + } + + private Tag parseStringOrLiteral() { + stream.skipWhitespace(); + if (stream.currentChar() == '"') { + return new StringTag(stream.parseQuotedString()); + } + String s = stream.parseSimpleString(); + if (s.isEmpty()) { + throw new ParseException("expected non empty value"); + } + try { + if (FLOAT_PATTERN.matcher(s).matches()) { + return new FloatTag(Float.parseFloat(s.substring(0, s.length() - 1))); + } else if (BYTE_PATTERN.matcher(s).matches()) { + return new ByteTag(Byte.parseByte(s.substring(0, s.length() - 1))); + } else if (SHORT_PATTERN.matcher(s).matches()) { + return new ShortTag(Short.parseShort(s.substring(0, s.length() - 1))); + } else if (LONG_PATTERN.matcher(s).matches()) { + return new LongTag(Long.parseLong(s.substring(0, s.length() - 1))); + } else if (INT_PATTERN.matcher(s).matches()) { + return new IntTag(Integer.parseInt(s)); + } else if (DOUBLE_PATTERN.matcher(s).matches()) { + return new DoubleTag(Double.parseDouble(s.substring(0, s.length() - 1))); + } else if (DOUBLE_NO_SUFFIX_PATTERN.matcher(s).matches()) { + return new DoubleTag(Double.parseDouble(s)); + } else if ("true".equalsIgnoreCase(s)) { + return new ByteTag(true); + } else if ("false".equalsIgnoreCase(s)) { + return new ByteTag(false); + } + } catch (NumberFormatException ex) { + return new StringTag(s); + } + return new StringTag(s); + } + + private CompoundTag parseCompoundTag(int maxDepth) { + stream.expectChar('{'); + + CompoundTag compoundTag = new CompoundTag(); + + stream.skipWhitespace(); + while (stream.hasNext() && stream.currentChar() != '}') { + stream.skipWhitespace(); + String key = stream.currentChar() == '"' ? stream.parseQuotedString() : stream.parseSimpleString(); + if (key.isEmpty()) { + throw new ParseException("empty keys are not allowed"); + } + stream.expectChar(':'); + + compoundTag.put(key, read(decrementMaxDepth(maxDepth))); + + if (!stream.nextArrayElement()) { + break; + } + } + stream.expectChar('}'); + return compoundTag; + } + + private ListTag parseListTag(int maxDepth) { + stream.expectChar('['); + stream.skipWhitespace(); + ListTag list = ListTag.createUnchecked(); + while (stream.currentChar() != ']') { + Tag element = read(decrementMaxDepth(maxDepth)); + list.addUnchecked(element); + if (!stream.nextArrayElement()) { + break; + } + } + stream.expectChar(']'); + return list; + } + + private ArrayTag parseNumArray() { + stream.expectChar('['); + char arrayType = stream.next(); + stream.expectChar(';'); + stream.skipWhitespace(); + switch (arrayType) { + case 'B': + return parseNumericArrayTag( + a -> new ByteArrayTag((byte[]) a), + Byte::parseByte, + li -> { + byte[] bytes = new byte[li.size()]; + for (int i = 0; i < li.size(); i++) { + bytes[i] = li.get(i).byteValue(); + } + return bytes; + }); + case 'I': + return parseNumericArrayTag( + a -> new IntArrayTag((int[]) a), + Integer::parseInt, + li -> li.stream().mapToInt(i -> (int) i).toArray()); + case 'L': + return parseNumericArrayTag( + a -> new LongArrayTag((long[]) a), + Long::parseLong, + li -> li.stream().mapToLong(l -> (long) l).toArray()); + } + throw new ParseException("invalid array type '" + arrayType + "'"); + } + + private ArrayTag parseNumericArrayTag( + Function> constructor, + Function numberParser, + Function, Object> arrayParser) { + + List numberList = new ArrayList<>(); + while (stream.currentChar() != ']') { + String s = stream.parseSimpleString(); + stream.skipWhitespace(); + numberList.add(numberParser.apply(s)); + if (!stream.nextArrayElement()) { + break; + } + } + stream.expectChar(']'); + return constructor.apply(arrayParser.apply(numberList)); + } +} diff --git a/src/main/java/net/querz/nbt/io/MSONSerializer.java b/src/main/java/net/querz/nbt/io/MSONSerializer.java new file mode 100644 index 00000000..814dbf2b --- /dev/null +++ b/src/main/java/net/querz/nbt/io/MSONSerializer.java @@ -0,0 +1,9 @@ +package net.querz.nbt.io; + +public class MSONSerializer implements Serializer { + + @Override + public String write() { + return null; + } +} diff --git a/src/main/java/net/querz/nbt/io/MaxDepthIO.java b/src/main/java/net/querz/nbt/io/MaxDepthIO.java new file mode 100644 index 00000000..f00efd25 --- /dev/null +++ b/src/main/java/net/querz/nbt/io/MaxDepthIO.java @@ -0,0 +1,15 @@ +package net.querz.nbt.io; + +import net.querz.nbt.MaxDepthReachedException; + +public interface MaxDepthIO { + + default 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 --maxDepth; + } +} diff --git a/src/main/java/net/querz/nbt/io/ParseException.java b/src/main/java/net/querz/nbt/io/ParseException.java new file mode 100644 index 00000000..e2bd61b1 --- /dev/null +++ b/src/main/java/net/querz/nbt/io/ParseException.java @@ -0,0 +1,23 @@ +package net.querz.nbt.io; + +public class ParseException extends RuntimeException { + + public ParseException(String msg) { + super(msg); + } + + public ParseException(String msg, String value, int index) { + super(msg + " at: " + formatError(value, index)); + } + + private static String formatError(String value, int index) { + StringBuilder builder = new StringBuilder(); + int i = Math.min(value.length(), index); + if (i > 35) { + builder.append("..."); + } + builder.append(value, Math.max(0, i - 35), i); + builder.append("<--[HERE]"); + return builder.toString(); + } +} diff --git a/src/main/java/net/querz/nbt/io/Serializer.java b/src/main/java/net/querz/nbt/io/Serializer.java new file mode 100644 index 00000000..84d00121 --- /dev/null +++ b/src/main/java/net/querz/nbt/io/Serializer.java @@ -0,0 +1,6 @@ +package net.querz.nbt.io; + +public interface Serializer { + + String write(); +} diff --git a/src/main/java/net/querz/nbt/io/StringPointer.java b/src/main/java/net/querz/nbt/io/StringPointer.java new file mode 100644 index 00000000..82fb6734 --- /dev/null +++ b/src/main/java/net/querz/nbt/io/StringPointer.java @@ -0,0 +1,114 @@ +package net.querz.nbt.io; + +public class StringPointer { + + private String value; + private int index; + + public StringPointer(String value) { + this.value = value; + } + + public String parseSimpleString() { + int oldIndex = index; + while (hasNext() && isSimpleChar(currentChar())) { + index++; + } + return value.substring(oldIndex, index); + } + + public String parseQuotedString() { + int oldIndex = ++index; //ignore beginning quotes + StringBuilder sb = null; + boolean escape = false; + while (hasNext()) { + char c = next(); + if (escape) { + if (c != '\\' && c != '"') { + throw parseException("invalid escape of '" + c + "'"); + } + escape = false; + } else { + if (c == '\\') { //escape + escape = true; + if (sb != null) { + continue; + } + sb = new StringBuilder(value.substring(oldIndex, index - 1)); + continue; + } + if (c == '"') { + return sb == null ? value.substring(oldIndex, index - 1) : sb.toString(); + } + } + if (sb != null) { + sb.append(c); + } + } + throw parseException("missing end quote"); + } + + public boolean nextArrayElement() { + skipWhitespace(); + if (hasNext() && currentChar() == ',') { + index++; + skipWhitespace(); + return true; + } + return false; + } + + public void expectChar(char c) { + skipWhitespace(); + boolean hasNext = hasNext(); + if (hasNext && currentChar() == c) { + index++; + return; + } + throw parseException("expected '" + c + "' but got " + (hasNext ? "'" + currentChar() + "'" : "EOF")); + } + + public void skipWhitespace() { + while (hasNext() && Character.isWhitespace(currentChar())) { + index++; + } + } + + public boolean hasNext() { + return index < value.length(); + } + + public boolean hasCharsLeft(int num) { + return this.index + num < value.length(); + } + + public char currentChar() { + return value.charAt(index); + } + + public char next() { + return value.charAt(index++); + } + + public void skip(int offset) { + index += offset; + } + + public char lookAhead(int offset) { + return value.charAt(index + offset); + } + + private static boolean isSimpleChar(char c) { + return c >= 'a' && c <= 'z' + || c >= 'A' && c <= 'Z' + || c >= '0' && c <= '9' + || c == '-' + || c == '+' + || c == '.' + || c == '_'; + } + + private ParseException parseException(String msg) { + return new ParseException(msg, value, index); + } +} diff --git a/src/test/java/net/querz/nbt/io/StringDeserializerTest.java b/src/test/java/net/querz/nbt/io/StringDeserializerTest.java new file mode 100644 index 00000000..25552da2 --- /dev/null +++ b/src/test/java/net/querz/nbt/io/StringDeserializerTest.java @@ -0,0 +1,12 @@ +package net.querz.nbt.io; + +import net.querz.nbt.NBTTestCase; + +public class StringDeserializerTest extends NBTTestCase { + + public void test() { + String s = "{blah:1.3f,\"foo\":\"bar\",test:\" moo\",bytes:[B;1,2,3,4]}"; + MSONDeserializer m = new MSONDeserializer(s); + System.out.println(m.read(1)); + } +} From 24ebe04a4a71181758fccacc72ab7bbf87dbfb6b Mon Sep 17 00:00:00 2001 From: Querz Date: Fri, 15 Mar 2019 16:55:40 +0100 Subject: [PATCH 04/51] mson / nbt serialization / deserialization with working but incomplete unit tests --- src/main/java/net/querz/io/Deserializer.java | 42 ++++ .../net/querz/io/ExceptionBiFunction.java | 7 + .../net/querz/io/ExceptionTriConsumer.java | 7 + .../net/querz/{nbt => }/io/MaxDepthIO.java | 4 +- .../{nbt => io}/MaxDepthReachedException.java | 2 +- src/main/java/net/querz/io/Serializer.java | 26 ++ .../java/net/querz/io/StringDeserializer.java | 37 +++ .../java/net/querz/io/StringSerializer.java | 35 +++ src/main/java/net/querz/nbt/ByteArrayTag.java | 21 +- src/main/java/net/querz/nbt/ByteTag.java | 25 +- src/main/java/net/querz/nbt/CompoundTag.java | 47 +--- src/main/java/net/querz/nbt/DoubleTag.java | 23 +- src/main/java/net/querz/nbt/EndTag.java | 24 +- src/main/java/net/querz/nbt/FloatTag.java | 23 +- src/main/java/net/querz/nbt/IntArrayTag.java | 25 +- src/main/java/net/querz/nbt/IntTag.java | 23 +- src/main/java/net/querz/nbt/ListTag.java | 72 ++---- src/main/java/net/querz/nbt/LongArrayTag.java | 25 +- src/main/java/net/querz/nbt/LongTag.java | 23 +- src/main/java/net/querz/nbt/NBTUtil.java | 172 -------------- src/main/java/net/querz/nbt/ShortTag.java | 23 +- src/main/java/net/querz/nbt/StringTag.java | 25 +- src/main/java/net/querz/nbt/Tag.java | 125 +--------- src/main/java/net/querz/nbt/TagFactory.java | 101 -------- .../java/net/querz/nbt/custom/CharTag.java | 28 +-- .../java/net/querz/nbt/custom/ObjectTag.java | 30 +-- .../net/querz/nbt/custom/ShortArrayTag.java | 34 ++- .../java/net/querz/nbt/custom/StructTag.java | 49 ++-- .../java/net/querz/nbt/io/Deserializer.java | 8 - .../net/querz/nbt/io/MSONDeserializer.java | 180 ++------------ .../java/net/querz/nbt/io/MSONParser.java | 209 ++++++++++++++++ .../java/net/querz/nbt/io/MSONSerializer.java | 25 +- .../java/net/querz/nbt/io/MSONWriter.java | 130 ++++++++++ .../net/querz/nbt/io/NBTDeserializer.java | 31 +++ .../java/net/querz/nbt/io/NBTInputStream.java | 156 ++++++++++++ .../net/querz/nbt/io/NBTOutputStream.java | 164 +++++++++++++ .../java/net/querz/nbt/io/NBTSerializer.java | 31 +++ src/main/java/net/querz/nbt/io/NBTUtil.java | 119 ++++++++++ src/main/java/net/querz/nbt/io/NamedTag.java | 30 +++ .../java/net/querz/nbt/io/ParseException.java | 4 +- .../java/net/querz/nbt/io/Serializer.java | 6 - .../java/net/querz/nbt/io/StringPointer.java | 8 +- src/main/java/net/querz/nbt/mca/Chunk.java | 223 ++---------------- .../java/net/querz/nbt/ByteArrayTagTest.java | 19 +- src/test/java/net/querz/nbt/ByteTagTest.java | 2 +- .../java/net/querz/nbt/CompoundTagTest.java | 13 +- .../java/net/querz/nbt/DoubleTagTest.java | 2 +- src/test/java/net/querz/nbt/EndTagTest.java | 10 +- src/test/java/net/querz/nbt/FloatTagTest.java | 2 +- .../java/net/querz/nbt/IntArrayTagTest.java | 2 +- src/test/java/net/querz/nbt/IntTagTest.java | 2 +- src/test/java/net/querz/nbt/ListTagTest.java | 39 +-- .../java/net/querz/nbt/LongArrayTagTest.java | 2 +- src/test/java/net/querz/nbt/LongTagTest.java | 2 +- src/test/java/net/querz/nbt/NBTTestCase.java | 23 +- src/test/java/net/querz/nbt/ShortTagTest.java | 2 +- .../java/net/querz/nbt/StringTagTest.java | 10 +- .../java/net/querz/nbt/TagFactoryTest.java | 46 ---- src/test/java/net/querz/nbt/TagTest.java | 75 ------ .../net/querz/nbt/custom/CharTagTest.java | 2 +- .../net/querz/nbt/custom/ObjectTagTest.java | 14 +- .../querz/nbt/custom/ShortArrayTagTest.java | 2 +- .../net/querz/nbt/custom/StructTagTest.java | 3 +- .../java/net/querz/nbt/io/MSONWriterTest.java | 15 ++ .../querz/nbt/io/StringDeserializerTest.java | 12 - .../java/net/querz/nbt/mca/MCAFileTest.java | 2 +- 66 files changed, 1333 insertions(+), 1370 deletions(-) create mode 100644 src/main/java/net/querz/io/Deserializer.java create mode 100644 src/main/java/net/querz/io/ExceptionBiFunction.java create mode 100644 src/main/java/net/querz/io/ExceptionTriConsumer.java rename src/main/java/net/querz/{nbt => }/io/MaxDepthIO.java (81%) rename src/main/java/net/querz/{nbt => io}/MaxDepthReachedException.java (91%) create mode 100644 src/main/java/net/querz/io/Serializer.java create mode 100644 src/main/java/net/querz/io/StringDeserializer.java create mode 100644 src/main/java/net/querz/io/StringSerializer.java delete mode 100644 src/main/java/net/querz/nbt/NBTUtil.java delete mode 100644 src/main/java/net/querz/nbt/TagFactory.java delete mode 100644 src/main/java/net/querz/nbt/io/Deserializer.java create mode 100644 src/main/java/net/querz/nbt/io/MSONParser.java create mode 100644 src/main/java/net/querz/nbt/io/MSONWriter.java create mode 100644 src/main/java/net/querz/nbt/io/NBTDeserializer.java create mode 100644 src/main/java/net/querz/nbt/io/NBTInputStream.java create mode 100644 src/main/java/net/querz/nbt/io/NBTOutputStream.java create mode 100644 src/main/java/net/querz/nbt/io/NBTSerializer.java create mode 100644 src/main/java/net/querz/nbt/io/NBTUtil.java create mode 100644 src/main/java/net/querz/nbt/io/NamedTag.java delete mode 100644 src/main/java/net/querz/nbt/io/Serializer.java delete mode 100644 src/test/java/net/querz/nbt/TagFactoryTest.java delete mode 100644 src/test/java/net/querz/nbt/TagTest.java create mode 100644 src/test/java/net/querz/nbt/io/MSONWriterTest.java delete mode 100644 src/test/java/net/querz/nbt/io/StringDeserializerTest.java diff --git a/src/main/java/net/querz/io/Deserializer.java b/src/main/java/net/querz/io/Deserializer.java new file mode 100644 index 00000000..1849fe9d --- /dev/null +++ b/src/main/java/net/querz/io/Deserializer.java @@ -0,0 +1,42 @@ +package net.querz.io; + +import java.io.BufferedInputStream; +import java.io.ByteArrayInputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; + +public interface Deserializer { + + T fromStream(InputStream stream) throws IOException; + + default T fromFile(File file) throws IOException { + try (BufferedInputStream bis = new BufferedInputStream(new FileInputStream(file))) { + return fromStream(bis); + } + } + + default T fromBytes(byte[] data) throws IOException { + ByteArrayInputStream stream = new ByteArrayInputStream(data); + return fromStream(stream); + } + + default T fromResource(Class clazz, String path) throws IOException { + try (InputStream stream = clazz.getClassLoader().getResourceAsStream(path)) { + if (stream == null) { + throw new IOException("resource \"" + path + "\" not found"); + } + return fromStream(stream); + } + } + + default T fromURL(URL url) throws IOException { + try (InputStream stream = url.openStream()) { + return fromStream(stream); + } + } + + +} diff --git a/src/main/java/net/querz/io/ExceptionBiFunction.java b/src/main/java/net/querz/io/ExceptionBiFunction.java new file mode 100644 index 00000000..c34dba72 --- /dev/null +++ b/src/main/java/net/querz/io/ExceptionBiFunction.java @@ -0,0 +1,7 @@ +package net.querz.io; + +@FunctionalInterface +public interface ExceptionBiFunction { + + R accept(T t, U u) throws E; +} diff --git a/src/main/java/net/querz/io/ExceptionTriConsumer.java b/src/main/java/net/querz/io/ExceptionTriConsumer.java new file mode 100644 index 00000000..d49ccc90 --- /dev/null +++ b/src/main/java/net/querz/io/ExceptionTriConsumer.java @@ -0,0 +1,7 @@ +package net.querz.io; + +@FunctionalInterface +public interface ExceptionTriConsumer { + + void accept(T t, U u, V v) throws E; +} diff --git a/src/main/java/net/querz/nbt/io/MaxDepthIO.java b/src/main/java/net/querz/io/MaxDepthIO.java similarity index 81% rename from src/main/java/net/querz/nbt/io/MaxDepthIO.java rename to src/main/java/net/querz/io/MaxDepthIO.java index f00efd25..0a5fc3e7 100644 --- a/src/main/java/net/querz/nbt/io/MaxDepthIO.java +++ b/src/main/java/net/querz/io/MaxDepthIO.java @@ -1,6 +1,4 @@ -package net.querz.nbt.io; - -import net.querz.nbt.MaxDepthReachedException; +package net.querz.io; public interface MaxDepthIO { diff --git a/src/main/java/net/querz/nbt/MaxDepthReachedException.java b/src/main/java/net/querz/io/MaxDepthReachedException.java similarity index 91% rename from src/main/java/net/querz/nbt/MaxDepthReachedException.java rename to src/main/java/net/querz/io/MaxDepthReachedException.java index 3b3dfb71..3824be9e 100644 --- a/src/main/java/net/querz/nbt/MaxDepthReachedException.java +++ b/src/main/java/net/querz/io/MaxDepthReachedException.java @@ -1,4 +1,4 @@ -package net.querz.nbt; +package net.querz.io; /** * Exception indicating that the maximum (de-)serialization depth has been reached. diff --git a/src/main/java/net/querz/io/Serializer.java b/src/main/java/net/querz/io/Serializer.java new file mode 100644 index 00000000..a6c9377a --- /dev/null +++ b/src/main/java/net/querz/io/Serializer.java @@ -0,0 +1,26 @@ +package net.querz.io; + +import java.io.BufferedOutputStream; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStream; + +public interface Serializer { + + void toStream(T object, OutputStream out) throws IOException; + + default void toFile(T object, File file) throws IOException { + try (BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(file))) { + toStream(object, bos); + } + } + + default byte[] toBytes(T object) throws IOException { + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + toStream(object, bos); + bos.close(); + return bos.toByteArray(); + } +} diff --git a/src/main/java/net/querz/io/StringDeserializer.java b/src/main/java/net/querz/io/StringDeserializer.java new file mode 100644 index 00000000..2160e2a8 --- /dev/null +++ b/src/main/java/net/querz/io/StringDeserializer.java @@ -0,0 +1,37 @@ +package net.querz.io; + +import java.io.File; +import java.io.FileReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.Reader; +import java.io.StringReader; + +public interface StringDeserializer extends Deserializer { + + T fromReader(Reader reader) throws IOException; + + default T fromString(String s) throws IOException { + return fromReader(new StringReader(s)); + } + + @Override + default T fromStream(InputStream stream) throws IOException { + try (Reader reader = new InputStreamReader(stream)) { + return fromReader(reader); + } + } + + @Override + default T fromFile(File file) throws IOException { + try (Reader reader = new FileReader(file)) { + return fromReader(reader); + } + } + + @Override + default T fromBytes(byte[] data) throws IOException { + return fromReader(new StringReader(new String(data))); + } +} diff --git a/src/main/java/net/querz/io/StringSerializer.java b/src/main/java/net/querz/io/StringSerializer.java new file mode 100644 index 00000000..c4da8104 --- /dev/null +++ b/src/main/java/net/querz/io/StringSerializer.java @@ -0,0 +1,35 @@ +package net.querz.io; + +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.io.OutputStream; +import java.io.OutputStreamWriter; +import java.io.StringWriter; +import java.io.Writer; + +public interface StringSerializer extends Serializer { + + void toWriter(T object, Writer writer) throws IOException; + + default String toString(T object) throws IOException { + Writer writer = new StringWriter(); + toWriter(object, writer); + writer.flush(); + return writer.toString(); + } + + @Override + default void toStream(T object, OutputStream stream) throws IOException { + Writer writer = new OutputStreamWriter(stream); + toWriter(object, writer); + writer.flush(); + } + + @Override + default void toFile(T object, File file) throws IOException { + try (Writer writer = new FileWriter(file)) { + toWriter(object, writer); + } + } +} diff --git a/src/main/java/net/querz/nbt/ByteArrayTag.java b/src/main/java/net/querz/nbt/ByteArrayTag.java index f73aae56..cccb8e29 100644 --- a/src/main/java/net/querz/nbt/ByteArrayTag.java +++ b/src/main/java/net/querz/nbt/ByteArrayTag.java @@ -1,12 +1,10 @@ package net.querz.nbt; -import java.io.DataInputStream; -import java.io.DataOutputStream; -import java.io.IOException; import java.util.Arrays; public class ByteArrayTag extends ArrayTag implements Comparable { + public static final byte ID = 7; public static final byte[] ZERO_VALUE = new byte[0]; public ByteArrayTag() { @@ -18,21 +16,8 @@ public ByteArrayTag(byte[] value) { } @Override - public void serializeValue(DataOutputStream dos, int maxDepth) throws IOException { - dos.writeInt(length()); - dos.write(getValue()); - } - - @Override - 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 maxDepth) { - return arrayToString("B", "b"); + public byte getID() { + return ID; } @Override diff --git a/src/main/java/net/querz/nbt/ByteTag.java b/src/main/java/net/querz/nbt/ByteTag.java index 5d12e558..859086ca 100644 --- a/src/main/java/net/querz/nbt/ByteTag.java +++ b/src/main/java/net/querz/nbt/ByteTag.java @@ -1,11 +1,8 @@ package net.querz.nbt; -import java.io.DataInputStream; -import java.io.DataOutputStream; -import java.io.IOException; - public class ByteTag extends NumberTag implements Comparable { + public static final byte ID = 1; public static final byte ZERO_VALUE = 0; public ByteTag() { @@ -20,6 +17,11 @@ public ByteTag(boolean value) { super((byte) (value ? 1 : 0)); } + @Override + public byte getID() { + return ID; + } + public boolean asBoolean() { return getValue() > 0; } @@ -28,21 +30,6 @@ public void setValue(byte value) { super.setValue(value); } - @Override - public void serializeValue(DataOutputStream dos, int maxDepth) throws IOException { - dos.writeByte(getValue()); - } - - @Override - public void deserializeValue(DataInputStream dis, int maxDepth) throws IOException { - setValue(dis.readByte()); - } - - @Override - public String valueToTagString(int maxDepth) { - return getValue() + "b"; - } - @Override public boolean equals(Object other) { return super.equals(other) && asByte() == ((ByteTag) other).asByte(); diff --git a/src/main/java/net/querz/nbt/CompoundTag.java b/src/main/java/net/querz/nbt/CompoundTag.java index adbf18b1..4ca27707 100644 --- a/src/main/java/net/querz/nbt/CompoundTag.java +++ b/src/main/java/net/querz/nbt/CompoundTag.java @@ -1,8 +1,7 @@ package net.querz.nbt; -import java.io.DataInputStream; -import java.io.DataOutputStream; -import java.io.IOException; +import net.querz.io.MaxDepthIO; + import java.util.Collection; import java.util.HashMap; import java.util.Iterator; @@ -11,12 +10,19 @@ import java.util.Set; import java.util.function.BiConsumer; -public class CompoundTag extends Tag>> implements Iterable>>, Comparable { +public class CompoundTag extends Tag>> implements Iterable>>, Comparable, MaxDepthIO { + + public static final byte ID = 10; public CompoundTag() { super(createEmptyValue()); } + @Override + public byte getID() { + return ID; + } + private static Map> createEmptyValue() { return new HashMap<>(8); } @@ -225,25 +231,6 @@ public Tag putLongArray(String key, long[] value) { return put(key, new LongArrayTag(value)); } - @Override - public void serializeValue(DataOutputStream dos, int maxDepth) throws IOException { - for (Map.Entry> e : getValue().entrySet()) { - e.getValue().serialize(dos, e.getKey(), decrementMaxDepth(maxDepth)); - } - EndTag.INSTANCE.serialize(dos, maxDepth); - } - - @Override - 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, decrementMaxDepth(maxDepth)); - put(name, tag); - } - } - @Override public String valueToString(int maxDepth) { StringBuilder sb = new StringBuilder("{"); @@ -258,20 +245,6 @@ public String valueToString(int maxDepth) { return sb.toString(); } - @Override - 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(decrementMaxDepth(maxDepth))); - first = false; - } - sb.append("}"); - return sb.toString(); - } - @Override public boolean equals(Object other) { if (this == other) { diff --git a/src/main/java/net/querz/nbt/DoubleTag.java b/src/main/java/net/querz/nbt/DoubleTag.java index 5057941a..d2168755 100644 --- a/src/main/java/net/querz/nbt/DoubleTag.java +++ b/src/main/java/net/querz/nbt/DoubleTag.java @@ -1,11 +1,8 @@ package net.querz.nbt; -import java.io.DataInputStream; -import java.io.DataOutputStream; -import java.io.IOException; - public class DoubleTag extends NumberTag implements Comparable { + public static final byte ID = 6; public static final double ZERO_VALUE = 0.0D; public DoubleTag() { @@ -16,23 +13,13 @@ public DoubleTag(double value) { super(value); } - public void setValue(double value) { - super.setValue(value); - } - @Override - public void serializeValue(DataOutputStream dos, int maxDepth) throws IOException { - dos.writeDouble(getValue()); + public byte getID() { + return ID; } - @Override - public void deserializeValue(DataInputStream dis, int maxDepth) throws IOException { - setValue(dis.readDouble()); - } - - @Override - public String valueToTagString(int maxDepth) { - return getValue() + "d"; + public void setValue(double value) { + super.setValue(value); } @Override diff --git a/src/main/java/net/querz/nbt/EndTag.java b/src/main/java/net/querz/nbt/EndTag.java index b07a8af4..ac6fdabc 100644 --- a/src/main/java/net/querz/nbt/EndTag.java +++ b/src/main/java/net/querz/nbt/EndTag.java @@ -1,29 +1,22 @@ package net.querz.nbt; -import java.io.DataInputStream; -import java.io.DataOutputStream; - public final class EndTag extends Tag { - static final EndTag INSTANCE = new EndTag(); + public static final byte ID = 0; + public static final EndTag INSTANCE = new EndTag(); private EndTag() { super(null); } @Override - protected Void checkValue(Void value) { - return value; - } - - @Override - public void serializeValue(DataOutputStream dos, int maxDepth) { - //nothing to do + public byte getID() { + return ID; } @Override - public void deserializeValue(DataInputStream dis, int maxDepth) { - //nothing to do + protected Void checkValue(Void value) { + return value; } @Override @@ -31,11 +24,6 @@ public String valueToString(int maxDepth) { return "\"end\""; } - @Override - public String valueToTagString(int maxDepth) { - throw new UnsupportedOperationException("EndTag cannot be turned into a String"); - } - @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 ff248e3f..c837d1a0 100644 --- a/src/main/java/net/querz/nbt/FloatTag.java +++ b/src/main/java/net/querz/nbt/FloatTag.java @@ -1,11 +1,8 @@ package net.querz.nbt; -import java.io.DataInputStream; -import java.io.DataOutputStream; -import java.io.IOException; - public class FloatTag extends NumberTag implements Comparable { + public static final byte ID = 5; public static final float ZERO_VALUE = 0.0F; public FloatTag() { @@ -16,23 +13,13 @@ public FloatTag(float value) { super(value); } - public void setValue(float value) { - super.setValue(value); - } - @Override - public void serializeValue(DataOutputStream dos, int maxDepth) throws IOException { - dos.writeFloat(getValue()); + public byte getID() { + return ID; } - @Override - public void deserializeValue(DataInputStream dis, int maxDepth) throws IOException { - setValue(dis.readFloat()); - } - - @Override - public String valueToTagString(int maxDepth) { - return getValue() + "f"; + public void setValue(float value) { + super.setValue(value); } @Override diff --git a/src/main/java/net/querz/nbt/IntArrayTag.java b/src/main/java/net/querz/nbt/IntArrayTag.java index 694c3192..53371e2b 100644 --- a/src/main/java/net/querz/nbt/IntArrayTag.java +++ b/src/main/java/net/querz/nbt/IntArrayTag.java @@ -1,12 +1,10 @@ package net.querz.nbt; -import java.io.DataInputStream; -import java.io.DataOutputStream; -import java.io.IOException; import java.util.Arrays; public class IntArrayTag extends ArrayTag implements Comparable { + public static final byte ID = 11; public static final int[] ZERO_VALUE = new int[0]; public IntArrayTag() { @@ -18,25 +16,8 @@ public IntArrayTag(int[] value) { } @Override - public void serializeValue(DataOutputStream dos, int maxDepth) throws IOException { - dos.writeInt(length()); - for (int i : getValue()) { - dos.writeInt(i); - } - } - - @Override - public void deserializeValue(DataInputStream dis, int maxDepth) throws IOException { - int length = dis.readInt(); - setValue(new int[length]); - for (int i = 0; i < length; i++) { - getValue()[i] = dis.readInt(); - } - } - - @Override - public String valueToTagString(int maxDepth) { - return arrayToString("I", ""); + public byte getID() { + return ID; } @Override diff --git a/src/main/java/net/querz/nbt/IntTag.java b/src/main/java/net/querz/nbt/IntTag.java index dcd6e8e3..a11d71f5 100644 --- a/src/main/java/net/querz/nbt/IntTag.java +++ b/src/main/java/net/querz/nbt/IntTag.java @@ -1,11 +1,8 @@ package net.querz.nbt; -import java.io.DataInputStream; -import java.io.DataOutputStream; -import java.io.IOException; - public class IntTag extends NumberTag implements Comparable { + public static final byte ID = 3; public static final int ZERO_VALUE = 0; public IntTag() { @@ -16,23 +13,13 @@ public IntTag(int value) { super(value); } - public void setValue(int value) { - super.setValue(value); - } - @Override - public void serializeValue(DataOutputStream dos, int maxDepth) throws IOException { - dos.writeInt(getValue()); + public byte getID() { + return ID; } - @Override - public void deserializeValue(DataInputStream dis, int maxDepth) throws IOException { - setValue(dis.readInt()); - } - - @Override - public String valueToTagString(int maxDepth) { - return getValue() + ""; + public void setValue(int value) { + super.setValue(value); } @Override diff --git a/src/main/java/net/querz/nbt/ListTag.java b/src/main/java/net/querz/nbt/ListTag.java index f9a4ea7b..60170902 100644 --- a/src/main/java/net/querz/nbt/ListTag.java +++ b/src/main/java/net/querz/nbt/ListTag.java @@ -1,8 +1,7 @@ package net.querz.nbt; -import java.io.DataInputStream; -import java.io.DataOutputStream; -import java.io.IOException; +import net.querz.io.MaxDepthIO; + import java.util.ArrayList; import java.util.Collection; import java.util.Comparator; @@ -13,18 +12,25 @@ /** * ListTag represents a typed List in the nbt structure. - * An empty {@link ListTag} created using {@link ListTag#createUnchecked()} will be of unknown type + * An empty {@link ListTag} created using {@link ListTag#createUnchecked(Class)} will be of unknown type * and returns an {@link EndTag}{@code .class} in {@link ListTag#getTypeClass()}. * 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, Comparable> { +public class ListTag> extends Tag> implements Iterable, Comparable>, MaxDepthIO { + + public static final byte ID = 9; private Class typeClass = null; private ListTag() { super(createEmptyValue(3)); } + + @Override + public byte getID() { + return ID; + } /** *

Creates a non-type-safe ListTag. Its element type will be set after the first @@ -35,8 +41,10 @@ private ListTag() { * * @return A new non-type-safe ListTag */ - public static ListTag createUnchecked() { - return new ListTag<>(); + public static ListTag createUnchecked(Class typeClass) { + ListTag list = new ListTag<>(); + list.typeClass = typeClass; + return list; } /** @@ -115,16 +123,10 @@ public void add(T t) { public void add(int index, T t) { Objects.requireNonNull(t); + getValue().add(index, t); if (typeClass == null || typeClass == EndTag.class) { typeClass = t.getClass(); - } else if (typeClass != t.getClass()) { - throw new ClassCastException( - String.format("cannot add %s to ListTag<%s>", - t.getClass().getSimpleName(), - typeClass.getSimpleName())); } - getValue().add(index, t); - } public void addAll(Collection t) { @@ -251,36 +253,6 @@ public ListTag asCompoundTagList() { return asTypedList(CompoundTag.class); } - @Override - public void serializeValue(DataOutputStream dos, int maxDepth) throws IOException { - dos.writeByte(size() != 0 ? TagFactory.idFromClass(getTypeClass()) : 0); // paylod type should be 0 in empty case - dos.writeInt(size()); - if (size() != 0) { - for (T t : getValue()) { - t.serializeValue(dos, decrementMaxDepth(maxDepth)); - } - } - } - - @SuppressWarnings("unchecked") - @Override - 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) { - for (int i = 0; i < size; i++) { - Tag tag = TagFactory.fromID(typeID); - tag.deserializeValue(dis, decrementMaxDepth(maxDepth)); - add((T) tag); - } - } - } - @Override public String valueToString(int maxDepth) { StringBuilder sb = new StringBuilder("{\"type\":\"").append(getTypeClass().getSimpleName()).append("\",\"list\":["); @@ -291,16 +263,6 @@ public String valueToString(int maxDepth) { return sb.toString(); } - @Override - public String valueToTagString(int maxDepth) { - StringBuilder sb = new StringBuilder("["); - for (int i = 0; i < size(); i++) { - sb.append(i > 0 ? "," : "").append(get(i).valueToTagString(decrementMaxDepth(maxDepth))); - } - sb.append("]"); - return sb.toString(); - } - @Override public boolean equals(Object other) { if (this == other) { @@ -351,7 +313,7 @@ public void addUnchecked(Tag tag) { } private void checkTypeClass(Class clazz) { - if (typeClass != null && typeClass != clazz) { + if (typeClass != null && typeClass != EndTag.class && typeClass != clazz) { throw new ClassCastException(String.format( "cannot cast ListTag<%s> to ListTag<%s>", typeClass.getSimpleName(), clazz.getSimpleName())); diff --git a/src/main/java/net/querz/nbt/LongArrayTag.java b/src/main/java/net/querz/nbt/LongArrayTag.java index ec5452d7..ae591ba6 100644 --- a/src/main/java/net/querz/nbt/LongArrayTag.java +++ b/src/main/java/net/querz/nbt/LongArrayTag.java @@ -1,12 +1,10 @@ package net.querz.nbt; -import java.io.DataInputStream; -import java.io.DataOutputStream; -import java.io.IOException; import java.util.Arrays; public class LongArrayTag extends ArrayTag implements Comparable { + public static final byte ID = 12; public static final long[] ZERO_VALUE = new long[0]; public LongArrayTag() { @@ -18,25 +16,8 @@ public LongArrayTag(long[] value) { } @Override - public void serializeValue(DataOutputStream dos, int maxDepth) throws IOException { - dos.writeInt(length()); - for (long i : getValue()) { - dos.writeLong(i); - } - } - - @Override - public void deserializeValue(DataInputStream dis, int maxDepth) throws IOException { - int length = dis.readInt(); - setValue(new long[length]); - for (int i = 0; i < length; i++) { - getValue()[i] = dis.readLong(); - } - } - - @Override - public String valueToTagString(int maxDepth) { - return arrayToString("L", "l"); + public byte getID() { + return ID; } @Override diff --git a/src/main/java/net/querz/nbt/LongTag.java b/src/main/java/net/querz/nbt/LongTag.java index 38a7e9f2..249f84ca 100644 --- a/src/main/java/net/querz/nbt/LongTag.java +++ b/src/main/java/net/querz/nbt/LongTag.java @@ -1,11 +1,8 @@ package net.querz.nbt; -import java.io.DataInputStream; -import java.io.DataOutputStream; -import java.io.IOException; - public class LongTag extends NumberTag implements Comparable { + public static final byte ID = 4; public static final long ZERO_VALUE = 0L; public LongTag() { @@ -16,23 +13,13 @@ public LongTag(long value) { super(value); } - public void setValue(long value) { - super.setValue(value); - } - @Override - public void serializeValue(DataOutputStream dos, int maxDepth) throws IOException { - dos.writeLong(getValue()); + public byte getID() { + return ID; } - @Override - public void deserializeValue(DataInputStream dis, int maxDepth) throws IOException { - setValue(dis.readLong()); - } - - @Override - public String valueToTagString(int maxDepth) { - return getValue() + "l"; + public void setValue(long value) { + super.setValue(value); } @Override diff --git a/src/main/java/net/querz/nbt/NBTUtil.java b/src/main/java/net/querz/nbt/NBTUtil.java deleted file mode 100644 index 3779e795..00000000 --- a/src/main/java/net/querz/nbt/NBTUtil.java +++ /dev/null @@ -1,172 +0,0 @@ -package net.querz.nbt; - -import java.io.DataInputStream; -import java.io.DataOutputStream; -import java.io.File; -import java.io.FileInputStream; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.PushbackInputStream; -import java.util.zip.GZIPInputStream; -import java.util.zip.GZIPOutputStream; - -/** - * Provides main functions to read Tags from and write Tags to nbt files. - * This does not support reading or writing .mca files, use {@link net.querz.nbt.mca.MCAUtil} instead. - * */ -public final class NBTUtil { - - private NBTUtil() {} - - /** - * Calls {@link NBTUtil#writeTag(Tag, String, File, boolean)} with an empty name and GZIP compression. - * @see NBTUtil#writeTag(Tag, String, File, boolean) - * @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. - * @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); - } - - /** - * Calls {@link NBTUtil#writeTag(Tag, String, File, boolean)} with an empty name and GZIP compression. - * @see NBTUtil#writeTag(Tag, String, File, boolean) - * @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. - * @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); - } - - /** - * Calls {@link NBTUtil#writeTag(Tag, String, File, boolean)} with an empty name. - * @see NBTUtil#writeTag(Tag, String, File, boolean) - * @param tag The tag to be written to the file. - * @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. - * @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); - } - - /** - * Calls {@link NBTUtil#writeTag(Tag, String, File, boolean)} with an empty name. - * @see NBTUtil#writeTag(Tag, String, File, boolean) - * @param tag The tag to be written to the file. - * @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. - * @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); - } - - /** - * @see NBTUtil#writeTag(Tag, String, File) - * @param tag The tag to be written to the file. - * @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. - * @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); - } - - /** - * Calls {@link NBTUtil#writeTag(Tag, String, String, boolean)} with GZIP compression. - * @see NBTUtil#writeTag(Tag, String, File, boolean) - * @param tag The tag to be written to the file. - * @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. - * @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); - } - - /** - * @see NBTUtil#writeTag(Tag, String, String, boolean) - * @param tag The tag to be written to the file. - * @param name The name of the root tag. - * @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. - * @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); - } - - /** - * Writes the Tag {@code tag} to File {@code file}. A {@code name} for the root tag can be specified, - * but will be ignored during deserialization. Also allows for optional GZIP compression. - * @param tag The tag to be written to the file. - * @param name The name of the root tag. - * @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. - * @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, Tag.DEFAULT_MAX_DEPTH); - } - } - - /** - * @see NBTUtil#readTag(File) - * @param file The file to read a Tag from. - * @return The tag read from the file. - * @throws IOException If something during deserialization goes wrong. - * */ - public static Tag readTag(String file) throws IOException { - return readTag(new File(file)); - } - - /** - * Reads a Tag from the specified file. Automatically detects GZIP detection - * and decompresses the stream if necessary. - * @param file The file to read a Tag from. - * @return The tag read from the file. - * @throws IOException If something during deserialization goes wrong. - * @throws NullPointerException If {@code file} is {@code null}. - * @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, Tag.DEFAULT_MAX_DEPTH); - } - } - - static InputStream applyDecompression(InputStream is) throws IOException { - PushbackInputStream pbis = new PushbackInputStream(is, 2); - int sig = (pbis.read() & 0xFF) + (pbis.read() << 8); - pbis.unread(sig >> 8); - pbis.unread(sig & 0xFF); - if (sig == GZIPInputStream.GZIP_MAGIC) { - return new GZIPInputStream(pbis); - } - return pbis; - } -} diff --git a/src/main/java/net/querz/nbt/ShortTag.java b/src/main/java/net/querz/nbt/ShortTag.java index 3534b217..60fe36d1 100644 --- a/src/main/java/net/querz/nbt/ShortTag.java +++ b/src/main/java/net/querz/nbt/ShortTag.java @@ -1,11 +1,8 @@ package net.querz.nbt; -import java.io.DataInputStream; -import java.io.DataOutputStream; -import java.io.IOException; - public class ShortTag extends NumberTag implements Comparable { + public static final byte ID = 2; public static final short ZERO_VALUE = 0; public ShortTag() { @@ -16,23 +13,13 @@ public ShortTag(short value) { super(value); } - public void setValue(short value) { - super.setValue(value); - } - @Override - public void serializeValue(DataOutputStream dos, int maxDepth) throws IOException { - dos.writeShort(getValue()); + public byte getID() { + return ID; } - @Override - public void deserializeValue(DataInputStream dis, int maxDepth) throws IOException { - setValue(dis.readShort()); - } - - @Override - public String valueToTagString(int maxDepth) { - return getValue() + "s"; + public void setValue(short value) { + super.setValue(value); } @Override diff --git a/src/main/java/net/querz/nbt/StringTag.java b/src/main/java/net/querz/nbt/StringTag.java index 8350f518..9d69684b 100644 --- a/src/main/java/net/querz/nbt/StringTag.java +++ b/src/main/java/net/querz/nbt/StringTag.java @@ -1,11 +1,8 @@ package net.querz.nbt; -import java.io.DataInputStream; -import java.io.DataOutputStream; -import java.io.IOException; - public class StringTag extends Tag implements Comparable { + public static final byte ID = 8; public static final String ZERO_VALUE = ""; public StringTag() { @@ -16,6 +13,11 @@ public StringTag(String value) { super(value); } + @Override + public byte getID() { + return ID; + } + @Override public String getValue() { return super.getValue(); @@ -26,26 +28,11 @@ public void setValue(String value) { super.setValue(value); } - @Override - public void serializeValue(DataOutputStream dos, int maxDepth) throws IOException { - dos.writeUTF(getValue()); - } - - @Override - public void deserializeValue(DataInputStream dis, int maxDepth) throws IOException { - setValue(dis.readUTF()); - } - @Override public String valueToString(int maxDepth) { return escapeString(getValue(), false); } - @Override - public String valueToTagString(int maxDepth) { - return escapeString(getValue(), true); - } - @Override public boolean equals(Object other) { return super.equals(other) && getValue().equals(((StringTag) other).getValue()); diff --git a/src/main/java/net/querz/nbt/Tag.java b/src/main/java/net/querz/nbt/Tag.java index 2a92c042..29da5b67 100644 --- a/src/main/java/net/querz/nbt/Tag.java +++ b/src/main/java/net/querz/nbt/Tag.java @@ -1,8 +1,7 @@ package net.querz.nbt; -import java.io.DataInputStream; -import java.io.DataOutputStream; -import java.io.IOException; +import net.querz.io.MaxDepthReachedException; + import java.util.Collections; import java.util.HashMap; import java.util.Map; @@ -20,7 +19,7 @@ * *

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. + * 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.

* @@ -69,9 +68,7 @@ public Tag(T value) { /** * @return This Tag's ID, usually used for serialization and deserialization. * */ - public byte getID() { - return TagFactory.idFromClass(getClass()); - } + public abstract byte getID(); /** * @return The value of this Tag. @@ -99,74 +96,6 @@ 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 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 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 maxDepth The maximum nesting depth - * @throws IOException If something went wrong during serialization. - * @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 maxDepth) throws IOException { - dos.writeByte(getID()); - if (getID() != 0) { - dos.writeUTF(name); - } - 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 maxDepth The maximum nesting depth. - * @throws IOException If something went wrong during deserialization. - * @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 maxDepth) throws IOException { - int id = dis.readByte() & 0xFF; - Tag tag = TagFactory.fromID(id); - if (id != 0) { - dis.readUTF(); - tag.deserializeValue(dis, maxDepth); - } - return tag; - } - - /** - * Serializes only the value of this Tag. - * @param dos The DataOutputStream to write to. - * @param maxDepth The maximum nesting depth. - * @throws IOException If something went wrong during serialization. - * @throws MaxDepthReachedException If the maximum nesting depth is exceeded. - * */ - 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 maxDepth The maximum nesting depth. - * @throws IOException If something went wrong during deserialization. - * @throws MaxDepthReachedException If the maximum nesting depth is exceeded - * */ - 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) @@ -196,33 +125,6 @@ public String toString(int maxDepth) { * */ public abstract String valueToString(int maxDepth); - /** - * Calls {@link Tag#toTagString(int)} with {@link #DEFAULT_MAX_DEPTH}. - * @see Tag#toTagString(int) - * @return The JSON-like string representation of this Tag. - * */ - 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 maxDepth The maximum nesting depth. - * @return The JSON-like string representation of this Tag. - * @throws MaxDepthReachedException If the maximum nesting depth is exceeded. - * */ - public String toTagString(int maxDepth) { - return valueToTagString(maxDepth); - } - - /** - * Returns a JSON-like representation of the value of this Tag. - * @param maxDepth The maximum nesting depth. - * @return The JSON-like string representation of the value of this Tag. - * @throws MaxDepthReachedException If the maximum nesting depth is exceeded. - * */ - public abstract String valueToTagString(int maxDepth); - /** * Returns whether this Tag and some other Tag are equal. * They are equal if {@code other} is not {@code null} and they are of the same class. @@ -253,25 +155,6 @@ public int hashCode() { @SuppressWarnings("CloneDoesntDeclareCloneNotSupportedException") public abstract Tag clone(); - /** - * 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 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 --maxDepth; - } - /** * Escapes a string to fit into a JSON-like string representation for Minecraft * or to create the JSON string representation of a Tag returned from {@link Tag#toString()} diff --git a/src/main/java/net/querz/nbt/TagFactory.java b/src/main/java/net/querz/nbt/TagFactory.java deleted file mode 100644 index cfd5a65e..00000000 --- a/src/main/java/net/querz/nbt/TagFactory.java +++ /dev/null @@ -1,101 +0,0 @@ -package net.querz.nbt; - -import java.util.HashMap; -import java.util.Map; -import java.util.function.Supplier; - -public final class TagFactory { - - private static class TagMapping> { - - private int id; - private Supplier factory; - private Class clazz; - - TagMapping(int id, Supplier factory, Class clazz) { - this.id = id; - this.factory = factory; - this.clazz = clazz; - } - } - - private static Map> idMapping = new HashMap<>(); - private static Map, TagMapping> classMapping = new HashMap<>(); - static { - put(0, () -> EndTag.INSTANCE, EndTag.class); - put(1, ByteTag::new, ByteTag.class); - put(2, ShortTag::new, ShortTag.class); - put(3, IntTag::new, IntTag.class); - put(4, LongTag::new, LongTag.class); - put(5, FloatTag::new, FloatTag.class); - put(6, DoubleTag::new, DoubleTag.class); - put(7, ByteArrayTag::new, ByteArrayTag.class); - put(8, StringTag::new, StringTag.class); - put(9, ListTag::createUnchecked, ListTag.class); - put(10, CompoundTag::new, CompoundTag.class); - put(11, IntArrayTag::new, IntArrayTag.class); - put(12, LongArrayTag::new, LongArrayTag.class); - } - - private static > void put(int id, Supplier factory, Class clazz) { - TagMapping mapping = new TagMapping<>(id, factory, clazz); - idMapping.put(id, mapping); - classMapping.put(clazz, mapping); - } - - private TagFactory() {} - - public static Tag fromID(int id) { - TagMapping mapping = idMapping.get(id); - if (mapping == null) { - throw new IllegalArgumentException("unknown Tag id " + id); - } - return mapping.factory.get(); - } - - public static Class classFromID(int id) { - TagMapping mapping = idMapping.get(id); - if (mapping == null) { - throw new IllegalArgumentException("unknown Tag id " + id); - } - return mapping.clazz; - } - - public static byte idFromClass(Class clazz) { - TagMapping mapping = classMapping.get(clazz); - if (mapping == null) { - throw new IllegalArgumentException("unknown Tag class " + clazz.getName()); - } - return (byte) mapping.id; - } - - public static > void registerCustomTag(int id, Supplier factory, Class clazz) { - checkID(id); - if (idMapping.containsKey(id)) { - throw new IllegalArgumentException("custom tag already registered"); - } - put(id, factory, clazz); - } - - public static void unregisterCustomTag(int id) { - idMapping.remove(id); - for (TagMapping mapping : classMapping.values()) { - if (mapping.id == id) { - classMapping.remove(mapping.clazz); - return; - } - } - } - - private static void checkID(int id) { - if (id < 0) { - throw new IllegalArgumentException("id cannot be negative"); - } - if (id <= 12) { - throw new IllegalArgumentException("cannot change default tags"); - } - if (id > Byte.MAX_VALUE) { - throw new IllegalArgumentException("id out of bounds: " + id); - } - } -} diff --git a/src/main/java/net/querz/nbt/custom/CharTag.java b/src/main/java/net/querz/nbt/custom/CharTag.java index cba9654c..56e8a3b7 100644 --- a/src/main/java/net/querz/nbt/custom/CharTag.java +++ b/src/main/java/net/querz/nbt/custom/CharTag.java @@ -1,9 +1,9 @@ package net.querz.nbt.custom; import net.querz.nbt.Tag; -import net.querz.nbt.TagFactory; -import java.io.DataInputStream; -import java.io.DataOutputStream; +import net.querz.nbt.io.NBTInputStream; +import net.querz.nbt.io.NBTOutputStream; +import net.querz.nbt.io.NBTUtil; import java.io.IOException; public class CharTag extends Tag implements Comparable { @@ -11,7 +11,7 @@ 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); + NBTUtil.registerCustomTag(110, (o, t, m) -> serialize(o, t), (i, m) -> deserialize(i), CharTag.class); } public CharTag() { @@ -22,6 +22,11 @@ public CharTag(char value) { super(value); } + @Override + public byte getID() { + return 110; + } + @Override public Character getValue() { return super.getValue(); @@ -31,14 +36,12 @@ public void setValue(char value) { super.setValue(value); } - @Override - public void serializeValue(DataOutputStream dos, int maxDepth) throws IOException { - dos.writeChar(getValue()); + public static void serialize(NBTOutputStream dos, CharTag tag) throws IOException { + dos.writeChar(tag.getValue()); } - @Override - public void deserializeValue(DataInputStream dis, int maxDepth) throws IOException { - setValue(dis.readChar()); + public static CharTag deserialize(NBTInputStream dis) throws IOException { + return new CharTag(dis.readChar()); } @Override @@ -46,11 +49,6 @@ public String valueToString(int maxDepth) { return escapeString(getValue() + "", false); } - @Override - public String valueToTagString(int maxDepth) { - return escapeString(getValue() + "", true); - } - @Override public boolean equals(Object other) { return super.equals(other) && getValue() == ((CharTag) other).getValue(); diff --git a/src/main/java/net/querz/nbt/custom/ObjectTag.java b/src/main/java/net/querz/nbt/custom/ObjectTag.java index 92ea5624..9a9a057e 100644 --- a/src/main/java/net/querz/nbt/custom/ObjectTag.java +++ b/src/main/java/net/querz/nbt/custom/ObjectTag.java @@ -1,9 +1,9 @@ package net.querz.nbt.custom; import net.querz.nbt.Tag; -import net.querz.nbt.TagFactory; +import net.querz.nbt.io.NBTOutputStream; +import net.querz.nbt.io.NBTUtil; import java.io.DataInputStream; -import java.io.DataOutputStream; import java.io.IOException; import java.io.InvalidClassException; import java.io.ObjectInputStream; @@ -15,7 +15,7 @@ public class ObjectTag extends Tag implements Comparable> { public static void register() { - TagFactory.registerCustomTag(90, ObjectTag::new, ObjectTag.class); + NBTUtil.registerCustomTag(90, (o, t, m) -> serialize(o, t), (i, m) -> deserialize(i), ObjectTag.class); } public ObjectTag() { @@ -26,6 +26,15 @@ public ObjectTag(T value) { super(value); } + @Override + public byte getID() { + return 90; + } + + private static ObjectTag createUnchecked(O value) { + return new ObjectTag<>(value); + } + @Override protected T checkValue(T value) { return value; @@ -47,16 +56,14 @@ public ObjectTag asTypedObjectTag(Class type) { return (ObjectTag) this; } - @Override - public void serializeValue(DataOutputStream dos, int maxDepth) throws IOException { - new ObjectOutputStream(dos).writeObject(getValue()); + public static void serialize(NBTOutputStream dos, ObjectTag tag) throws IOException { + new ObjectOutputStream(dos).writeObject(tag.getValue()); } @SuppressWarnings("unchecked") - @Override - public void deserializeValue(DataInputStream dis, int maxDepth) throws IOException { + public static ObjectTag deserialize(DataInputStream dis) throws IOException { try { - setValue((T) new ObjectInputStream(dis).readObject()); + return createUnchecked((Serializable) new ObjectInputStream(dis).readObject()); } catch (InvalidClassException | ClassNotFoundException e) { throw new IOException(e.getCause()); } @@ -67,11 +74,6 @@ public String valueToString(int maxDepth) { return getValue() == null ? "null" : escapeString(getValue().toString(), false); } - @Override - public String valueToTagString(int maxDepth) { - return getValue() == null ? "null" : escapeString(getValue().toString(), true); - } - @Override public boolean equals(Object other) { return super.equals(other) && Objects.equals(getValue(), ((ObjectTag) other).getValue()); diff --git a/src/main/java/net/querz/nbt/custom/ShortArrayTag.java b/src/main/java/net/querz/nbt/custom/ShortArrayTag.java index ba46d33e..6b748d01 100644 --- a/src/main/java/net/querz/nbt/custom/ShortArrayTag.java +++ b/src/main/java/net/querz/nbt/custom/ShortArrayTag.java @@ -1,9 +1,9 @@ package net.querz.nbt.custom; import net.querz.nbt.ArrayTag; -import net.querz.nbt.TagFactory; -import java.io.DataInputStream; -import java.io.DataOutputStream; +import net.querz.nbt.io.NBTInputStream; +import net.querz.nbt.io.NBTOutputStream; +import net.querz.nbt.io.NBTUtil; import java.io.IOException; import java.util.Arrays; @@ -12,7 +12,7 @@ public class ShortArrayTag extends ArrayTag implements Comparable serialize(o, t), (i, m) -> deserialize(i), ShortArrayTag.class); } public ShortArrayTag() { @@ -24,25 +24,23 @@ public ShortArrayTag(short[] value) { } @Override - public void serializeValue(DataOutputStream dos, int maxDepth) throws IOException { - dos.writeInt(length()); - for (int i : getValue()) { - dos.writeShort(i); - } + public byte getID() { + return 100; } - @Override - public void deserializeValue(DataInputStream dis, int maxDepth) throws IOException { - int length = dis.readInt(); - setValue(new short[length]); - for (int i = 0; i < length; i++) { - getValue()[i] = dis.readShort(); + public static void serialize(NBTOutputStream dos, ShortArrayTag tag) throws IOException { + dos.writeInt(tag.length()); + for (int i : tag.getValue()) { + dos.writeShort(i); } } - @Override - public String valueToTagString(int maxDepth) { - return arrayToString("S", "s"); + public static ShortArrayTag deserialize(NBTInputStream dis) throws IOException { + ShortArrayTag tag = new ShortArrayTag(new short[dis.readInt()]); + for (int i = 0; i < tag.length(); i++) { + tag.getValue()[i] = dis.readShort(); + } + return tag; } @Override diff --git a/src/main/java/net/querz/nbt/custom/StructTag.java b/src/main/java/net/querz/nbt/custom/StructTag.java index 47c7215c..6218ab8f 100644 --- a/src/main/java/net/querz/nbt/custom/StructTag.java +++ b/src/main/java/net/querz/nbt/custom/StructTag.java @@ -13,9 +13,10 @@ import net.querz.nbt.ShortTag; import net.querz.nbt.StringTag; import net.querz.nbt.Tag; -import net.querz.nbt.TagFactory; -import java.io.DataInputStream; -import java.io.DataOutputStream; +import net.querz.io.MaxDepthIO; +import net.querz.nbt.io.NBTInputStream; +import net.querz.nbt.io.NBTOutputStream; +import net.querz.nbt.io.NBTUtil; import java.io.IOException; import java.util.ArrayList; import java.util.Collection; @@ -24,16 +25,21 @@ import java.util.Objects; import java.util.function.Consumer; -public class StructTag extends Tag>> implements Iterable>, Comparable { +public class StructTag extends Tag>> implements Iterable>, Comparable, MaxDepthIO { public static void register() { - TagFactory.registerCustomTag(120, StructTag::new, StructTag.class); + NBTUtil.registerCustomTag(120, StructTag::serialize, StructTag::deserialize, StructTag.class); } public StructTag() { super(createEmptyValue()); } + @Override + public byte getID() { + return 120; + } + private static List> createEmptyValue() { return new ArrayList<>(3); } @@ -230,25 +236,24 @@ public void addLongArray(long[] value) { add(new LongArrayTag(value)); } - @Override - public void serializeValue(DataOutputStream dos, int maxDepth) throws IOException { - dos.writeInt(size()); - for (Tag tag : getValue()) { - dos.writeByte(tag.getID()); - tag.serializeValue(dos, decrementMaxDepth(maxDepth)); + public static void serialize(NBTOutputStream dos, StructTag tag, int maxDepth) throws IOException { + dos.writeInt(tag.size()); + for (Tag t : tag.getValue()) { + dos.writeByte(t.getID()); + dos.writeRawTag(t, dos.decrementMaxDepth(maxDepth)); } } - @Override - public void deserializeValue(DataInputStream dis, int maxDepth) throws IOException { + public static StructTag deserialize(NBTInputStream dis, int maxDepth) throws IOException { int size = dis.readInt(); size = size < 0 ? 0 : size; - setValue(new ArrayList<>(size)); + StructTag t = new StructTag(); + t.setValue(new ArrayList<>(size)); for (int i = 0; i < size; i++) { - Tag tag = TagFactory.fromID(dis.readByte()); - tag.deserializeValue(dis, decrementMaxDepth(maxDepth)); - add(tag); + Tag tag = dis.readRawTag(dis.decrementMaxDepth(maxDepth)); + t.add(tag); } + return t; } @Override @@ -261,16 +266,6 @@ public String valueToString(int maxDepth) { return sb.toString(); } - @Override - public String valueToTagString(int maxDepth) { - StringBuilder sb = new StringBuilder("["); - for (int i = 0; i < size(); i++) { - sb.append(i > 0 ? "," : "").append(get(i).valueToTagString(decrementMaxDepth(maxDepth))); - } - sb.append("]"); - return sb.toString(); - } - @Override public boolean equals(Object other) { if (!super.equals(other) || size() != ((StructTag) other).size()) { diff --git a/src/main/java/net/querz/nbt/io/Deserializer.java b/src/main/java/net/querz/nbt/io/Deserializer.java deleted file mode 100644 index 0d664a1d..00000000 --- a/src/main/java/net/querz/nbt/io/Deserializer.java +++ /dev/null @@ -1,8 +0,0 @@ -package net.querz.nbt.io; - -import net.querz.nbt.Tag; - -public interface Deserializer extends MaxDepthIO { - - Tag read(int maxDepth); -} diff --git a/src/main/java/net/querz/nbt/io/MSONDeserializer.java b/src/main/java/net/querz/nbt/io/MSONDeserializer.java index 1550f4f9..071d6f4f 100644 --- a/src/main/java/net/querz/nbt/io/MSONDeserializer.java +++ b/src/main/java/net/querz/nbt/io/MSONDeserializer.java @@ -1,176 +1,26 @@ package net.querz.nbt.io; -import net.querz.nbt.ArrayTag; -import net.querz.nbt.ByteArrayTag; -import net.querz.nbt.ByteTag; -import net.querz.nbt.CompoundTag; -import net.querz.nbt.DoubleTag; -import net.querz.nbt.FloatTag; -import net.querz.nbt.IntArrayTag; -import net.querz.nbt.IntTag; -import net.querz.nbt.ListTag; -import net.querz.nbt.LongArrayTag; -import net.querz.nbt.LongTag; -import net.querz.nbt.ShortTag; -import net.querz.nbt.StringTag; +import net.querz.io.StringDeserializer; import net.querz.nbt.Tag; -import java.util.ArrayList; -import java.util.List; -import java.util.function.Function; -import java.util.regex.Pattern; +import java.io.BufferedReader; +import java.io.IOException; +import java.io.Reader; +import java.util.stream.Collectors; -public final class MSONDeserializer implements Deserializer { - - private static final Pattern - FLOAT_PATTERN = Pattern.compile("^[-+]?(?:\\d+\\.?|\\d*\\.\\d+)(?:e[-+]?\\d+)?f$", Pattern.CASE_INSENSITIVE), - DOUBLE_PATTERN = Pattern.compile("^[-+]?(?:\\d+\\.?|\\d*\\.\\d+)(?:e[-+]?\\d+)?d$", Pattern.CASE_INSENSITIVE), - DOUBLE_NO_SUFFIX_PATTERN = Pattern.compile("^[-+]?(?:\\d+\\.|\\d*\\.\\d+)(?:e[-+]?\\d+)?$", Pattern.CASE_INSENSITIVE), - BYTE_PATTERN = Pattern.compile("^[-+]?\\d+b$", Pattern.CASE_INSENSITIVE), - SHORT_PATTERN = Pattern.compile("^[-+]?\\d+s$", Pattern.CASE_INSENSITIVE), - INT_PATTERN = Pattern.compile("^[-+]?\\d+$", Pattern.CASE_INSENSITIVE), - LONG_PATTERN = Pattern.compile("^[-+]?\\d+l$", Pattern.CASE_INSENSITIVE); - - private StringPointer stream; - - public MSONDeserializer(String string) { - this.stream = new StringPointer(string); - } +public class MSONDeserializer implements StringDeserializer> { @Override - public Tag read(int maxDepth) { - stream.skipWhitespace(); - switch (stream.currentChar()) { - case '{': - return parseCompoundTag(maxDepth); - case '[': - if (stream.hasCharsLeft(2) && stream.lookAhead(1) != '"' && stream.lookAhead(2) == ';') { - return parseNumArray(); - } - return parseListTag(maxDepth); - } - return parseStringOrLiteral(); - } - - private Tag parseStringOrLiteral() { - stream.skipWhitespace(); - if (stream.currentChar() == '"') { - return new StringTag(stream.parseQuotedString()); - } - String s = stream.parseSimpleString(); - if (s.isEmpty()) { - throw new ParseException("expected non empty value"); - } - try { - if (FLOAT_PATTERN.matcher(s).matches()) { - return new FloatTag(Float.parseFloat(s.substring(0, s.length() - 1))); - } else if (BYTE_PATTERN.matcher(s).matches()) { - return new ByteTag(Byte.parseByte(s.substring(0, s.length() - 1))); - } else if (SHORT_PATTERN.matcher(s).matches()) { - return new ShortTag(Short.parseShort(s.substring(0, s.length() - 1))); - } else if (LONG_PATTERN.matcher(s).matches()) { - return new LongTag(Long.parseLong(s.substring(0, s.length() - 1))); - } else if (INT_PATTERN.matcher(s).matches()) { - return new IntTag(Integer.parseInt(s)); - } else if (DOUBLE_PATTERN.matcher(s).matches()) { - return new DoubleTag(Double.parseDouble(s.substring(0, s.length() - 1))); - } else if (DOUBLE_NO_SUFFIX_PATTERN.matcher(s).matches()) { - return new DoubleTag(Double.parseDouble(s)); - } else if ("true".equalsIgnoreCase(s)) { - return new ByteTag(true); - } else if ("false".equalsIgnoreCase(s)) { - return new ByteTag(false); - } - } catch (NumberFormatException ex) { - return new StringTag(s); - } - return new StringTag(s); - } - - private CompoundTag parseCompoundTag(int maxDepth) { - stream.expectChar('{'); - - CompoundTag compoundTag = new CompoundTag(); - - stream.skipWhitespace(); - while (stream.hasNext() && stream.currentChar() != '}') { - stream.skipWhitespace(); - String key = stream.currentChar() == '"' ? stream.parseQuotedString() : stream.parseSimpleString(); - if (key.isEmpty()) { - throw new ParseException("empty keys are not allowed"); - } - stream.expectChar(':'); - - compoundTag.put(key, read(decrementMaxDepth(maxDepth))); - - if (!stream.nextArrayElement()) { - break; - } - } - stream.expectChar('}'); - return compoundTag; - } - - private ListTag parseListTag(int maxDepth) { - stream.expectChar('['); - stream.skipWhitespace(); - ListTag list = ListTag.createUnchecked(); - while (stream.currentChar() != ']') { - Tag element = read(decrementMaxDepth(maxDepth)); - list.addUnchecked(element); - if (!stream.nextArrayElement()) { - break; - } - } - stream.expectChar(']'); - return list; - } - - private ArrayTag parseNumArray() { - stream.expectChar('['); - char arrayType = stream.next(); - stream.expectChar(';'); - stream.skipWhitespace(); - switch (arrayType) { - case 'B': - return parseNumericArrayTag( - a -> new ByteArrayTag((byte[]) a), - Byte::parseByte, - li -> { - byte[] bytes = new byte[li.size()]; - for (int i = 0; i < li.size(); i++) { - bytes[i] = li.get(i).byteValue(); - } - return bytes; - }); - case 'I': - return parseNumericArrayTag( - a -> new IntArrayTag((int[]) a), - Integer::parseInt, - li -> li.stream().mapToInt(i -> (int) i).toArray()); - case 'L': - return parseNumericArrayTag( - a -> new LongArrayTag((long[]) a), - Long::parseLong, - li -> li.stream().mapToLong(l -> (long) l).toArray()); - } - throw new ParseException("invalid array type '" + arrayType + "'"); + public Tag fromReader(Reader reader) throws IOException { + return fromReader(reader, Tag.DEFAULT_MAX_DEPTH); } - private ArrayTag parseNumericArrayTag( - Function> constructor, - Function numberParser, - Function, Object> arrayParser) { - - List numberList = new ArrayList<>(); - while (stream.currentChar() != ']') { - String s = stream.parseSimpleString(); - stream.skipWhitespace(); - numberList.add(numberParser.apply(s)); - if (!stream.nextArrayElement()) { - break; - } + public Tag fromReader(Reader reader, int maxDepth) throws IOException { + BufferedReader bufferedReader; + if (reader instanceof BufferedReader) { + bufferedReader = (BufferedReader) reader; + } else { + bufferedReader = new BufferedReader(reader); } - stream.expectChar(']'); - return constructor.apply(arrayParser.apply(numberList)); + return MSONParser.parse(bufferedReader.lines().collect(Collectors.joining()), maxDepth); } } diff --git a/src/main/java/net/querz/nbt/io/MSONParser.java b/src/main/java/net/querz/nbt/io/MSONParser.java new file mode 100644 index 00000000..6e28c160 --- /dev/null +++ b/src/main/java/net/querz/nbt/io/MSONParser.java @@ -0,0 +1,209 @@ +package net.querz.nbt.io; + +import net.querz.io.MaxDepthIO; +import net.querz.nbt.ArrayTag; +import net.querz.nbt.ByteArrayTag; +import net.querz.nbt.ByteTag; +import net.querz.nbt.CompoundTag; +import net.querz.nbt.DoubleTag; +import net.querz.nbt.EndTag; +import net.querz.nbt.FloatTag; +import net.querz.nbt.IntArrayTag; +import net.querz.nbt.IntTag; +import net.querz.nbt.ListTag; +import net.querz.nbt.LongArrayTag; +import net.querz.nbt.LongTag; +import net.querz.nbt.ShortTag; +import net.querz.nbt.StringTag; +import net.querz.nbt.Tag; +import java.util.ArrayList; +import java.util.List; +import java.util.regex.Pattern; + +public final class MSONParser implements MaxDepthIO { + + private static final Pattern + FLOAT_PATTERN = Pattern.compile("^[-+]?(?:\\d+\\.?|\\d*\\.\\d+)(?:e[-+]?\\d+)?f$", Pattern.CASE_INSENSITIVE), + DOUBLE_PATTERN = Pattern.compile("^[-+]?(?:\\d+\\.?|\\d*\\.\\d+)(?:e[-+]?\\d+)?d$", Pattern.CASE_INSENSITIVE), + DOUBLE_NO_SUFFIX_PATTERN = Pattern.compile("^[-+]?(?:\\d+\\.|\\d*\\.\\d+)(?:e[-+]?\\d+)?$", Pattern.CASE_INSENSITIVE), + BYTE_PATTERN = Pattern.compile("^[-+]?\\d+b$", Pattern.CASE_INSENSITIVE), + SHORT_PATTERN = Pattern.compile("^[-+]?\\d+s$", Pattern.CASE_INSENSITIVE), + INT_PATTERN = Pattern.compile("^[-+]?\\d+$", Pattern.CASE_INSENSITIVE), + LONG_PATTERN = Pattern.compile("^[-+]?\\d+l$", Pattern.CASE_INSENSITIVE); + + private StringPointer ptr; + + private MSONParser(String string) { + this.ptr = new StringPointer(string); + } + + public static Tag parse(String string, int maxDepth) throws ParseException { + return new MSONParser(string).parseAnything(maxDepth); + } + + public static Tag parse(String string) throws ParseException { + return parse(string, Tag.DEFAULT_MAX_DEPTH); + } + + private Tag parseAnything(int maxDepth) throws ParseException { + ptr.skipWhitespace(); + switch (ptr.currentChar()) { + case '{': + return parseCompoundTag(maxDepth); + case '[': + if (ptr.hasCharsLeft(2) && ptr.lookAhead(1) != '"' && ptr.lookAhead(2) == ';') { + return parseNumArray(); + } + return parseListTag(maxDepth); + } + return parseStringOrLiteral(); + } + + private Tag parseStringOrLiteral() throws ParseException { + ptr.skipWhitespace(); + if (ptr.currentChar() == '"') { + return new StringTag(ptr.parseQuotedString()); + } + String s = ptr.parseSimpleString(); + if (s.isEmpty()) { + throw new ParseException("expected non empty value"); + } + try { + if (FLOAT_PATTERN.matcher(s).matches()) { + return new FloatTag(Float.parseFloat(s.substring(0, s.length() - 1))); + } else if (BYTE_PATTERN.matcher(s).matches()) { + return new ByteTag(Byte.parseByte(s.substring(0, s.length() - 1))); + } else if (SHORT_PATTERN.matcher(s).matches()) { + return new ShortTag(Short.parseShort(s.substring(0, s.length() - 1))); + } else if (LONG_PATTERN.matcher(s).matches()) { + return new LongTag(Long.parseLong(s.substring(0, s.length() - 1))); + } else if (INT_PATTERN.matcher(s).matches()) { + return new IntTag(Integer.parseInt(s)); + } else if (DOUBLE_PATTERN.matcher(s).matches()) { + return new DoubleTag(Double.parseDouble(s.substring(0, s.length() - 1))); + } else if (DOUBLE_NO_SUFFIX_PATTERN.matcher(s).matches()) { + return new DoubleTag(Double.parseDouble(s)); + } else if ("true".equalsIgnoreCase(s)) { + return new ByteTag(true); + } else if ("false".equalsIgnoreCase(s)) { + return new ByteTag(false); + } + } catch (NumberFormatException ex) { + return new StringTag(s); + } + return new StringTag(s); + } + + private CompoundTag parseCompoundTag(int maxDepth) throws ParseException { + ptr.expectChar('{'); + + CompoundTag compoundTag = new CompoundTag(); + + ptr.skipWhitespace(); + while (ptr.hasNext() && ptr.currentChar() != '}') { + ptr.skipWhitespace(); + String key = ptr.currentChar() == '"' ? ptr.parseQuotedString() : ptr.parseSimpleString(); + if (key.isEmpty()) { + throw new ParseException("empty keys are not allowed"); + } + ptr.expectChar(':'); + + compoundTag.put(key, parseAnything(decrementMaxDepth(maxDepth))); + + if (!ptr.nextArrayElement()) { + break; + } + } + ptr.expectChar('}'); + return compoundTag; + } + + private ListTag parseListTag(int maxDepth) throws ParseException { + ptr.expectChar('['); + ptr.skipWhitespace(); + ListTag list = ListTag.createUnchecked(EndTag.class); + while (ptr.currentChar() != ']') { + Tag element = parseAnything(decrementMaxDepth(maxDepth)); + list.addUnchecked(element); + if (!ptr.nextArrayElement()) { + break; + } + } + ptr.expectChar(']'); + return list; + } + + private ArrayTag parseNumArray() throws ParseException { + ptr.expectChar('['); + char arrayType = ptr.next(); + ptr.expectChar(';'); + ptr.skipWhitespace(); + switch (arrayType) { + case 'B': + return parseByteArrayTag(); + case 'I': + return parseIntArrayTag(); + case 'L': + return parseLongArrayTag(); + } + throw new ParseException("invalid array type '" + arrayType + "'"); + } + + private ByteArrayTag parseByteArrayTag() throws ParseException { + List byteList = new ArrayList<>(); + while (ptr.currentChar() != ']') { + String s = ptr.parseSimpleString(); + ptr.skipWhitespace(); + if (BYTE_PATTERN.matcher(s).matches()) { + byteList.add(Byte.parseByte(s.substring(0, s.length() - 1))); + } else { + throw ptr.parseException("invalid byte literal in ByteArrayTag: \"" + s + "\""); + } + if (!ptr.nextArrayElement()) { + break; + } + } + ptr.expectChar(']'); + byte[] bytes = new byte[byteList.size()]; + for (int i = 0; i < byteList.size(); i++) { + bytes[i] = byteList.get(i); + } + return new ByteArrayTag(bytes); + } + + private IntArrayTag parseIntArrayTag() throws ParseException { + List intList = new ArrayList<>(); + while (ptr.currentChar() != ']') { + String s = ptr.parseSimpleString(); + ptr.skipWhitespace(); + if (INT_PATTERN.matcher(s).matches()) { + intList.add(Integer.parseInt(s.substring(0, s.length() - 1))); + } else { + throw ptr.parseException("invalid int literal in IntArrayTag: \"" + s + "\""); + } + if (!ptr.nextArrayElement()) { + break; + } + } + ptr.expectChar(']'); + return new IntArrayTag(intList.stream().mapToInt(i -> i).toArray()); + } + + private LongArrayTag parseLongArrayTag() throws ParseException { + List longList = new ArrayList<>(); + while (ptr.currentChar() != ']') { + String s = ptr.parseSimpleString(); + ptr.skipWhitespace(); + if (LONG_PATTERN.matcher(s).matches()) { + longList.add(Long.parseLong(s.substring(0, s.length() - 1))); + } else { + throw ptr.parseException("invalid long literal in LongArrayTag: \"" + s + "\""); + } + if (!ptr.nextArrayElement()) { + break; + } + } + ptr.expectChar(']'); + return new LongArrayTag(longList.stream().mapToLong(l -> l).toArray()); + } +} diff --git a/src/main/java/net/querz/nbt/io/MSONSerializer.java b/src/main/java/net/querz/nbt/io/MSONSerializer.java index 814dbf2b..ad35f7b4 100644 --- a/src/main/java/net/querz/nbt/io/MSONSerializer.java +++ b/src/main/java/net/querz/nbt/io/MSONSerializer.java @@ -1,9 +1,28 @@ package net.querz.nbt.io; -public class MSONSerializer implements Serializer { +import net.querz.io.StringSerializer; +import net.querz.nbt.Tag; +import java.io.IOException; +import java.io.Writer; + +public class MSONSerializer implements StringSerializer> { + + private final boolean pretty; + + public MSONSerializer() { + this(false); + } + + public MSONSerializer(boolean pretty) { + this.pretty = pretty; + } @Override - public String write() { - return null; + public void toWriter(Tag tag, Writer writer) throws IOException { + toWriter(tag, writer, Tag.DEFAULT_MAX_DEPTH); + } + + public void toWriter(Tag tag, Writer writer, int maxDepth) throws IOException { + MSONWriter.write(tag, writer, maxDepth); } } diff --git a/src/main/java/net/querz/nbt/io/MSONWriter.java b/src/main/java/net/querz/nbt/io/MSONWriter.java new file mode 100644 index 00000000..ffde0923 --- /dev/null +++ b/src/main/java/net/querz/nbt/io/MSONWriter.java @@ -0,0 +1,130 @@ +package net.querz.nbt.io; + +import net.querz.io.MaxDepthIO; +import net.querz.nbt.ByteArrayTag; +import net.querz.nbt.ByteTag; +import net.querz.nbt.CompoundTag; +import net.querz.nbt.DoubleTag; +import net.querz.nbt.EndTag; +import net.querz.nbt.FloatTag; +import net.querz.nbt.IntArrayTag; +import net.querz.nbt.IntTag; +import net.querz.nbt.ListTag; +import net.querz.nbt.LongArrayTag; +import net.querz.nbt.LongTag; +import net.querz.nbt.ShortTag; +import net.querz.nbt.StringTag; +import net.querz.nbt.Tag; +import java.io.IOException; +import java.io.Writer; +import java.lang.reflect.Array; +import java.util.Map; +import java.util.regex.Pattern; + +/** + * MSONWriter creates an MSON String. + * This does not support custom tags, because + * + * */ +public final class MSONWriter implements MaxDepthIO { + + private static final Pattern NON_QUOTE_PATTERN = Pattern.compile("[a-zA-Z0-9_.+\\-]+"); + + private Writer writer; + + private MSONWriter(Writer writer) { + this.writer = writer; + } + + public static void write(Tag tag, Writer writer, int maxDepth) throws IOException { + new MSONWriter(writer).writeAnything(tag, maxDepth); + } + + public static void write(Tag tag, Writer writer) throws IOException { + write(tag, writer, Tag.DEFAULT_MAX_DEPTH); + } + + private void writeAnything(Tag tag, int maxDepth) throws IOException { + switch (tag.getID()) { + case EndTag.ID: + //do nothing + break; + case ByteTag.ID: + writer.append(Byte.toString(((ByteTag) tag).asByte())).write('b'); + break; + case ShortTag.ID: + writer.append(Short.toString(((ShortTag) tag).asShort())).write('s'); + break; + case IntTag.ID: + writer.write(Integer.toString(((IntTag) tag).asInt())); + break; + case LongTag.ID: + writer.append(Long.toString(((LongTag) tag).asLong())).write('l'); + break; + case FloatTag.ID: + writer.append(Float.toString(((FloatTag) tag).asFloat())).write('f'); + break; + case DoubleTag.ID: + writer.append(Double.toString(((DoubleTag) tag).asDouble())).write('d'); + break; + case ByteArrayTag.ID: + writeArray(((ByteArrayTag) tag).getValue(), ((ByteArrayTag) tag).length(), "B", "b"); + break; + case StringTag.ID: + writer.write(escapeString(((StringTag) tag).getValue())); + break; + case ListTag.ID: + writer.write('['); + for (int i = 0; i < ((ListTag) tag).size(); i++) { + writer.write(i == 0 ? "" : ","); + writeAnything(((ListTag) tag).get(i), decrementMaxDepth(maxDepth)); + } + writer.write(']'); + break; + case CompoundTag.ID: + writer.write('{'); + boolean first = true; + for (Map.Entry> entry : (CompoundTag) tag) { + writer.write(first ? "" : ","); + writer.append(escapeString(entry.getKey())).write(':'); + writeAnything(entry.getValue(), decrementMaxDepth(maxDepth)); + first = false; + } + writer.write('}'); + break; + case IntArrayTag.ID: + writeArray(((IntArrayTag) tag).getValue(), ((IntArrayTag) tag).length(), "I", ""); + break; + case LongArrayTag.ID: + writeArray(((LongArrayTag) tag).getValue(), ((LongArrayTag) tag).length(), "L", "l"); + break; + default: + throw new IOException("unknown tag with id \"" + tag.getID() + "\""); + } + } + + private void writeArray(Object array, int length, String prefix, String suffix) throws IOException { + writer.append('[').append(prefix).write(';'); + for (int i = 0; i < length; i++) { + writer.append(i == 0 ? "" : ",").append(Integer.toString((int) Array.get(array, i))).write(suffix); + } + writer.write(']'); + } + + public static String escapeString(String s) { + if (!NON_QUOTE_PATTERN.matcher(s).matches()) { + StringBuilder sb = new StringBuilder(); + sb.append('"'); + for (int i = 0; i < s.length(); i++) { + char c = s.charAt(i); + if (c == '\\' || c == '"') { + sb.append('\\'); + } + sb.append(c); + } + sb.append('"'); + return sb.toString(); + } + return s; + } +} diff --git a/src/main/java/net/querz/nbt/io/NBTDeserializer.java b/src/main/java/net/querz/nbt/io/NBTDeserializer.java new file mode 100644 index 00000000..97abaedf --- /dev/null +++ b/src/main/java/net/querz/nbt/io/NBTDeserializer.java @@ -0,0 +1,31 @@ +package net.querz.nbt.io; + +import net.querz.io.Deserializer; +import net.querz.nbt.Tag; +import java.io.IOException; +import java.io.InputStream; +import java.util.zip.GZIPInputStream; + +public class NBTDeserializer implements Deserializer { + + private boolean compressed; + + public NBTDeserializer() { + this(true); + } + + public NBTDeserializer(boolean compressed) { + this.compressed = compressed; + } + + @Override + public NamedTag fromStream(InputStream stream) throws IOException { + NBTInputStream nbtIn; + if (compressed) { + nbtIn = new NBTInputStream(new GZIPInputStream(stream)); + } else { + nbtIn = new NBTInputStream(stream); + } + return nbtIn.readTag(Tag.DEFAULT_MAX_DEPTH); + } +} diff --git a/src/main/java/net/querz/nbt/io/NBTInputStream.java b/src/main/java/net/querz/nbt/io/NBTInputStream.java new file mode 100644 index 00000000..a97cbae8 --- /dev/null +++ b/src/main/java/net/querz/nbt/io/NBTInputStream.java @@ -0,0 +1,156 @@ +package net.querz.nbt.io; + +import net.querz.io.ExceptionBiFunction; +import net.querz.io.MaxDepthIO; +import net.querz.nbt.ByteArrayTag; +import net.querz.nbt.ByteTag; +import net.querz.nbt.CompoundTag; +import net.querz.nbt.DoubleTag; +import net.querz.nbt.EndTag; +import net.querz.nbt.FloatTag; +import net.querz.nbt.IntArrayTag; +import net.querz.nbt.IntTag; +import net.querz.nbt.ListTag; +import net.querz.nbt.LongArrayTag; +import net.querz.nbt.LongTag; +import net.querz.nbt.ShortTag; +import net.querz.nbt.StringTag; +import net.querz.nbt.Tag; +import java.io.DataInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.HashMap; +import java.util.Map; + +public class NBTInputStream extends DataInputStream implements MaxDepthIO { + + private static Map, IOException>> readers = new HashMap<>(); + private static Map> idClassMapping = new HashMap<>(); + + static { + put(EndTag.ID, (i, d) -> EndTag.INSTANCE, EndTag.class); + put(ByteTag.ID, (i, d) -> readByte(i), ByteTag.class); + put(ShortTag.ID, (i, d) -> readShort(i), ShortTag.class); + put(IntTag.ID, (i, d) -> readInt(i), IntTag.class); + put(LongTag.ID, (i, d) -> readLong(i), LongTag.class); + put(FloatTag.ID, (i, d) -> readFloat(i), FloatTag.class); + put(DoubleTag.ID, (i, d) -> readDouble(i), DoubleTag.class); + put(ByteArrayTag.ID, (i, d) -> readByteArray(i), ByteArrayTag.class); + put(StringTag.ID, (i, d) -> readString(i), StringTag.class); + put(ListTag.ID, NBTInputStream::readListTag, ListTag.class); + put(CompoundTag.ID, NBTInputStream::readCompound, CompoundTag.class); + put(IntArrayTag.ID, (i, d) -> readIntArray(i), IntArrayTag.class); + put(LongArrayTag.ID, (i, d) -> readLongArray(i), LongArrayTag.class); + } + + private static void put(byte id, ExceptionBiFunction, IOException> reader, Class clazz) { + readers.put(id, reader); + idClassMapping.put(id, clazz); + } + + public NBTInputStream(InputStream in) { + super(in); + } + + public NamedTag readTag(int maxDepth) throws IOException { + byte id = readByte(); + return new NamedTag(readUTF(), readTag(id, maxDepth)); + } + + public Tag readRawTag(int maxDepth) throws IOException { + byte id = readByte(); + return readTag(id, maxDepth); + } + + private Tag readTag(byte type, int maxDepth) throws IOException { + ExceptionBiFunction, IOException> f; + if ((f = readers.get(type)) == null) { + throw new IOException("invalid tag id \"" + type + "\""); + } + return f.accept(this, maxDepth); + } + + static > void registerCustomTag(byte id, ExceptionBiFunction f) { + if (readers.containsKey(id)) { + throw new IllegalArgumentException("custom tag already registered"); + } + readers.put(id, f); + } + + static void unregisterCustomTag(byte id) { + readers.remove(id); + } + + private static ByteTag readByte(NBTInputStream in) throws IOException { + return new ByteTag(in.readByte()); + } + + private static ShortTag readShort(NBTInputStream in) throws IOException { + return new ShortTag(in.readShort()); + } + + private static IntTag readInt(NBTInputStream in) throws IOException { + return new IntTag(in.readInt()); + } + + private static LongTag readLong(NBTInputStream in) throws IOException { + return new LongTag(in.readLong()); + } + + private static FloatTag readFloat(NBTInputStream in) throws IOException { + return new FloatTag(in.readFloat()); + } + + private static DoubleTag readDouble(NBTInputStream in) throws IOException { + return new DoubleTag(in.readDouble()); + } + + private static StringTag readString(NBTInputStream in) throws IOException { + return new StringTag(in.readUTF()); + } + + private static ByteArrayTag readByteArray(NBTInputStream in) throws IOException { + ByteArrayTag bat = new ByteArrayTag(new byte[in.readInt()]); + in.readFully(bat.getValue()); + return bat; + } + + private static IntArrayTag readIntArray(NBTInputStream in) throws IOException { + IntArrayTag iat = new IntArrayTag(new int[in.readInt()]); + for (int i = 0; i < iat.length(); i++) { + iat.getValue()[i] = in.readInt(); + } + return iat; + } + + private static LongArrayTag readLongArray(NBTInputStream in) throws IOException { + LongArrayTag iat = new LongArrayTag(new long[in.readInt()]); + for (int i = 0; i < iat.length(); i++) { + iat.getValue()[i] = in.readLong(); + } + return iat; + } + + private static ListTag readListTag(NBTInputStream in, int maxDepth) throws IOException { + byte listType = in.readByte(); + ListTag list = ListTag.createUnchecked(idClassMapping.get(listType)); + int length = in.readInt(); + if (length < 0) { + length = 0; + } + for (int i = 0; i < length; i++) { + list.addUnchecked(in.readTag(listType, in.decrementMaxDepth(maxDepth))); + } + return list; + } + + private static CompoundTag readCompound(NBTInputStream in, int maxDepth) throws IOException { + CompoundTag comp = new CompoundTag(); + for (int id = in.readByte() & 0xFF; id != 0; id = in.readByte() & 0xFF) { + String key = in.readUTF(); + Tag element = in.readTag((byte) id, in.decrementMaxDepth(maxDepth)); + comp.put(key, element); + } + return comp; + } +} diff --git a/src/main/java/net/querz/nbt/io/NBTOutputStream.java b/src/main/java/net/querz/nbt/io/NBTOutputStream.java new file mode 100644 index 00000000..00f8af88 --- /dev/null +++ b/src/main/java/net/querz/nbt/io/NBTOutputStream.java @@ -0,0 +1,164 @@ +package net.querz.nbt.io; + +import net.querz.io.ExceptionTriConsumer; +import net.querz.io.MaxDepthIO; +import net.querz.nbt.ByteArrayTag; +import net.querz.nbt.ByteTag; +import net.querz.nbt.CompoundTag; +import net.querz.nbt.DoubleTag; +import net.querz.nbt.EndTag; +import net.querz.nbt.FloatTag; +import net.querz.nbt.IntArrayTag; +import net.querz.nbt.IntTag; +import net.querz.nbt.ListTag; +import net.querz.nbt.LongArrayTag; +import net.querz.nbt.LongTag; +import net.querz.nbt.ShortTag; +import net.querz.nbt.StringTag; +import net.querz.nbt.Tag; +import java.io.DataOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.util.HashMap; +import java.util.Map; + +public class NBTOutputStream extends DataOutputStream implements MaxDepthIO { + + private static Map, Integer, IOException>> writers = new HashMap<>(); + private static Map, Byte> classIdMapping = new HashMap<>(); + + static { + put(EndTag.ID, (o, t, d) -> {}, EndTag.class); + put(ByteTag.ID, (o, t, d) -> writeByte(o, t), ByteTag.class); + put(ShortTag.ID, (o, t, d) -> writeShort(o, t), ShortTag.class); + put(IntTag.ID, (o, t, d) -> writeInt(o, t), IntTag.class); + put(LongTag.ID, (o, t, d) -> writeLong(o, t), LongTag.class); + put(FloatTag.ID, (o, t, d) -> writeFloat(o, t), FloatTag.class); + put(DoubleTag.ID, (o, t, d) -> writeDouble(o, t), DoubleTag.class); + put(ByteArrayTag.ID, (o, t, d) -> writeByteArray(o, t), ByteArrayTag.class); + put(StringTag.ID, (o, t, d) -> writeString(o, t), StringTag.class); + put(ListTag.ID, NBTOutputStream::writeList, ListTag.class); + put(CompoundTag.ID, NBTOutputStream::writeCompound, CompoundTag.class); + put(IntArrayTag.ID, (o, t, d) -> writeIntArray(o, t), IntArrayTag.class); + put(LongArrayTag.ID, (o, t, d) -> writeLongArray(o, t), LongArrayTag.class); + } + + private static void put(byte id, ExceptionTriConsumer, Integer, IOException> f, Class clazz) { + writers.put(id, f); + classIdMapping.put(clazz, id); + } + + public NBTOutputStream(OutputStream out) { + super(out); + } + + public void writeTag(NamedTag tag, int maxDepth) throws IOException { + writeByte(tag.getTag().getID()); + if (tag.getTag().getID() != 0) { + writeUTF(tag.getName() == null ? "" : tag.getName()); + } + writeRawTag(tag.getTag(), maxDepth); + } + + public void writeTag(Tag tag, int maxDepth) throws IOException { + writeByte(tag.getID()); + if (tag.getID() != 0) { + writeUTF(""); + } + writeRawTag(tag, maxDepth); + } + + public void writeRawTag(Tag tag, int maxDepth) throws IOException { + ExceptionTriConsumer, Integer, IOException> f; + if ((f = writers.get(tag.getID())) == null) { + throw new IOException("invalid tag \"" + tag.getID() + "\""); + } + f.accept(this, tag, maxDepth); + } + + static void registerCustomTag(byte id, ExceptionTriConsumer, Integer, IOException> f, Class clazz) { + if (writers.containsKey(id)) { + throw new IllegalArgumentException("custom tag already registered"); + } + put(id, f, clazz); + } + + static void unregisterCustomTag(byte id) { + writers.remove(id); + } + + static byte idFromClass(Class clazz) { + Byte id = classIdMapping.get(clazz); + if (id == null) { + throw new IllegalArgumentException("unknown Tag class " + clazz.getName()); + } + return id; + } + + private static void writeByte(NBTOutputStream out, Tag tag) throws IOException { + out.writeByte(((ByteTag) tag).asByte()); + } + + private static void writeShort(NBTOutputStream out, Tag tag) throws IOException { + out.writeShort(((ShortTag) tag).asShort()); + } + + private static void writeInt(NBTOutputStream out, Tag tag) throws IOException { + out.writeInt(((IntTag) tag).asInt()); + } + + private static void writeLong(NBTOutputStream out, Tag tag) throws IOException { + out.writeLong(((LongTag) tag).asLong()); + } + + private static void writeFloat(NBTOutputStream out, Tag tag) throws IOException { + out.writeFloat(((FloatTag) tag).asFloat()); + } + + private static void writeDouble(NBTOutputStream out, Tag tag) throws IOException { + out.writeDouble(((DoubleTag) tag).asDouble()); + } + + private static void writeString(NBTOutputStream out, Tag tag) throws IOException { + out.writeUTF(((StringTag) tag).getValue()); + } + + private static void writeByteArray(NBTOutputStream out, Tag tag) throws IOException { + out.writeInt(((ByteArrayTag) tag).length()); + out.write(((ByteArrayTag) tag).getValue()); + } + + private static void writeIntArray(NBTOutputStream out, Tag tag) throws IOException { + out.writeInt(((IntArrayTag) tag).length()); + for (int i : ((IntArrayTag) tag).getValue()) { + out.writeInt(i); + } + } + + private static void writeLongArray(NBTOutputStream out, Tag tag) throws IOException { + out.writeInt(((LongArrayTag) tag).length()); + for (long l : ((LongArrayTag) tag).getValue()) { + out.writeLong(l); + } + } + + private static void writeList(NBTOutputStream out, Tag tag, int maxDepth) throws IOException { + out.writeByte(idFromClass(((ListTag) tag).getTypeClass())); + out.writeInt(((ListTag) tag).size()); + for (Tag t : ((ListTag) tag)) { + out.writeRawTag(t, out.decrementMaxDepth(maxDepth)); + } + } + + private static void writeCompound(NBTOutputStream out, Tag tag, int maxDepth) throws IOException { + for (Map.Entry> entry : (CompoundTag) tag) { + if (entry.getValue().getID() == 0) { + throw new IOException("end tag not allowed"); + } + out.writeByte(entry.getValue().getID()); + out.writeUTF(entry.getKey()); + out.writeRawTag(entry.getValue(), out.decrementMaxDepth(maxDepth)); + } + out.writeByte(0); + } +} diff --git a/src/main/java/net/querz/nbt/io/NBTSerializer.java b/src/main/java/net/querz/nbt/io/NBTSerializer.java new file mode 100644 index 00000000..f35998b4 --- /dev/null +++ b/src/main/java/net/querz/nbt/io/NBTSerializer.java @@ -0,0 +1,31 @@ +package net.querz.nbt.io; + +import net.querz.io.Serializer; +import net.querz.nbt.Tag; +import java.io.IOException; +import java.io.OutputStream; +import java.util.zip.GZIPOutputStream; + +public class NBTSerializer implements Serializer { + + private boolean compressed; + + public NBTSerializer() { + this(true); + } + + public NBTSerializer(boolean compressed) { + this.compressed = compressed; + } + + @Override + public void toStream(NamedTag object, OutputStream out) throws IOException { + NBTOutputStream nbtOut; + if (compressed) { + nbtOut = new NBTOutputStream(new GZIPOutputStream(out)); + } else { + nbtOut = new NBTOutputStream(out); + } + nbtOut.writeTag(object, Tag.DEFAULT_MAX_DEPTH); + } +} diff --git a/src/main/java/net/querz/nbt/io/NBTUtil.java b/src/main/java/net/querz/nbt/io/NBTUtil.java new file mode 100644 index 00000000..819b91b3 --- /dev/null +++ b/src/main/java/net/querz/nbt/io/NBTUtil.java @@ -0,0 +1,119 @@ +package net.querz.nbt.io; + +import net.querz.io.ExceptionBiFunction; +import net.querz.io.ExceptionTriConsumer; +import net.querz.nbt.Tag; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.PushbackInputStream; +import java.util.zip.GZIPInputStream; + +public final class NBTUtil { + + private NBTUtil() {} + + @SuppressWarnings("unchecked") + public static > void registerCustomTag( + int id, + ExceptionTriConsumer serializer, + ExceptionBiFunction deserializer, + Class clazz) { + checkID(id); + NBTInputStream.registerCustomTag((byte) id, deserializer); + NBTOutputStream.registerCustomTag((byte) id, (ExceptionTriConsumer, Integer, IOException>) serializer, clazz); + } + + public static void unregisterCustomTag(int id) { + checkID(id); + NBTInputStream.unregisterCustomTag((byte) id); + NBTOutputStream.unregisterCustomTag((byte) id); + } + + private static void checkID(int id) { + if (id < 0) { + throw new IllegalArgumentException("id cannot be negative"); + } + if (id <= 12) { + throw new IllegalArgumentException("cannot change default tags"); + } + if (id > Byte.MAX_VALUE) { + throw new IllegalArgumentException("id out of bounds: " + id); + } + } + + public static void write(NamedTag tag, File file, boolean compressed) throws IOException { + try (FileOutputStream fos = new FileOutputStream(file)) { + new NBTSerializer(compressed).toStream(tag, fos); + } + } + + public static void write(NamedTag tag, String file, boolean compressed) throws IOException { + write(tag, new File(file), compressed); + } + + public static void write(NamedTag tag, File file) throws IOException { + write(tag, file, true); + } + + public static void write(NamedTag tag, String file) throws IOException { + write(tag, new File(file), true); + } + + public static void write(Tag tag, File file, boolean compressed) throws IOException { + write(new NamedTag(null, tag), file, compressed); + } + + public static void write(Tag tag, String file, boolean compressed) throws IOException { + write(new NamedTag(null, tag), new File(file), compressed); + } + + public static void write(Tag tag, File file) throws IOException { + write(new NamedTag(null, tag), file, true); + } + + public static void write(Tag tag, String file) throws IOException { + write(new NamedTag(null, tag), new File(file), true); + } + + public static NamedTag read(File file, boolean compressed) throws IOException { + try (FileInputStream fis = new FileInputStream(file)) { + return new NBTDeserializer(compressed).fromStream(fis); + } + } + + public static NamedTag read(String file, boolean compressed) throws IOException { + return read(new File(file), compressed); + } + + public static NamedTag read(File file) throws IOException { + try (FileInputStream fis = new FileInputStream(file)) { + return new NBTDeserializer(false).fromStream(detectDecompression(fis)); + } + } + + public static NamedTag read(String file) throws IOException { + return read(new File(file)); + } + + private static InputStream detectDecompression(InputStream is) throws IOException { + PushbackInputStream pbis = new PushbackInputStream(is, 2); + int signature = (pbis.read() & 0xFF) + (pbis.read() << 8); + pbis.unread(signature >> 8); + pbis.unread(signature & 0xFF); + if (signature == GZIPInputStream.GZIP_MAGIC) { + return new GZIPInputStream(pbis); + } + return pbis; + } + + public static String toMSONString(Tag tag) throws IOException { + return new MSONSerializer().toString(tag); + } + + public static Tag fromMSONString(String string) throws IOException { + return new MSONDeserializer().fromString(string); + } +} diff --git a/src/main/java/net/querz/nbt/io/NamedTag.java b/src/main/java/net/querz/nbt/io/NamedTag.java new file mode 100644 index 00000000..c6f3f9ec --- /dev/null +++ b/src/main/java/net/querz/nbt/io/NamedTag.java @@ -0,0 +1,30 @@ +package net.querz.nbt.io; + +import net.querz.nbt.Tag; + +public class NamedTag { + + private String name; + private Tag tag; + + public NamedTag(String name, Tag tag) { + this.name = name; + this.tag = tag; + } + + public void setName(String name) { + this.name = name; + } + + public void setTag(Tag tag) { + this.tag = tag; + } + + public String getName() { + return name; + } + + public Tag getTag() { + return tag; + } +} diff --git a/src/main/java/net/querz/nbt/io/ParseException.java b/src/main/java/net/querz/nbt/io/ParseException.java index e2bd61b1..c62e0610 100644 --- a/src/main/java/net/querz/nbt/io/ParseException.java +++ b/src/main/java/net/querz/nbt/io/ParseException.java @@ -1,6 +1,8 @@ package net.querz.nbt.io; -public class ParseException extends RuntimeException { +import java.io.IOException; + +public class ParseException extends IOException { public ParseException(String msg) { super(msg); diff --git a/src/main/java/net/querz/nbt/io/Serializer.java b/src/main/java/net/querz/nbt/io/Serializer.java deleted file mode 100644 index 84d00121..00000000 --- a/src/main/java/net/querz/nbt/io/Serializer.java +++ /dev/null @@ -1,6 +0,0 @@ -package net.querz.nbt.io; - -public interface Serializer { - - String write(); -} diff --git a/src/main/java/net/querz/nbt/io/StringPointer.java b/src/main/java/net/querz/nbt/io/StringPointer.java index 82fb6734..ed6dc211 100644 --- a/src/main/java/net/querz/nbt/io/StringPointer.java +++ b/src/main/java/net/querz/nbt/io/StringPointer.java @@ -1,5 +1,7 @@ package net.querz.nbt.io; +import net.querz.nbt.io.ParseException; + public class StringPointer { private String value; @@ -17,7 +19,7 @@ public String parseSimpleString() { return value.substring(oldIndex, index); } - public String parseQuotedString() { + public String parseQuotedString() throws ParseException { int oldIndex = ++index; //ignore beginning quotes StringBuilder sb = null; boolean escape = false; @@ -58,7 +60,7 @@ public boolean nextArrayElement() { return false; } - public void expectChar(char c) { + public void expectChar(char c) throws ParseException { skipWhitespace(); boolean hasNext = hasNext(); if (hasNext && currentChar() == c) { @@ -108,7 +110,7 @@ private static boolean isSimpleChar(char c) { || c == '_'; } - private ParseException parseException(String msg) { + public ParseException parseException(String msg) { return new ParseException(msg, value, index); } } diff --git a/src/main/java/net/querz/nbt/mca/Chunk.java b/src/main/java/net/querz/nbt/mca/Chunk.java index f299a107..ed30159b 100644 --- a/src/main/java/net/querz/nbt/mca/Chunk.java +++ b/src/main/java/net/querz/nbt/mca/Chunk.java @@ -2,9 +2,15 @@ import net.querz.nbt.CompoundTag; import net.querz.nbt.ListTag; -import net.querz.nbt.Tag; - -import java.io.*; +import net.querz.nbt.io.NamedTag; +import net.querz.nbt.io.NBTDeserializer; +import net.querz.nbt.io.NBTSerializer; +import java.io.BufferedInputStream; +import java.io.BufferedOutputStream; +import java.io.ByteArrayOutputStream; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.RandomAccessFile; public class Chunk { @@ -36,10 +42,6 @@ public class Chunk { this.lastMCAUpdate = lastMCAUpdate; } - /** - * Create a new chunk based on raw base data from a region file. - * @param data The raw base data to be used. - */ public Chunk(CompoundTag data) { this.data = data; initReferences(); @@ -57,7 +59,7 @@ private void initReferences() { this.inhabitedTime = level.getLong("InhabitedTime"); this.lastUpdate = level.getLong("LastUpdate"); this.biomes = level.getIntArray("Biomes"); - this.heightMaps = level.getCompoundTag("Heightmaps"); + this.heightMaps = level.getCompoundTag("HeightMaps"); this.carvingMasks = level.getCompoundTag("CarvingMasks"); this.entities = level.containsKey("Entities") ? level.getListTag("Entities").asCompoundTagList() : null; this.tileEntities = level.containsKey("TileEntities") ? level.getListTag("TileEntities").asCompoundTagList() : null; @@ -71,67 +73,39 @@ private void initReferences() { this.structures = level.getCompoundTag("Structures"); if (level.containsKey("Sections")) { for (CompoundTag section : level.getListTag("Sections").asCompoundTagList()) { - int sectionIndex = section.getByte("Y"); - if (sectionIndex > 15 || sectionIndex < 0) { - continue; - } - Section newSection = new Section(section); - if (newSection.isEmpty()) { - continue; - } - this.sections[sectionIndex] = newSection; + this.sections[section.getByte("Y")] = new Section(section); } } } - /** - * Serializes this chunk to a RandomAccessFile. - * @param raf The RandomAccessFile to be written to. - * @param xPos The x-coordinate of the chunk. - * @param zPos The z-coodrinate of the chunk. - * @return The amount of bytes written to the RandomAccessFile. - * @throws IOException When something went wrong during writing. - */ 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, Tag.DEFAULT_MAX_DEPTH); + try (BufferedOutputStream nbtOut = new BufferedOutputStream(CompressionType.ZLIB.compress(baos))) { + new NBTSerializer(false).toStream(new NamedTag(null, updateHandle(xPos, zPos)), nbtOut); } byte[] rawData = baos.toByteArray(); - raf.writeInt(rawData.length + 1); + raf.writeInt(rawData.length); raf.writeByte(CompressionType.ZLIB.getID()); raf.write(rawData); return rawData.length + 5; } - /** - * Reads chunk data from a RandomAccessFile. The RandomAccessFile must already be at the correct position. - * @param raf The RandomAccessFile to read the chunk data from. - * @throws IOException When something went wrong during reading. - */ public void deserialize(RandomAccessFile raf) throws IOException { byte compressionTypeByte = raf.readByte(); CompressionType compressionType = CompressionType.getFromID(compressionTypeByte); if (compressionType == null) { throw new IOException("invalid compression type " + compressionTypeByte); } - DataInputStream dis = new DataInputStream(new BufferedInputStream(compressionType.decompress(new FileInputStream(raf.getFD())))); - Tag tag = Tag.deserialize(dis, Tag.DEFAULT_MAX_DEPTH); - if (tag instanceof CompoundTag) { - data = (CompoundTag) tag; + BufferedInputStream dis = new BufferedInputStream(compressionType.decompress(new FileInputStream(raf.getFD()))); + NamedTag tag = new NBTDeserializer(false).fromStream(dis); + if (tag != null && tag.getTag() instanceof CompoundTag) { + data = (CompoundTag) tag.getTag(); initReferences(); } else { throw new IOException("invalid data tag: " + (tag == null ? "null" : tag.getClass().getName())); } } - /** - * Fetches a biome id at a specific block column in this chunk. - * The coordinates can be absolute coordinates or relative to the region or chunk. - * @param blockX The x-coordinate of the block column. - * @param blockZ The z-coordinate of the block column. - * @return The biome id or -1 if the biomes are not correctly initialized. - */ public int getBiomeAt(int blockX, int blockZ) { if (biomes == null || biomes.length != 256) { return -1; @@ -139,14 +113,6 @@ public int getBiomeAt(int blockX, int blockZ) { return biomes[getBlockIndex(blockX, blockZ)]; } - /** - * Sets a biome id at a specific block column. - * The coordinates can be absolute coordinates or relative to the region or chunk. - * @param blockX The x-coordinate of the block column. - * @param blockZ The z-coordinate of the block column. - * @param biomeID The biome id to be set. - * When set to a negative number, Minecraft will replace it with the block column's default biome. - */ public void setBiomeAt(int blockX, int blockZ, int biomeID) { if (biomes == null || biomes.length != 256) { biomes = new int[256]; @@ -157,14 +123,6 @@ public void setBiomeAt(int blockX, int blockZ, int biomeID) { biomes[getBlockIndex(blockX, blockZ)] = biomeID; } - /** - * Fetches the block state at a specific block location in this chunk. - * The block coordinate can be absolute or relative to the region or chunk. - * @param blockX The x-coordinate of the block. - * @param blockY The y-coordinate of the block. - * @param blockZ The z-coordinate of the block. - * @return The block state or null if a section at the location does not exist. - */ public CompoundTag getBlockStateAt(int blockX, int blockY, int blockZ) { Section section = sections[MCAUtil.blockToChunk(blockY)]; if (section == null) { @@ -173,17 +131,6 @@ public CompoundTag getBlockStateAt(int blockX, int blockY, int blockZ) { return section.getBlockStateAt(blockX, blockY, blockZ); } - /** - * Sets a block state at a specific location. - * The block coordinates can be absolute or relative to the region or chunk. - * @param blockX The x-coordinate of the block. - * @param blockY The y-coordinate of the block. - * @param blockZ The z-coordinate of the block. - * @param state The block state to be set. - * @param cleanup When true, it will cleanup all palettes of this chunk. - * This option should only be used moderately to avoid unnecessary recalculation of the palette indices. - * Recalculating the Palette should only be executed once right before saving the Chunk to file. - */ public void setBlockStateAt(int blockX, int blockY, int blockZ, CompoundTag state, boolean cleanup) { int sectionIndex = MCAUtil.blockToChunk(blockY); Section section = sections[sectionIndex]; @@ -193,113 +140,58 @@ public void setBlockStateAt(int blockX, int blockY, int blockZ, CompoundTag stat section.setBlockStateAt(blockX, blockY, blockZ, state, cleanup); } - /** - * @return The DataVersion of this chunk. - */ public int getDataVersion() { return dataVersion; } - /** - * Sets the DataVersion of this chunk. This does not check if the data of this chunk conforms - * to that DataVersion, that is the responsibility of the developer. - * @param dataVersion The DataVersion to be set. - */ public void setDataVersion(int dataVersion) { this.dataVersion = dataVersion; } - /** - * @return The timestamp when this region file was last updated in seconds since 1970-01-01. - */ public int getLastMCAUpdate() { return lastMCAUpdate; } - /** - * Sets the timestamp when this region file was last updated in seconds since 1970-01-01. - * @param lastMCAUpdate The time in seconds since 1970-01-01. - */ public void setLastMCAUpdate(int lastMCAUpdate) { this.lastMCAUpdate = lastMCAUpdate; } - /** - * @return The generation station of this chunk. - */ public String getStatus() { return status; } - /** - * Sets the generation status of this chunk. - * @param status The generation status of this chunk. - */ public void setStatus(String status) { this.status = status; } - /** - * Fetches the section at the given y-coordinate. - * @param sectionY The y-coordinate of the section in this chunk ranging from 0 to 15. - * @return The Section. - */ public Section getSection(int sectionY) { return sections[sectionY]; } - /** - * Sets a section at a givesn y-coordinate - * @param sectionY The y-coordinate of the section in this chunk ranging from 0 to 15. - * @param section The section to be set. - */ public void setSection(int sectionY, Section section) { sections[sectionY] = section; } - /** - * @return The timestamp when this chunk was last updated as a UNIX timestamp. - */ public long getLastUpdate() { return lastUpdate; } - /** - * Sets the time when this chunk was last updated as a UNIX timestamp. - * @param lastUpdate The UNIX timestamp. - */ public void setLastUpdate(long lastUpdate) { this.lastUpdate = lastUpdate; } - /** - * @return The cumulative amount of time players have spent in this chunk in ticks. - */ public long getInhabitedTime() { return inhabitedTime; } - /** - * Sets the cumulative amount of time players have spent in this chunk in ticks. - * @param inhabitedTime The time in ticks. - */ public void setInhabitedTime(long inhabitedTime) { this.inhabitedTime = inhabitedTime; } - /** - * @return A matrix of biome IDs for all block columns in this chunk. - */ public int[] getBiomes() { return biomes; } - /** - * Sets the biome IDs for this chunk. - * @param biomes The biome ID matrix of this chunk. Must have a length of 256. - * @throws IllegalArgumentException When the biome matrix does not have a length of 256 - * or is null - */ public void setBiomes(int[] biomes) { if (biomes != null && biomes.length != 256) { throw new IllegalArgumentException("biomes array must have a length of 256"); @@ -307,167 +199,90 @@ public void setBiomes(int[] biomes) { this.biomes = biomes; } - /** - * @return The height maps of this chunk. - */ public CompoundTag getHeightMaps() { return heightMaps; } - /** - * Sets the height maps of this chunk. - * @param heightMaps The height maps. - */ public void setHeightMaps(CompoundTag heightMaps) { this.heightMaps = heightMaps; } - /** - * @return The carving masks of this chunk. - */ public CompoundTag getCarvingMasks() { return carvingMasks; } - /** - * Sets the carving masks of this chunk. - * @param carvingMasks The carving masks. - */ public void setCarvingMasks(CompoundTag carvingMasks) { this.carvingMasks = carvingMasks; } - /** - * @return The entities of this chunk. - */ public ListTag getEntities() { return entities; } - /** - * Sets the entities of this chunk. - * @param entities The entities. - */ public void setEntities(ListTag entities) { this.entities = entities; } - /** - * @return The tile entities of this chunk. - */ public ListTag getTileEntities() { return tileEntities; } - /** - * Sets the tile entities of this chunk. - * @param tileEntities The tile entities of this chunk. - */ public void setTileEntities(ListTag tileEntities) { this.tileEntities = tileEntities; } - /** - * @return The tile ticks of this chunk. - */ public ListTag getTileTicks() { return tileTicks; } - /** - * Sets the tile ticks of this chunk. - * @param tileTicks Thee tile ticks. - */ public void setTileTicks(ListTag tileTicks) { this.tileTicks = tileTicks; } - /** - * @return The liquid ticks of this chunk. - */ public ListTag getLiquidTicks() { return liquidTicks; } - /** - * Sets the liquid ticks of this chunk. - * @param liquidTicks The liquid ticks. - */ public void setLiquidTicks(ListTag liquidTicks) { this.liquidTicks = liquidTicks; } - /** - * @return The light sources in this chunk. - */ public ListTag> getLights() { return lights; } - /** - * Sets the light sources in this chunk. - * @param lights The light sources. - */ public void setLights(ListTag> lights) { this.lights = lights; } - /** - * @return THe liquids to be ticked in this chunk. - */ public ListTag> getLiquidsToBeTicked() { return liquidsToBeTicked; } - /** - * Sets the liquids to be ticked in this chunk. - * @param liquidsToBeTicked The liquids to be ticked. - */ public void setLiquidsToBeTicked(ListTag> liquidsToBeTicked) { this.liquidsToBeTicked = liquidsToBeTicked; } - /** - * @return Stuff to be ticked in this chunk. - */ public ListTag> getToBeTicked() { return toBeTicked; } - /** - * Sets stuff to be ticked in this chunk. - * @param toBeTicked The stuff to be ticked. - */ public void setToBeTicked(ListTag> toBeTicked) { this.toBeTicked = toBeTicked; } - /** - * @return Things that are in post processing in this chunk. - */ public ListTag> getPostProcessing() { return postProcessing; } - /** - * Sets things to be post processed in this chunk. - * @param postProcessing The things to be post processed. - */ public void setPostProcessing(ListTag> postProcessing) { this.postProcessing = postProcessing; } - /** - * @return Data about structures in this chunk. - */ public CompoundTag getStructures() { return structures; } - /** - * Sets data about structures in this chunk. - * @param structures The data about structures. - */ public void setStructures(CompoundTag structures) { this.structures = structures; } @@ -501,7 +316,7 @@ public CompoundTag updateHandle(int xPos, int zPos) { level.putLong("LastUpdate", lastUpdate); level.putLong("InhabitedTime", inhabitedTime); if (biomes != null && biomes.length == 256) level.putIntArray("Biomes", biomes); - if (heightMaps != null) level.put("Heightmaps", heightMaps); + if (heightMaps != null) level.put("HeightMaps", heightMaps); if (carvingMasks != null) level.put("CarvingMasks", carvingMasks); if (entities != null) level.put("Entities", entities); if (tileEntities != null) level.put("TileEntities", tileEntities); diff --git a/src/test/java/net/querz/nbt/ByteArrayTagTest.java b/src/test/java/net/querz/nbt/ByteArrayTagTest.java index 8de2f590..ba6e3334 100644 --- a/src/test/java/net/querz/nbt/ByteArrayTagTest.java +++ b/src/test/java/net/querz/nbt/ByteArrayTagTest.java @@ -1,7 +1,5 @@ package net.querz.nbt; -import java.io.DataInputStream; -import java.io.DataOutputStream; import java.util.Arrays; public class ByteArrayTagTest extends NBTTestCase { @@ -10,7 +8,7 @@ public void testStringConversion() { ByteArrayTag t = new ByteArrayTag(new byte[]{Byte.MIN_VALUE, 0, Byte.MAX_VALUE}); assertTrue(Arrays.equals(new byte[]{Byte.MIN_VALUE, 0, Byte.MAX_VALUE}, t.getValue())); assertEquals(7, t.getID()); - assertEquals("[B;-128b,0b,127b]", t.toTagString()); +// assertEquals("[B;-128b,0b,127b]", t.toTagString()); assertEquals("{\"type\":\"" + t.getClass().getSimpleName() + "\",\"value\":[-128,0,127]}", t.toString()); } @@ -33,6 +31,7 @@ public void testClone() { public void testSerializeDeserialize() { ByteArrayTag t = new ByteArrayTag(new byte[]{Byte.MIN_VALUE, 0, Byte.MAX_VALUE}); byte[] data = serialize(t); + System.out.println(Arrays.toString(data)); assertTrue(Arrays.equals(new byte[]{7, 0, 0, 0, 0, 0, 3, -128, 0, 127}, data)); ByteArrayTag tt = (ByteArrayTag) deserialize(data); assertTrue(t.equals(tt)); @@ -66,18 +65,8 @@ public NotAnArrayTag(String value) { } @Override - public void serializeValue(DataOutputStream dos, int depth) { - throw new UnsupportedOperationException("goddammit, this is a test class, you don't want to save it."); - } - - @Override - public void deserializeValue(DataInputStream dis, int depth) { - throw new UnsupportedOperationException("goddammit, this is a test class, you don't want to load it."); - } - - @Override - public String valueToTagString(int depth) { - return escapeString(getValue(), true); + public byte getID() { + return 0; } @Override diff --git a/src/test/java/net/querz/nbt/ByteTagTest.java b/src/test/java/net/querz/nbt/ByteTagTest.java index de8b604a..56a346f6 100644 --- a/src/test/java/net/querz/nbt/ByteTagTest.java +++ b/src/test/java/net/querz/nbt/ByteTagTest.java @@ -11,7 +11,7 @@ public void testStringConversion() { assertEquals(Byte.MAX_VALUE, t.asInt()); assertEquals(Byte.MAX_VALUE, t.asLong()); assertEquals(1, t.getID()); - assertEquals(Byte.MAX_VALUE + "b", t.toTagString()); +// assertEquals(Byte.MAX_VALUE + "b", t.toTagString()); assertEquals("{\"type\":\"" + t.getClass().getSimpleName() + "\",\"value\":" + Byte.MAX_VALUE + "}", t.toString()); } diff --git a/src/test/java/net/querz/nbt/CompoundTagTest.java b/src/test/java/net/querz/nbt/CompoundTagTest.java index b511a335..7c5b783d 100644 --- a/src/test/java/net/querz/nbt/CompoundTagTest.java +++ b/src/test/java/net/querz/nbt/CompoundTagTest.java @@ -1,5 +1,7 @@ package net.querz.nbt; +import net.querz.io.MaxDepthReachedException; + import java.util.Arrays; import java.util.LinkedHashMap; import java.util.Map; @@ -19,7 +21,7 @@ private CompoundTag createCompoundTag() { public void testStringConversion() { CompoundTag ct = createCompoundTag(); - assertEquals("{b:127b,str:foo,list:[123b]}", ct.toTagString()); +// assertEquals("{b:127b,str:foo,list:[123b]}", ct.toTagString()); assertEquals("{\"type\":\"CompoundTag\"," + "\"value\":{" + "\"b\":{\"type\":\"ByteTag\",\"value\":127}," + @@ -152,6 +154,7 @@ public void testClear() { public void testSerializeDeserialize() { CompoundTag ct = createCompoundTag(); byte[] data = serialize(ct); + System.out.println(Arrays.toString(data)); assertTrue(Arrays.equals(new byte[]{10, 0, 0, 1, 0, 1, 98, 127, 8, 0, 3, 115, 116, 114, 0, 3, 102, 111, 111, 9, 0, 4, 108, 105, 115, 116, 1, 0, 0, 0, 1, 123, 0}, data)); CompoundTag tt = (CompoundTag) deserialize(data); assertTrue(ct.equals(tt)); @@ -277,10 +280,10 @@ public void testMaxDepth() { 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); +// 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); +// assertThrowsRuntimeException(() -> root.valueToTagString(-1), IllegalArgumentException.class); } public void testRecursion() { @@ -288,7 +291,7 @@ public void testRecursion() { recursive.put("recursive", recursive); assertThrowsRuntimeException(() -> serialize(recursive), MaxDepthReachedException.class); assertThrowsRuntimeException(recursive::toString, MaxDepthReachedException.class); - assertThrowsRuntimeException(recursive::toTagString, MaxDepthReachedException.class); +// assertThrowsRuntimeException(recursive::toTagString, MaxDepthReachedException.class); } public void testEntrySet() { diff --git a/src/test/java/net/querz/nbt/DoubleTagTest.java b/src/test/java/net/querz/nbt/DoubleTagTest.java index 2444aebc..8309d72e 100644 --- a/src/test/java/net/querz/nbt/DoubleTagTest.java +++ b/src/test/java/net/querz/nbt/DoubleTagTest.java @@ -9,7 +9,7 @@ public void testStringConversion() { DoubleTag t = new DoubleTag(Double.MAX_VALUE); assertEquals(Double.MAX_VALUE, t.asDouble()); assertEquals(6, t.getID()); - assertEquals(Double.MAX_VALUE + "d", t.toTagString()); +// assertEquals(Double.MAX_VALUE + "d", t.toTagString()); assertEquals("{\"type\":\"" + t.getClass().getSimpleName() + "\",\"value\":" + Double.MAX_VALUE + "}", t.toString()); } diff --git a/src/test/java/net/querz/nbt/EndTagTest.java b/src/test/java/net/querz/nbt/EndTagTest.java index 72c538b5..a128515c 100644 --- a/src/test/java/net/querz/nbt/EndTagTest.java +++ b/src/test/java/net/querz/nbt/EndTagTest.java @@ -7,15 +7,15 @@ public void testStringConversion() { assertEquals(0, e.getID()); assertNull(e.getValue()); assertEquals("{\"type\":\"" + e.getClass().getSimpleName() + "\",\"value\":\"end\"}", e.toString()); - assertThrowsRuntimeException(e::toTagString, UnsupportedOperationException.class); +// assertThrowsRuntimeException(e::toTagString, UnsupportedOperationException.class); } public void testClone() { assertTrue(EndTag.INSTANCE == EndTag.INSTANCE.clone()); } - public void testSerializeDeserialize() { - assertThrowsNoRuntimeException(() -> EndTag.INSTANCE.serializeValue(null, 0)); - assertThrowsNoRuntimeException(() -> EndTag.INSTANCE.deserializeValue(null, 0)); - } +// public void testSerializeDeserialize() { +// assertThrowsNoRuntimeException(() -> EndTag.INSTANCE.serializeValue(null, 0)); +// assertThrowsNoRuntimeException(() -> EndTag.INSTANCE.deserializeValue(null, 0)); +// } } diff --git a/src/test/java/net/querz/nbt/FloatTagTest.java b/src/test/java/net/querz/nbt/FloatTagTest.java index 1d6e8172..71074e29 100644 --- a/src/test/java/net/querz/nbt/FloatTagTest.java +++ b/src/test/java/net/querz/nbt/FloatTagTest.java @@ -9,7 +9,7 @@ public void testStringConversion() { FloatTag t = new FloatTag(Float.MAX_VALUE); assertEquals(Float.MAX_VALUE, t.asFloat()); assertEquals(5, t.getID()); - assertEquals(Float.MAX_VALUE + "f", t.toTagString()); +// assertEquals(Float.MAX_VALUE + "f", t.toTagString()); assertEquals("{\"type\":\"" + t.getClass().getSimpleName() + "\",\"value\":" + Float.MAX_VALUE + "}", t.toString()); } diff --git a/src/test/java/net/querz/nbt/IntArrayTagTest.java b/src/test/java/net/querz/nbt/IntArrayTagTest.java index e02c686e..00e992d0 100644 --- a/src/test/java/net/querz/nbt/IntArrayTagTest.java +++ b/src/test/java/net/querz/nbt/IntArrayTagTest.java @@ -8,7 +8,7 @@ public void testStringConversion() { IntArrayTag t = new IntArrayTag(new int[]{Integer.MIN_VALUE, 0, Integer.MAX_VALUE}); assertTrue(Arrays.equals(new int[]{Integer.MIN_VALUE, 0, Integer.MAX_VALUE}, t.getValue())); assertEquals(11, t.getID()); - assertEquals("[I;-2147483648,0,2147483647]", t.toTagString()); +// assertEquals("[I;-2147483648,0,2147483647]", t.toTagString()); assertEquals("{\"type\":\"" + t.getClass().getSimpleName() + "\",\"value\":[-2147483648,0,2147483647]}", t.toString()); } diff --git a/src/test/java/net/querz/nbt/IntTagTest.java b/src/test/java/net/querz/nbt/IntTagTest.java index d62ad42e..a7eba303 100644 --- a/src/test/java/net/querz/nbt/IntTagTest.java +++ b/src/test/java/net/querz/nbt/IntTagTest.java @@ -9,7 +9,7 @@ public void testStringConversion() { assertEquals(Integer.MAX_VALUE, t.asInt()); assertEquals(Integer.MAX_VALUE, t.asLong()); assertEquals(3, t.getID()); - assertEquals(Integer.MAX_VALUE + "", t.toTagString()); +// assertEquals(Integer.MAX_VALUE + "", t.toTagString()); assertEquals("{\"type\":\"" + t.getClass().getSimpleName() + "\",\"value\":" + Integer.MAX_VALUE + "}", t.toString()); } diff --git a/src/test/java/net/querz/nbt/ListTagTest.java b/src/test/java/net/querz/nbt/ListTagTest.java index 7e2d02cc..447d1663 100644 --- a/src/test/java/net/querz/nbt/ListTagTest.java +++ b/src/test/java/net/querz/nbt/ListTagTest.java @@ -1,6 +1,8 @@ package net.querz.nbt; import junit.framework.TestCase; +import net.querz.io.MaxDepthReachedException; + import java.util.Arrays; import java.util.Comparator; import static org.junit.Assert.assertNotEquals; @@ -26,7 +28,7 @@ public void testStringConversion() { assertEquals(Byte.MIN_VALUE, bl.get(0).asByte()); assertEquals(0, bl.get(1).asByte()); assertEquals(Byte.MAX_VALUE, bl.get(2).asByte()); - assertEquals("[-128b,0b,127b]", bl.toTagString()); +// assertEquals("[-128b,0b,127b]", bl.toTagString()); assertEquals("{\"type\":\"ListTag\"," + "\"value\":{" + "\"type\":\"ByteTag\"," + @@ -34,7 +36,7 @@ public void testStringConversion() { "-128," + "0," + "127]}}", bl.toString()); - ListTag lu = ListTag.createUnchecked(); + ListTag lu = ListTag.createUnchecked(null); assertEquals("{\"type\":\"ListTag\",\"value\":{\"type\":\"EndTag\",\"list\":[]}}", lu.toString()); } @@ -66,8 +68,8 @@ public void testEquals() { assertEquals(il, new ListTag<>(IntTag.class)); // test empty untyped list - ListTag lu = ListTag.createUnchecked(); - ListTag lu2 = ListTag.createUnchecked(); + ListTag lu = ListTag.createUnchecked(null); + ListTag lu2 = ListTag.createUnchecked(null); assertTrue(lu.equals(lu2)); lu2.asIntTagList(); assertFalse(lu.equals(lu2)); @@ -117,6 +119,7 @@ public void testSerializeDeserializeEmptyList() { // empty list can't have type assertTrue(Arrays.equals(new byte[]{9, 0, 0, 0, 0, 0, 0, 0}, data)); ListTag et = (ListTag) deserialize(data); + System.out.println(et.getTypeClass()); assertNotNull(et); // doesn't make sense as there's no type // assertThrowsRuntimeException(et::asByteTagList, ClassCastException.class); @@ -139,53 +142,53 @@ public void testCasting() { assertEquals(ByteTag.class, b.getTypeClass()); //adjust ListTag type during deserialization - ListTag l = ListTag.createUnchecked(); + ListTag l = ListTag.createUnchecked(null); assertThrowsNoRuntimeException(l::asByteTagList); l.addByte(Byte.MAX_VALUE); assertThrowsNoRuntimeException(l::asByteTagList); assertThrowsRuntimeException(l::asShortTagList, ClassCastException.class); - l = ListTag.createUnchecked(); + l = ListTag.createUnchecked(null); l.addShort(Short.MAX_VALUE); assertThrowsNoRuntimeException(l::asShortTagList); assertThrowsRuntimeException(l::asIntTagList, ClassCastException.class); - l = ListTag.createUnchecked(); + l = ListTag.createUnchecked(null); l.addInt(Integer.MAX_VALUE); assertThrowsNoRuntimeException(l::asIntTagList); assertThrowsRuntimeException(l::asLongTagList, ClassCastException.class); - l = ListTag.createUnchecked(); + l = ListTag.createUnchecked(null); l.addLong(Long.MAX_VALUE); assertThrowsNoRuntimeException(l::asLongTagList); assertThrowsRuntimeException(l::asFloatTagList, ClassCastException.class); - l = ListTag.createUnchecked(); + l = ListTag.createUnchecked(null); l.addFloat(Float.MAX_VALUE); assertThrowsNoRuntimeException(l::asFloatTagList); assertThrowsRuntimeException(l::asDoubleTagList, ClassCastException.class); - l = ListTag.createUnchecked(); + l = ListTag.createUnchecked(null); l.addDouble(Double.MAX_VALUE); assertThrowsNoRuntimeException(l::asDoubleTagList); assertThrowsRuntimeException(l::asStringTagList, ClassCastException.class); - l = ListTag.createUnchecked(); + l = ListTag.createUnchecked(null); l.addString("foo"); assertThrowsNoRuntimeException(l::asStringTagList); assertThrowsRuntimeException(l::asByteArrayTagList, ClassCastException.class); - l = ListTag.createUnchecked(); + l = ListTag.createUnchecked(null); l.addByteArray(new byte[]{Byte.MIN_VALUE, 0, Byte.MAX_VALUE}); assertThrowsNoRuntimeException(l::asByteArrayTagList); assertThrowsRuntimeException(l::asIntArrayTagList, ClassCastException.class); - l = ListTag.createUnchecked(); + l = ListTag.createUnchecked(null); l.addIntArray(new int[]{Integer.MIN_VALUE, 0, Integer.MAX_VALUE}); assertThrowsNoRuntimeException(l::asIntArrayTagList); assertThrowsRuntimeException(l::asLongArrayTagList, ClassCastException.class); - l = ListTag.createUnchecked(); + l = ListTag.createUnchecked(null); l.addLongArray(new long[]{Long.MIN_VALUE, 0, Long.MAX_VALUE}); assertThrowsNoRuntimeException(l::asLongArrayTagList); assertThrowsRuntimeException(l::asListTagList, ClassCastException.class); @@ -200,7 +203,7 @@ public void testCasting() { assertThrowsNoRuntimeException(lco::asCompoundTagList); assertThrowsRuntimeException(lco::asByteTagList, ClassCastException.class); - ListTag lg = ListTag.createUnchecked(); + ListTag lg = ListTag.createUnchecked(null); ListTag lb = assertThrowsNoRuntimeException(lg::asByteTagList); assertEquals(lb, lg); //only allow casting once from untyped list to typed list @@ -246,9 +249,9 @@ public void testMaxDepth() { assertThrowsRuntimeException(() -> serialize(root), MaxDepthReachedException.class); assertThrowsRuntimeException(() -> deserializeFromFile("max_depth_reached.dat"), MaxDepthReachedException.class); assertThrowsRuntimeException(root::toString, MaxDepthReachedException.class); - assertThrowsRuntimeException(root::toTagString, MaxDepthReachedException.class); +// assertThrowsRuntimeException(root::toTagString, MaxDepthReachedException.class); assertThrowsRuntimeException(() -> root.valueToString(-1), IllegalArgumentException.class); - assertThrowsRuntimeException(() -> root.valueToTagString(-1), IllegalArgumentException.class); +// assertThrowsRuntimeException(() -> root.valueToTagString(-1), IllegalArgumentException.class); } public void testRecursion() { @@ -256,7 +259,7 @@ public void testRecursion() { recursive.add(recursive); assertThrowsRuntimeException(() -> serialize(recursive), MaxDepthReachedException.class); assertThrowsRuntimeException(recursive::toString, MaxDepthReachedException.class); - assertThrowsRuntimeException(recursive::toTagString, MaxDepthReachedException.class); +// assertThrowsRuntimeException(recursive::toTagString, MaxDepthReachedException.class); } public void testContains() { diff --git a/src/test/java/net/querz/nbt/LongArrayTagTest.java b/src/test/java/net/querz/nbt/LongArrayTagTest.java index a2fe39c4..f0509cc1 100644 --- a/src/test/java/net/querz/nbt/LongArrayTagTest.java +++ b/src/test/java/net/querz/nbt/LongArrayTagTest.java @@ -8,7 +8,7 @@ public void testStringConversion() { LongArrayTag t = new LongArrayTag(new long[]{Long.MIN_VALUE, 0, Long.MAX_VALUE}); assertTrue(Arrays.equals(new long[]{Long.MIN_VALUE, 0, Long.MAX_VALUE}, t.getValue())); assertEquals(12, t.getID()); - assertEquals("[L;-9223372036854775808l,0l,9223372036854775807l]", t.toTagString()); +// assertEquals("[L;-9223372036854775808l,0l,9223372036854775807l]", t.toTagString()); assertEquals("{\"type\":\"" + t.getClass().getSimpleName() + "\",\"value\":[-9223372036854775808,0,9223372036854775807]}", t.toString()); } diff --git a/src/test/java/net/querz/nbt/LongTagTest.java b/src/test/java/net/querz/nbt/LongTagTest.java index 6d9ea1e1..1bc285f3 100644 --- a/src/test/java/net/querz/nbt/LongTagTest.java +++ b/src/test/java/net/querz/nbt/LongTagTest.java @@ -8,7 +8,7 @@ public void testStringConversion() { LongTag t = new LongTag(Long.MAX_VALUE); assertEquals(Long.MAX_VALUE, t.asLong()); assertEquals(4, t.getID()); - assertEquals(Long.MAX_VALUE + "l", t.toTagString()); +// assertEquals(Long.MAX_VALUE + "l", t.toTagString()); assertEquals("{\"type\":\"" + t.getClass().getSimpleName() + "\",\"value\":" + Long.MAX_VALUE + "}", t.toString()); } diff --git a/src/test/java/net/querz/nbt/NBTTestCase.java b/src/test/java/net/querz/nbt/NBTTestCase.java index 52d00aa5..b5934c5e 100644 --- a/src/test/java/net/querz/nbt/NBTTestCase.java +++ b/src/test/java/net/querz/nbt/NBTTestCase.java @@ -1,6 +1,11 @@ package net.querz.nbt; import junit.framework.TestCase; +import net.querz.nbt.io.NBTDeserializer; +import net.querz.nbt.io.NBTSerializer; +import net.querz.nbt.io.NBTUtil; +import net.querz.nbt.io.NamedTag; + import java.io.BufferedInputStream; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; @@ -24,17 +29,21 @@ public abstract class NBTTestCase extends TestCase { @Override public void tearDown() throws Exception { super.tearDown(); - TagFactory.unregisterCustomTag(90); - TagFactory.unregisterCustomTag(100); - TagFactory.unregisterCustomTag(110); - TagFactory.unregisterCustomTag(120); +// TagFactory.unregisterCustomTag(90); +// TagFactory.unregisterCustomTag(100); +// TagFactory.unregisterCustomTag(110); +// TagFactory.unregisterCustomTag(120); + NBTUtil.unregisterCustomTag(90); + NBTUtil.unregisterCustomTag(100); + NBTUtil.unregisterCustomTag(110); + NBTUtil.unregisterCustomTag(120); // cleanupTmpDir(); } protected byte[] serialize(Tag tag) { ByteArrayOutputStream baos = new ByteArrayOutputStream(); try (DataOutputStream dos = new DataOutputStream(baos)) { - tag.serialize(dos, Tag.DEFAULT_MAX_DEPTH); + new NBTSerializer(false).toStream(new NamedTag(null, tag), dos); } catch (IOException ex) { ex.printStackTrace(); fail(ex.getMessage()); @@ -44,7 +53,7 @@ protected byte[] serialize(Tag tag) { protected Tag deserialize(byte[] data) { try (DataInputStream dis = new DataInputStream(new ByteArrayInputStream(data))) { - return Tag.deserialize(dis, Tag.DEFAULT_MAX_DEPTH); + return new NBTDeserializer(false).fromStream(dis).getTag(); } catch (IOException ex) { ex.printStackTrace(); fail(ex.getMessage()); @@ -60,7 +69,7 @@ protected File getResourceFile(String name) { protected Tag deserializeFromFile(String f) { try (DataInputStream dis = new DataInputStream(new FileInputStream(getResourceFile(f)))) { - return Tag.deserialize(dis, Tag.DEFAULT_MAX_DEPTH); + return new NBTDeserializer(false).fromStream(dis).getTag(); } 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 ec039a7d..009d9708 100644 --- a/src/test/java/net/querz/nbt/ShortTagTest.java +++ b/src/test/java/net/querz/nbt/ShortTagTest.java @@ -10,7 +10,7 @@ public void testStringConversion() { assertEquals(Short.MAX_VALUE, t.asInt()); assertEquals(Short.MAX_VALUE, t.asLong()); assertEquals(2, t.getID()); - assertEquals(Short.MAX_VALUE + "s", t.toTagString()); +// assertEquals(Short.MAX_VALUE + "s", t.toTagString()); assertEquals("{\"type\":\"" + t.getClass().getSimpleName() + "\",\"value\":" + Short.MAX_VALUE + "}", t.toString()); } diff --git a/src/test/java/net/querz/nbt/StringTagTest.java b/src/test/java/net/querz/nbt/StringTagTest.java index da6b39fb..f1fc425a 100644 --- a/src/test/java/net/querz/nbt/StringTagTest.java +++ b/src/test/java/net/querz/nbt/StringTagTest.java @@ -8,7 +8,7 @@ public void testStringConversion() { StringTag t = new StringTag("foo"); assertEquals("foo", t.getValue()); assertEquals(8, t.getID()); - assertEquals("foo", t.toTagString()); +// assertEquals("foo", t.toTagString()); assertEquals("{\"type\":\"" + t.getClass().getSimpleName() + "\",\"value\":\"foo\"}", t.toString()); } @@ -37,13 +37,13 @@ public void testSerializeDeserialize() { public void testEscape() { StringTag allValue = new StringTag("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXZY0123456789_-+"); - assertEquals("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXZY0123456789_-+", allValue.toTagString()); +// assertEquals("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXZY0123456789_-+", allValue.toTagString()); StringTag escapeValue = new StringTag("öäü"); - assertEquals("\"öäü\"", escapeValue.toTagString()); +// assertEquals("\"öäü\"", escapeValue.toTagString()); StringTag escapeSpecialChars = new StringTag("\\\n\r\t\""); - assertEquals("\"\\\\\\n\\r\\t\\\"\"", escapeSpecialChars.toTagString()); +// assertEquals("\"\\\\\\n\\r\\t\\\"\"", escapeSpecialChars.toTagString()); StringTag escapeEmpty = new StringTag(""); - assertEquals("\"\"", escapeEmpty.toTagString()); +// assertEquals("\"\"", escapeEmpty.toTagString()); //no null values allowed assertThrowsRuntimeException(() -> new StringTag().setValue(null), NullPointerException.class); diff --git a/src/test/java/net/querz/nbt/TagFactoryTest.java b/src/test/java/net/querz/nbt/TagFactoryTest.java deleted file mode 100644 index 0fa7aa6e..00000000 --- a/src/test/java/net/querz/nbt/TagFactoryTest.java +++ /dev/null @@ -1,46 +0,0 @@ -package net.querz.nbt; - -import net.querz.nbt.custom.CharTag; - -public class TagFactoryTest extends NBTTestCase { - - public void testFromID() { - assertEquals(EndTag.class, TagFactory.fromID(0).getClass()); - assertEquals(ByteTag.class, TagFactory.fromID(1).getClass()); - assertEquals(ShortTag.class, TagFactory.fromID(2).getClass()); - assertEquals(IntTag.class, TagFactory.fromID(3).getClass()); - assertEquals(LongTag.class, TagFactory.fromID(4).getClass()); - assertEquals(FloatTag.class, TagFactory.fromID(5).getClass()); - assertEquals(DoubleTag.class, TagFactory.fromID(6).getClass()); - assertEquals(ByteArrayTag.class, TagFactory.fromID(7).getClass()); - assertEquals(StringTag.class, TagFactory.fromID(8).getClass()); - assertEquals(ListTag.class, TagFactory.fromID(9).getClass()); - assertEquals(CompoundTag.class, TagFactory.fromID(10).getClass()); - assertEquals(IntArrayTag.class, TagFactory.fromID(11).getClass()); - assertEquals(LongArrayTag.class, TagFactory.fromID(12).getClass()); - assertThrowsRuntimeException(() -> TagFactory.fromID(-1), IllegalArgumentException.class); - } - - public void testClassFromID() { - assertThrowsNoRuntimeException(() -> TagFactory.classFromID(1)); - assertThrowsRuntimeException(() -> TagFactory.classFromID(20), IllegalArgumentException.class); - } - - public void testIDFromClass() { - assertThrowsNoRuntimeException(() -> TagFactory.idFromClass(ByteTag.class)); - assertThrowsRuntimeException(() -> TagFactory.idFromClass(CharTag.class), IllegalArgumentException.class); - } - - public void testRegisterCustomTag() { - assertThrowsRuntimeException(() -> TagFactory.registerCustomTag(-1, CharTag::new, CharTag.class), IllegalArgumentException.class); - assertThrowsRuntimeException(() -> TagFactory.registerCustomTag(12, CharTag::new, CharTag.class), IllegalArgumentException.class); - assertThrowsRuntimeException(() -> TagFactory.registerCustomTag(128, CharTag::new, CharTag.class), IllegalArgumentException.class); - CharTag.register(); - assertThrowsRuntimeException(CharTag::register, IllegalArgumentException.class); - } - - public void testUnregisterCustomTag() { - CharTag.register(); - assertThrowsNoRuntimeException(() -> TagFactory.unregisterCustomTag(new CharTag().getID())); - } -} diff --git a/src/test/java/net/querz/nbt/TagTest.java b/src/test/java/net/querz/nbt/TagTest.java deleted file mode 100644 index 02937fb3..00000000 --- a/src/test/java/net/querz/nbt/TagTest.java +++ /dev/null @@ -1,75 +0,0 @@ -package net.querz.nbt; - -import java.io.ByteArrayInputStream; -import java.io.DataInputStream; -import java.io.File; -import java.io.IOException; -import java.io.PushbackInputStream; -import java.util.LinkedHashMap; -import java.util.zip.GZIPInputStream; - -public class TagTest extends NBTTestCase { - - public void testWriteReadTag() { - CompoundTag t = new CompoundTag(); - invokeSetValue(t, new LinkedHashMap<>()); - t.putByte("byte", Byte.MAX_VALUE); - t.putShort("short", Short.MAX_VALUE); - File file = getNewTmpFile("compressed.dat"); - try { - NBTUtil.writeTag(t, "name", file, true); - } catch (IOException ex) { - fail(ex.getMessage()); - } - - assertEquals("E8F7B55F81FADB8A5657461D9188DE73", calculateFileMD5(file)); - - try { - CompoundTag tt = (CompoundTag) NBTUtil.readTag(file); - assertEquals(t, tt); - } catch (IOException ex) { - fail(ex.getMessage()); - } - } - - public void testApplyDecompression() { - ByteArrayInputStream baisComp = new ByteArrayInputStream(new byte[]{31, -117, 8, 0, 0, 0, 0, 0, 0, 0}); - try (DataInputStream in = new DataInputStream(baisComp)) { - assertTrue(NBTUtil.applyDecompression(in) instanceof GZIPInputStream); - } catch (IOException ex) { - ex.printStackTrace(); - fail(ex.getMessage()); - } - - ByteArrayInputStream baisUncomp = new ByteArrayInputStream(new byte[]{0, 0}); - try (DataInputStream in = new DataInputStream(baisUncomp)) { - assertTrue(NBTUtil.applyDecompression(in) instanceof PushbackInputStream); - } catch (IOException ex) { - ex.printStackTrace(); - fail(ex.getMessage()); - } - } - - public void testMakeMyCoverageGreatAgain() { - assertThrowsException(() -> NBTUtil.readTag((String) null), NullPointerException.class); - assertThrowsException(() -> NBTUtil.writeTag(null, (String) null), NullPointerException.class); - assertThrowsException(() -> NBTUtil.writeTag(null, (File) null), NullPointerException.class); - assertThrowsException(() -> NBTUtil.writeTag(null, (String) null, false), NullPointerException.class); - assertThrowsException(() -> NBTUtil.writeTag(null, (File) null, false), NullPointerException.class); - assertThrowsException(() -> NBTUtil.writeTag(null, null, (String) null), NullPointerException.class); - assertThrowsException(() -> NBTUtil.writeTag(null, (File) null), NullPointerException.class); - assertThrowsException(() -> NBTUtil.writeTag(null, null, (File) null), NullPointerException.class); - assertThrowsException(() -> NBTUtil.writeTag(null, (File) null), NullPointerException.class); - assertThrowsException(() -> NBTUtil.writeTag(null, null, (File) null, false), NullPointerException.class); - assertThrowsException(() -> NBTUtil.writeTag(null, null, (String) null, false), NullPointerException.class); - - CompoundTag dummy = new CompoundTag(); - assertThrowsNoException(() -> NBTUtil.writeTag(dummy, getNewTmpFile("coverage.dat"))); - assertThrowsNoException(() -> NBTUtil.writeTag(dummy, getNewTmpFile("coverage.dat").getAbsolutePath())); - assertThrowsNoException(() -> NBTUtil.writeTag(dummy, getNewTmpFile("coverage.dat"), true)); - assertThrowsNoException(() -> NBTUtil.writeTag(dummy, getNewTmpFile("coverage.dat").getAbsolutePath(), true)); - assertThrowsNoException(() -> NBTUtil.writeTag(dummy, "foo", getNewTmpFile("coverage.dat"))); - assertThrowsNoException(() -> NBTUtil.writeTag(dummy, "foo", getNewTmpFile("coverage.dat").getAbsolutePath())); - assertThrowsNoException(() -> NBTUtil.writeTag(dummy, "foo", getNewTmpFile("coverage.dat").getAbsolutePath(), true)); - } -} diff --git a/src/test/java/net/querz/nbt/custom/CharTagTest.java b/src/test/java/net/querz/nbt/custom/CharTagTest.java index 9f9c2bf0..df7f8aa1 100644 --- a/src/test/java/net/querz/nbt/custom/CharTagTest.java +++ b/src/test/java/net/querz/nbt/custom/CharTagTest.java @@ -10,7 +10,7 @@ public void testStringConversion() { CharTag t = new CharTag('a'); assertEquals('a', (char) t.getValue()); assertEquals(110, t.getID()); - assertEquals("a", t.toTagString()); +// assertEquals("a", t.toTagString()); assertEquals("{\"type\":\"" + t.getClass().getSimpleName() + "\",\"value\":\"a\"}", t.toString()); } diff --git a/src/test/java/net/querz/nbt/custom/ObjectTagTest.java b/src/test/java/net/querz/nbt/custom/ObjectTagTest.java index e069d88c..b1685887 100644 --- a/src/test/java/net/querz/nbt/custom/ObjectTagTest.java +++ b/src/test/java/net/querz/nbt/custom/ObjectTagTest.java @@ -1,8 +1,6 @@ package net.querz.nbt.custom; import net.querz.nbt.NBTTestCase; -import net.querz.nbt.NBTUtil; -import net.querz.nbt.TagFactory; import static org.junit.Assert.assertNotEquals; import java.io.IOException; import java.io.Serializable; @@ -20,7 +18,7 @@ public void testStringConversion() { ObjectTag o = new ObjectTag<>(d); assertEquals(90, o.getID()); assertEquals("{\"type\":\"ObjectTag\",\"value\":\"" + d + "\"}", o.toString()); - assertEquals("\"" + d + "\"", o.toTagString()); +// assertEquals("\"" + d + "\"", o.toTagString()); } public void testEquals() { @@ -77,7 +75,7 @@ public void testNullValue() { ObjectTag n = new ObjectTag<>(); assertNull(n.getValue()); assertEquals("{\"type\":\"ObjectTag\",\"value\":null}", n.toString()); - assertEquals("null", n.toTagString()); +// assertEquals("null", n.toTagString()); } public void testNullValueEquals() { @@ -140,10 +138,10 @@ public void testCompareTo() { assertEquals(d9, l.get(2)); } - public void testUnknownObject() { - TagFactory.registerCustomTag(90, ObjectTag::new, ObjectTag.class); - assertThrowsException(() -> NBTUtil.readTag(getResourceFile("unknown_object_tag.dat")), IOException.class); - } +// public void testUnknownObject() { +// TagFactory.registerCustomTag(90, ObjectTag::new, ObjectTag.class); +// assertThrowsException(() -> NBTUtil.readTag(getResourceFile("unknown_object_tag.dat")), IOException.class); +// } public static abstract class AbstractDummyObject implements Serializable { private static final long serialVersionUID = 1L; diff --git a/src/test/java/net/querz/nbt/custom/ShortArrayTagTest.java b/src/test/java/net/querz/nbt/custom/ShortArrayTagTest.java index b4a7fd5b..2d380305 100644 --- a/src/test/java/net/querz/nbt/custom/ShortArrayTagTest.java +++ b/src/test/java/net/querz/nbt/custom/ShortArrayTagTest.java @@ -11,7 +11,7 @@ public void testStringConversion() { ShortArrayTag t = new ShortArrayTag(new short[]{Short.MIN_VALUE, 0, Short.MAX_VALUE}); assertTrue(Arrays.equals(new short[]{Short.MIN_VALUE, 0, Short.MAX_VALUE}, t.getValue())); assertEquals(100, t.getID()); - assertEquals("[S;-32768s,0s,32767s]", t.toTagString()); +// assertEquals("[S;-32768s,0s,32767s]", t.toTagString()); assertEquals("{\"type\":\"" + t.getClass().getSimpleName() + "\",\"value\":[-32768,0,32767]}", t.toString()); } diff --git a/src/test/java/net/querz/nbt/custom/StructTagTest.java b/src/test/java/net/querz/nbt/custom/StructTagTest.java index 77c8cfbc..fa885eae 100644 --- a/src/test/java/net/querz/nbt/custom/StructTagTest.java +++ b/src/test/java/net/querz/nbt/custom/StructTagTest.java @@ -25,7 +25,7 @@ public void testStringConversion() { StructTag.register(); StructTag s = createStructTag(); assertEquals(120, s.getID()); - assertEquals("[127b,2147483647]", s.toTagString()); +// assertEquals("[127b,2147483647]", s.toTagString()); assertEquals("{\"type\":\"StructTag\",\"value\":[{\"type\":\"ByteTag\",\"value\":127},{\"type\":\"IntTag\",\"value\":2147483647}]}", s.toString()); } @@ -68,6 +68,7 @@ public void testSerializeDeserialize() { StructTag s = createStructTag(); StructTag.register(); byte[] data = serialize(s); + System.out.println(Arrays.toString(data)); assertTrue(Arrays.equals(new byte[]{120, 0, 0, 0, 0, 0, 2, 1, 127, 3, 127, -1, -1, -1}, data)); StructTag ss = (StructTag) deserialize(data); assertTrue(s.equals(ss)); diff --git a/src/test/java/net/querz/nbt/io/MSONWriterTest.java b/src/test/java/net/querz/nbt/io/MSONWriterTest.java new file mode 100644 index 00000000..f81e275f --- /dev/null +++ b/src/test/java/net/querz/nbt/io/MSONWriterTest.java @@ -0,0 +1,15 @@ +package net.querz.nbt.io; + +import net.querz.nbt.EndTag; +import net.querz.nbt.NBTTestCase; + +import java.io.StringWriter; + +public class MSONWriterTest extends NBTTestCase { + + public void testWrite() { + StringWriter sw = new StringWriter(); + assertThrowsNoException(() -> MSONWriter.write(EndTag.INSTANCE, sw)); + System.out.println(sw.toString()); + } +} diff --git a/src/test/java/net/querz/nbt/io/StringDeserializerTest.java b/src/test/java/net/querz/nbt/io/StringDeserializerTest.java deleted file mode 100644 index 25552da2..00000000 --- a/src/test/java/net/querz/nbt/io/StringDeserializerTest.java +++ /dev/null @@ -1,12 +0,0 @@ -package net.querz.nbt.io; - -import net.querz.nbt.NBTTestCase; - -public class StringDeserializerTest extends NBTTestCase { - - public void test() { - String s = "{blah:1.3f,\"foo\":\"bar\",test:\" moo\",bytes:[B;1,2,3,4]}"; - MSONDeserializer m = new MSONDeserializer(s); - System.out.println(m.read(1)); - } -} diff --git a/src/test/java/net/querz/nbt/mca/MCAFileTest.java b/src/test/java/net/querz/nbt/mca/MCAFileTest.java index 6fe66c51..e937fc71 100644 --- a/src/test/java/net/querz/nbt/mca/MCAFileTest.java +++ b/src/test/java/net/querz/nbt/mca/MCAFileTest.java @@ -189,7 +189,7 @@ public void testGetBiomeAt() { } public void testSetBiomeAt() { - MCAFile f = assertThrowsNoException(() -> MCAUtil.readMCAFile(copyResourceToTmp("r.2.2.mca"))); + MCAFile f = assertThrowsNoException(() -> MCAUtil.readMCAFile(copyResourceToTmp("r.2.2.mca")), true); f.setBiomeAt(1024, 1024, 20); assertEquals(20, f.getChunk(64, 64).updateHandle(64, 64).getCompoundTag("Level").getIntArray("Biomes")[0]); f.setBiomeAt(1039, 1039, 47); From 80cc260c871f25f5da02e9e519ef9ecd4755d33a Mon Sep 17 00:00:00 2001 From: Querz Date: Fri, 24 Apr 2020 11:43:20 +0200 Subject: [PATCH 05/51] ignore patch files --- .gitignore | 1 - 1 file changed, 1 deletion(-) diff --git a/.gitignore b/.gitignore index ad25607b..bf59b54c 100644 --- a/.gitignore +++ b/.gitignore @@ -83,5 +83,4 @@ doc/ # ignore out out/ -# ignore patches *.patch \ No newline at end of file From 7123cb2d64254d5a0a50bd6fd6d0eb2d2fc0abcf Mon Sep 17 00:00:00 2001 From: Querz Date: Fri, 24 Apr 2020 14:27:35 +0200 Subject: [PATCH 06/51] the big refactoring: better package names / fix mca biome functions / fix mca blockstate index functions --- .../querz/io/MaxDepthReachedException.java | 1 + .../java/net/querz/{nbt => }/mca/Chunk.java | 95 +++++- .../querz/{nbt => }/mca/CompressionType.java | 2 +- .../{nbt => }/mca/ExceptionFunction.java | 2 +- .../java/net/querz/{nbt => }/mca/MCAFile.java | 66 ++-- .../java/net/querz/{nbt => }/mca/MCAUtil.java | 2 +- .../java/net/querz/{nbt => }/mca/Section.java | 172 +++------- .../java/net/querz/nbt/custom/CharTag.java | 66 ---- .../java/net/querz/nbt/custom/ObjectTag.java | 126 -------- .../net/querz/nbt/custom/ShortArrayTag.java | 68 ---- .../java/net/querz/nbt/custom/StructTag.java | 300 ------------------ .../net/querz/nbt/io/MSONDeserializer.java | 2 +- .../java/net/querz/nbt/io/MSONParser.java | 30 +- .../java/net/querz/nbt/io/MSONSerializer.java | 2 +- .../java/net/querz/nbt/io/MSONWriter.java | 28 +- .../net/querz/nbt/io/NBTDeserializer.java | 2 +- .../java/net/querz/nbt/io/NBTInputStream.java | 28 +- .../net/querz/nbt/io/NBTOutputStream.java | 28 +- .../java/net/querz/nbt/io/NBTSerializer.java | 2 +- src/main/java/net/querz/nbt/io/NBTUtil.java | 2 +- src/main/java/net/querz/nbt/io/NamedTag.java | 2 +- .../java/net/querz/nbt/io/StringPointer.java | 2 - .../net/querz/nbt/{ => tag}/ArrayTag.java | 2 +- .../net/querz/nbt/{ => tag}/ByteArrayTag.java | 2 +- .../java/net/querz/nbt/{ => tag}/ByteTag.java | 2 +- .../net/querz/nbt/{ => tag}/CompoundTag.java | 2 +- .../net/querz/nbt/{ => tag}/DoubleTag.java | 2 +- .../java/net/querz/nbt/{ => tag}/EndTag.java | 2 +- .../net/querz/nbt/{ => tag}/FloatTag.java | 2 +- .../net/querz/nbt/{ => tag}/IntArrayTag.java | 2 +- .../java/net/querz/nbt/{ => tag}/IntTag.java | 2 +- .../java/net/querz/nbt/{ => tag}/ListTag.java | 2 +- .../net/querz/nbt/{ => tag}/LongArrayTag.java | 2 +- .../java/net/querz/nbt/{ => tag}/LongTag.java | 2 +- .../querz/nbt/{ => tag}/NonNullEntrySet.java | 2 +- .../net/querz/nbt/{ => tag}/NumberTag.java | 2 +- .../net/querz/nbt/{ => tag}/ShortTag.java | 2 +- .../net/querz/nbt/{ => tag}/StringTag.java | 2 +- .../java/net/querz/nbt/{ => tag}/Tag.java | 2 +- .../{nbt => }/mca/CompressionTypeTest.java | 2 +- .../net/querz/{nbt => }/mca/MCAFileTest.java | 6 +- .../net/querz/{nbt => }/mca/MCATestCase.java | 8 +- .../net/querz/{nbt => }/mca/MCAUtilTest.java | 2 +- .../net/querz/nbt/custom/CharTagTest.java | 47 --- .../net/querz/nbt/custom/ObjectTagTest.java | 201 ------------ .../querz/nbt/custom/ShortArrayTagTest.java | 61 ---- .../net/querz/nbt/custom/StructTagTest.java | 196 ------------ .../java/net/querz/nbt/io/MSONWriterTest.java | 6 +- .../querz/nbt/{ => tag}/ByteArrayTagTest.java | 3 +- .../net/querz/nbt/{ => tag}/ByteTagTest.java | 2 +- .../querz/nbt/{ => tag}/CompoundTagTest.java | 3 +- .../querz/nbt/{ => tag}/DoubleTagTest.java | 2 +- .../net/querz/nbt/{ => tag}/EndTagTest.java | 2 +- .../nbt/{ => tag}/ExceptionRunnable.java | 2 +- .../nbt/{ => tag}/ExceptionSupplier.java | 2 +- .../net/querz/nbt/{ => tag}/FloatTagTest.java | 2 +- .../querz/nbt/{ => tag}/IntArrayTagTest.java | 2 +- .../net/querz/nbt/{ => tag}/IntTagTest.java | 2 +- .../net/querz/nbt/{ => tag}/ListTagTest.java | 3 +- .../querz/nbt/{ => tag}/LongArrayTagTest.java | 2 +- .../net/querz/nbt/{ => tag}/LongTagTest.java | 2 +- .../net/querz/nbt/{ => tag}/NBTTestCase.java | 2 +- .../nbt/{ => tag}/NoNullEntrySetTest.java | 2 +- .../net/querz/nbt/{ => tag}/ShortTagTest.java | 2 +- .../querz/nbt/{ => tag}/StringTagTest.java | 2 +- 65 files changed, 267 insertions(+), 1361 deletions(-) rename src/main/java/net/querz/{nbt => }/mca/Chunk.java (80%) rename src/main/java/net/querz/{nbt => }/mca/CompressionType.java (98%) rename src/main/java/net/querz/{nbt => }/mca/ExceptionFunction.java (80%) rename src/main/java/net/querz/{nbt => }/mca/MCAFile.java (74%) rename src/main/java/net/querz/{nbt => }/mca/MCAUtil.java (99%) rename src/main/java/net/querz/{nbt => }/mca/Section.java (56%) delete mode 100644 src/main/java/net/querz/nbt/custom/CharTag.java delete mode 100644 src/main/java/net/querz/nbt/custom/ObjectTag.java delete mode 100644 src/main/java/net/querz/nbt/custom/ShortArrayTag.java delete mode 100644 src/main/java/net/querz/nbt/custom/StructTag.java rename src/main/java/net/querz/nbt/{ => tag}/ArrayTag.java (97%) rename src/main/java/net/querz/nbt/{ => tag}/ByteArrayTag.java (96%) rename src/main/java/net/querz/nbt/{ => tag}/ByteTag.java (96%) rename src/main/java/net/querz/nbt/{ => tag}/CompoundTag.java (99%) rename src/main/java/net/querz/nbt/{ => tag}/DoubleTag.java (96%) rename src/main/java/net/querz/nbt/{ => tag}/EndTag.java (94%) rename src/main/java/net/querz/nbt/{ => tag}/FloatTag.java (96%) rename src/main/java/net/querz/nbt/{ => tag}/IntArrayTag.java (96%) rename src/main/java/net/querz/nbt/{ => tag}/IntTag.java (96%) rename src/main/java/net/querz/nbt/{ => tag}/ListTag.java (99%) rename src/main/java/net/querz/nbt/{ => tag}/LongArrayTag.java (96%) rename src/main/java/net/querz/nbt/{ => tag}/LongTag.java (96%) rename src/main/java/net/querz/nbt/{ => tag}/NonNullEntrySet.java (98%) rename src/main/java/net/querz/nbt/{ => tag}/NumberTag.java (95%) rename src/main/java/net/querz/nbt/{ => tag}/ShortTag.java (96%) rename src/main/java/net/querz/nbt/{ => tag}/StringTag.java (97%) rename src/main/java/net/querz/nbt/{ => tag}/Tag.java (99%) rename src/test/java/net/querz/{nbt => }/mca/CompressionTypeTest.java (94%) rename src/test/java/net/querz/{nbt => }/mca/MCAFileTest.java (99%) rename src/test/java/net/querz/{nbt => }/mca/MCATestCase.java (91%) rename src/test/java/net/querz/{nbt => }/mca/MCAUtilTest.java (99%) delete mode 100644 src/test/java/net/querz/nbt/custom/CharTagTest.java delete mode 100644 src/test/java/net/querz/nbt/custom/ObjectTagTest.java delete mode 100644 src/test/java/net/querz/nbt/custom/ShortArrayTagTest.java delete mode 100644 src/test/java/net/querz/nbt/custom/StructTagTest.java rename src/test/java/net/querz/nbt/{ => tag}/ByteArrayTagTest.java (97%) rename src/test/java/net/querz/nbt/{ => tag}/ByteTagTest.java (98%) rename src/test/java/net/querz/nbt/{ => tag}/CompoundTagTest.java (99%) rename src/test/java/net/querz/nbt/{ => tag}/DoubleTagTest.java (98%) rename src/test/java/net/querz/nbt/{ => tag}/EndTagTest.java (96%) rename src/test/java/net/querz/nbt/{ => tag}/ExceptionRunnable.java (79%) rename src/test/java/net/querz/nbt/{ => tag}/ExceptionSupplier.java (79%) rename src/test/java/net/querz/nbt/{ => tag}/FloatTagTest.java (98%) rename src/test/java/net/querz/nbt/{ => tag}/IntArrayTagTest.java (98%) rename src/test/java/net/querz/nbt/{ => tag}/IntTagTest.java (98%) rename src/test/java/net/querz/nbt/{ => tag}/ListTagTest.java (99%) rename src/test/java/net/querz/nbt/{ => tag}/LongArrayTagTest.java (98%) rename src/test/java/net/querz/nbt/{ => tag}/LongTagTest.java (98%) rename src/test/java/net/querz/nbt/{ => tag}/NBTTestCase.java (99%) rename src/test/java/net/querz/nbt/{ => tag}/NoNullEntrySetTest.java (98%) rename src/test/java/net/querz/nbt/{ => tag}/ShortTagTest.java (98%) rename src/test/java/net/querz/nbt/{ => tag}/StringTagTest.java (98%) diff --git a/src/main/java/net/querz/io/MaxDepthReachedException.java b/src/main/java/net/querz/io/MaxDepthReachedException.java index 3824be9e..eb903228 100644 --- a/src/main/java/net/querz/io/MaxDepthReachedException.java +++ b/src/main/java/net/querz/io/MaxDepthReachedException.java @@ -5,6 +5,7 @@ */ @SuppressWarnings("serial") public class MaxDepthReachedException extends RuntimeException { + public MaxDepthReachedException(String msg) { super(msg); } diff --git a/src/main/java/net/querz/nbt/mca/Chunk.java b/src/main/java/net/querz/mca/Chunk.java similarity index 80% rename from src/main/java/net/querz/nbt/mca/Chunk.java rename to src/main/java/net/querz/mca/Chunk.java index ed30159b..364e0703 100644 --- a/src/main/java/net/querz/nbt/mca/Chunk.java +++ b/src/main/java/net/querz/mca/Chunk.java @@ -1,7 +1,7 @@ -package net.querz.nbt.mca; +package net.querz.mca; -import net.querz.nbt.CompoundTag; -import net.querz.nbt.ListTag; +import net.querz.nbt.tag.CompoundTag; +import net.querz.nbt.tag.ListTag; import net.querz.nbt.io.NamedTag; import net.querz.nbt.io.NBTDeserializer; import net.querz.nbt.io.NBTSerializer; @@ -11,6 +11,7 @@ import java.io.FileInputStream; import java.io.IOException; import java.io.RandomAccessFile; +import java.util.Arrays; public class Chunk { @@ -73,7 +74,7 @@ private void initReferences() { this.structures = level.getCompoundTag("Structures"); if (level.containsKey("Sections")) { for (CompoundTag section : level.getListTag("Sections").asCompoundTagList()) { - this.sections[section.getByte("Y")] = new Section(section); + this.sections[section.getByte("Y")] = new Section(section, dataVersion); } } } @@ -106,21 +107,91 @@ public void deserialize(RandomAccessFile raf) throws IOException { } } + /** + * @deprecated Use {@link #getBiomeAt(int, int, int)} instead + */ + @Deprecated public int getBiomeAt(int blockX, int blockZ) { - if (biomes == null || biomes.length != 256) { - return -1; + if (dataVersion < 2202) { + if (biomes == null || biomes.length != 256) { + return -1; + } + return biomes[getBlockIndex(blockX, blockZ)]; + } else { + throw new IllegalStateException("cannot get biome using Chunk#getBiomeAt(int,int) from biome data with DataVersion of 2202 or higher, use Chunk#getBiomeAt(int,int,int) instead"); + } + } + + /** + * + * @param blockX The x-location of the block + * @param blockY The y-location of the block + * @param blockZ The z-location of the block + * @return The biome ID + */ + public int getBiomeAt(int blockX, int blockY, int blockZ) { + if (dataVersion < 2202) { + if (biomes == null || biomes.length != 256) { + return -1; + } + return biomes[getBlockIndex(blockX, blockZ)]; + } else { + if (biomes == null || biomes.length != 1024) { + return -1; + } + int biomeX = (blockX & 0xF) >> 2; + int biomeY = (blockY & 0xF) >> 2; + int biomeZ = (blockZ & 0xF) >> 2; + + return biomes[getBiomeIndex(biomeX, biomeY, biomeZ)]; } - return biomes[getBlockIndex(blockX, blockZ)]; } + @Deprecated public void setBiomeAt(int blockX, int blockZ, int biomeID) { - if (biomes == null || biomes.length != 256) { - biomes = new int[256]; - for (int i = 0; i < biomes.length; i++) { - biomes[i] = -1; + if (dataVersion < 2202) { + if (biomes == null || biomes.length != 256) { + biomes = new int[256]; + Arrays.fill(biomes, -1); + } + biomes[getBlockIndex(blockX, blockZ)] = biomeID; + } else { + if (biomes == null || biomes.length != 1024) { + biomes = new int[1024]; + Arrays.fill(biomes, -1); + } + + int biomeX = (blockX & 0xF) >> 2; + int biomeZ = (blockZ & 0xF) >> 2; + + for (int y = 0; y < 64; y++) { + biomes[getBiomeIndex(biomeX, y, biomeZ)] = biomeID; + } + } + } + + public void setBiomeAt(int blockX, int blockY, int blockZ, int biomeID) { + if (dataVersion < 2202) { + if (biomes == null || biomes.length != 256) { + biomes = new int[256]; + Arrays.fill(biomes, -1); } + biomes[getBlockIndex(blockX, blockZ)] = biomeID; + } else { + if (biomes == null || biomes.length != 1024) { + biomes = new int[1024]; + Arrays.fill(biomes, -1); + } + + int biomeX = (blockX & 0xF) >> 2; + int biomeZ = (blockZ & 0xF) >> 2; + + biomes[getBiomeIndex(biomeX, blockY, biomeZ)] = biomeID; } - biomes[getBlockIndex(blockX, blockZ)] = biomeID; + } + + int getBiomeIndex(int biomeX, int biomeY, int biomeZ) { + return biomeY * 64 + biomeX * 4 + biomeZ; } public CompoundTag getBlockStateAt(int blockX, int blockY, int blockZ) { diff --git a/src/main/java/net/querz/nbt/mca/CompressionType.java b/src/main/java/net/querz/mca/CompressionType.java similarity index 98% rename from src/main/java/net/querz/nbt/mca/CompressionType.java rename to src/main/java/net/querz/mca/CompressionType.java index c93c1ba3..9ae41d4c 100644 --- a/src/main/java/net/querz/nbt/mca/CompressionType.java +++ b/src/main/java/net/querz/mca/CompressionType.java @@ -1,4 +1,4 @@ -package net.querz.nbt.mca; +package net.querz.mca; import java.io.IOException; import java.io.InputStream; diff --git a/src/main/java/net/querz/nbt/mca/ExceptionFunction.java b/src/main/java/net/querz/mca/ExceptionFunction.java similarity index 80% rename from src/main/java/net/querz/nbt/mca/ExceptionFunction.java rename to src/main/java/net/querz/mca/ExceptionFunction.java index 679eac99..40fe8195 100644 --- a/src/main/java/net/querz/nbt/mca/ExceptionFunction.java +++ b/src/main/java/net/querz/mca/ExceptionFunction.java @@ -1,4 +1,4 @@ -package net.querz.nbt.mca; +package net.querz.mca; @FunctionalInterface public interface ExceptionFunction { diff --git a/src/main/java/net/querz/nbt/mca/MCAFile.java b/src/main/java/net/querz/mca/MCAFile.java similarity index 74% rename from src/main/java/net/querz/nbt/mca/MCAFile.java rename to src/main/java/net/querz/mca/MCAFile.java index 8710756a..4f03cd77 100644 --- a/src/main/java/net/querz/nbt/mca/MCAFile.java +++ b/src/main/java/net/querz/mca/MCAFile.java @@ -1,6 +1,6 @@ -package net.querz.nbt.mca; +package net.querz.mca; -import net.querz.nbt.CompoundTag; +import net.querz.nbt.tag.CompoundTag; import java.io.IOException; import java.io.RandomAccessFile; @@ -100,8 +100,7 @@ public int serialize(RandomAccessFile raf, boolean changeLastUpdate) throws IOEx chunksWritten++; -// we shouldn't add 1 in case of lastWritten % 4096 == 0 (see docs) - int sectors = (lastWritten >> 12) + (lastWritten % 4096 == 0 ? 0 : 1); + int sectors = (lastWritten >> 12) + 1; raf.seek(index * 4); raf.writeByte(globalOffset >>> 16); @@ -125,12 +124,6 @@ public int serialize(RandomAccessFile raf, boolean changeLastUpdate) throws IOEx return chunksWritten; } - /** - * Set a specific Chunk at a specific index. The index must be in range of 0 - 1023. - * @param index The index of the Chunk. - * @param chunk The Chunk to be set. - * @throws IndexOutOfBoundsException If index is not in the range. - */ public void setChunk(int index, Chunk chunk) { checkIndex(index); if (chunks == null) { @@ -139,13 +132,6 @@ public void setChunk(int index, Chunk chunk) { chunks[index] = chunk; } - /** - * Set a specific Chunk at a specific chunk location. - * The x- and z-value can be absolute chunk coordinates or they can be relative to the region origin. - * @param chunkX The x-coordinate of the Chunk. - * @param chunkZ The z-coordinate of the Chunk. - * @param chunk The chunk to be set. - */ public void setChunk(int chunkX, int chunkZ, Chunk chunk) { setChunk(getChunkIndex(chunkX, chunkZ), chunk); } @@ -202,22 +188,21 @@ private Chunk createChunkIfMissing(int blockX, int blockZ) { } /** - * Sets the biome at a specific block column. - * A negative number will be replaced by the columns default biome when loaded by Minecraft. - * @param blockX The x-coordinate of the block column. - * @param blockZ The z-coordinate of the block column. - * @param biomeID The biome id to be set. + * @deprecated Use {@link #setBiomeAt(int, int, int, int)} instead */ + @Deprecated public void setBiomeAt(int blockX, int blockZ, int biomeID) { createChunkIfMissing(blockX, blockZ).setBiomeAt(blockX, blockZ, biomeID); } + public void setBiomeAt(int blockX, int blockY, int blockZ, int biomeID) { + createChunkIfMissing(blockX, blockZ).setBiomeAt(blockX, blockY, blockZ, biomeID); + } + /** - * Fetches the biome id at a specific block column. - * @param blockX The x-coordinate of the block column. - * @param blockZ The z-coordinate of the block column. - * @return The biome id if the chunk exists and the chunk has biomes, otherwise -1. + * @deprecated Use {@link #getBiomeAt(int, int, int)} instead */ + @Deprecated public int getBiomeAt(int blockX, int blockZ) { int chunkX = MCAUtil.blockToChunk(blockX), chunkZ = MCAUtil.blockToChunk(blockZ); Chunk chunk = getChunk(getChunkIndex(chunkX, chunkZ)); @@ -227,27 +212,19 @@ public int getBiomeAt(int blockX, int blockZ) { return chunk.getBiomeAt(blockX, blockZ); } - /** - * Set a block state at a specific block location. - * The block coordinates can be absolute coordinates or they can be relative to the region. - * @param blockX The x-coordinate of the block. - * @param blockY The y-coordinate of the block. - * @param blockZ The z-coordinate of the block. - * @param state The block state to be set. - * @param cleanup Whether the Palette and the BLockStates should be recalculated after adding the block state. - */ + public int getBiomeAt(int blockX, int blockY, int blockZ) { + int chunkX = MCAUtil.blockToChunk(blockX), chunkZ = MCAUtil.blockToChunk(blockZ); + Chunk chunk = getChunk(getChunkIndex(chunkX, chunkZ)); + if (chunk == null) { + return -1; + } + return chunk.getBiomeAt(blockX,blockY, blockZ); + } + public void setBlockStateAt(int blockX, int blockY, int blockZ, CompoundTag state, boolean cleanup) { createChunkIfMissing(blockX, blockZ).setBlockStateAt(blockX, blockY, blockZ, state, cleanup); } - /** - * Fetches a block state at a specific block location. - * The block coordinates can be absolute coordinates or they can be relative to the region. - * @param blockX The x-coordinate of the block. - * @param blockY The y-coordinate of the block. - * @param blockZ The z-coordinate of the block. - * @return The block state or null if the chunk or the section do not exist. - */ public CompoundTag getBlockStateAt(int blockX, int blockY, int blockZ) { int chunkX = MCAUtil.blockToChunk(blockX), chunkZ = MCAUtil.blockToChunk(blockZ); Chunk chunk = getChunk(chunkX, chunkZ); @@ -257,9 +234,6 @@ public CompoundTag getBlockStateAt(int blockX, int blockY, int blockZ) { return chunk.getBlockStateAt(blockX, blockY, blockZ); } - /** - * Recalculates the Palette and the BlockStates of all chunks and sections of this region. - */ public void cleanupPalettesAndBlockStates() { for (Chunk chunk : chunks) { if (chunk != null) { diff --git a/src/main/java/net/querz/nbt/mca/MCAUtil.java b/src/main/java/net/querz/mca/MCAUtil.java similarity index 99% rename from src/main/java/net/querz/nbt/mca/MCAUtil.java rename to src/main/java/net/querz/mca/MCAUtil.java index fdd7142a..8dfcd058 100644 --- a/src/main/java/net/querz/nbt/mca/MCAUtil.java +++ b/src/main/java/net/querz/mca/MCAUtil.java @@ -1,4 +1,4 @@ -package net.querz.nbt.mca; +package net.querz.mca; import java.io.File; import java.io.IOException; diff --git a/src/main/java/net/querz/nbt/mca/Section.java b/src/main/java/net/querz/mca/Section.java similarity index 56% rename from src/main/java/net/querz/nbt/mca/Section.java rename to src/main/java/net/querz/mca/Section.java index 2858539e..1e8c9541 100644 --- a/src/main/java/net/querz/nbt/mca/Section.java +++ b/src/main/java/net/querz/mca/Section.java @@ -1,10 +1,7 @@ -package net.querz.nbt.mca; - -import net.querz.nbt.ByteArrayTag; -import net.querz.nbt.CompoundTag; -import net.querz.nbt.ListTag; -import net.querz.nbt.LongArrayTag; +package net.querz.mca; +import net.querz.nbt.tag.CompoundTag; +import net.querz.nbt.tag.ListTag; import java.util.ArrayList; import java.util.HashMap; import java.util.List; @@ -18,36 +15,24 @@ public class Section { private byte[] blockLight; private long[] blockStates; private byte[] skyLight; + private int dataVersion; - /** - * Creates a new Section based on raw section data. - * @param sectionRoot The raw section data - */ - public Section(CompoundTag sectionRoot) { - ListTag rawPalette = sectionRoot.getListTag("Palette"); - if (rawPalette == null) { - return; - } - palette = rawPalette.asCompoundTagList(); + public Section(CompoundTag sectionRoot, int dataVersion) { + data = sectionRoot; + this.dataVersion = dataVersion; + palette = sectionRoot.getListTag("Palette").asCompoundTagList(); for (int i = 0; i < palette.size(); i++) { CompoundTag data = palette.get(i); putValueIndexedPalette(data, i); } - -// we need null in case if there's no such byteArrays. TODO: add getOrNull - ByteArrayTag blockLightTag = sectionRoot.getByteArrayTag("BlockLight"); - LongArrayTag blockStatesTag = sectionRoot.getLongArrayTag("BlockStates"); - ByteArrayTag skyLightTag = sectionRoot.getByteArrayTag("SkyLight"); - - this.blockLight = blockLightTag != null ? blockLightTag.getValue() : null; - this.blockStates = blockStatesTag != null ? blockStatesTag.getValue() : null; - this.skyLight = skyLightTag != null ? skyLightTag.getValue() : null; - data = sectionRoot; + blockLight = sectionRoot.getByteArray("BlockLight"); + blockStates = sectionRoot.getLongArray("BlockStates"); + skyLight = sectionRoot.getByteArray("SkyLight"); } Section() {} - private void putValueIndexedPalette(CompoundTag data, int index) { + void putValueIndexedPalette(CompoundTag data, int index) { PaletteIndex leaf = new PaletteIndex(data, index); String name = data.getString("Name"); List leaves = valueIndexedPalette.get(name); @@ -65,7 +50,7 @@ private void putValueIndexedPalette(CompoundTag data, int index) { } } - private PaletteIndex getValueIndexedPalette(CompoundTag data) { + PaletteIndex getValueIndexedPalette(CompoundTag data) { List leaves = valueIndexedPalette.get(data.getString("Name")); if (leaves == null) { return null; @@ -89,38 +74,12 @@ private class PaletteIndex { } } - /** - * Checks whether the data of this Section is empty. - * @return true if empty - */ - public boolean isEmpty() { - return data == null; - } - - /** - * Fetches a block state based on a block location from this section. - * The coordinates represent the location of the block inside of this Section. - * @param blockX The x-coordinate of the block in this Section - * @param blockY The y-coordinate of the block in this Section - * @param blockZ The z-coordinate of the block in this Section - * @return The block state data of this block. - */ public CompoundTag getBlockStateAt(int blockX, int blockY, int blockZ) { int index = getBlockIndex(blockX, blockY, blockZ); int paletteIndex = getPaletteIndex(index); return palette.get(paletteIndex); } - /** - * Attempts to add a block state for a specific block location in this Section. - * @param blockX The x-coordinate of the block in this Section - * @param blockY The y-coordinate of the block in this Section - * @param blockZ The z-coordinate of the block in this Section - * @param state The block state to be set - * @param cleanup When true, it will cleanup the palette of this section. - * This option should only be used moderately to avoid unnecessary recalculation of the palette indices. - * Recalculating the Palette should only be executed once right before saving the Section to file. - */ public void setBlockStateAt(int blockX, int blockY, int blockZ, CompoundTag state, boolean cleanup) { int paletteSizeBefore = palette.size(); int paletteIndex = addToPalette(state); @@ -146,15 +105,23 @@ public void setBlockStateAt(int blockX, int blockY, int blockZ, CompoundTag stat * */ public int getPaletteIndex(int blockStateIndex) { int bits = blockStates.length >> 6; - double blockStatesIndex = blockStateIndex / (4096D / blockStates.length); - int longIndex = (int) blockStatesIndex; - int startBit = (int) ((blockStatesIndex - Math.floor(blockStatesIndex)) * 64D); - if (startBit + bits > 64) { - long prev = bitRange(blockStates[longIndex], startBit, 64); - long next = bitRange(blockStates[longIndex + 1], 0, startBit + bits - 64); - return (int) ((next << 64 - startBit) + prev); + + if (dataVersion < 2527) { + double blockStatesIndex = blockStateIndex / (4096D / blockStates.length); + int longIndex = (int) blockStatesIndex; + int startBit = (int) ((blockStatesIndex - Math.floor(blockStatesIndex)) * 64D); + if (startBit + bits > 64) { + long prev = bitRange(blockStates[longIndex], startBit, 64); + long next = bitRange(blockStates[longIndex + 1], 0, startBit + bits - 64); + return (int) ((next << 64 - startBit) + prev); + } else { + return (int) bitRange(blockStates[longIndex], startBit, startBit + bits); + } } else { - return (int) bitRange(blockStates[longIndex], startBit, startBit + bits); + int indicesPerLong = (int) (64D / bits); + int blockStatesIndex = blockStateIndex / indicesPerLong; + int startBit = (blockStateIndex % indicesPerLong) * bits; + return (int) bitRange(blockStates[blockStatesIndex], startBit, startBit + bits); } } @@ -165,23 +132,27 @@ public int getPaletteIndex(int blockStateIndex) { * @param blockStates The block states to be updated. * */ public void setPaletteIndex(int blockIndex, int paletteIndex, long[] blockStates) { - int bits = blockStates.length / 64; - double blockStatesIndex = blockIndex / (4096D / blockStates.length); - int longIndex = (int) blockStatesIndex; - int startBit = (int) ((blockStatesIndex - Math.floor(longIndex)) * 64D); - if (startBit + bits > 64) { - blockStates[longIndex] = updateBits(blockStates[longIndex], paletteIndex, startBit, 64); - blockStates[longIndex + 1] = updateBits(blockStates[longIndex + 1], paletteIndex, startBit - 64, startBit + bits - 64); + int bits = blockStates.length >> 6; + + if (dataVersion < 2527) { + double blockStatesIndex = blockIndex / (4096D / blockStates.length); + int longIndex = (int) blockStatesIndex; + int startBit = (int) ((blockStatesIndex - Math.floor(longIndex)) * 64D); + if (startBit + bits > 64) { + blockStates[longIndex] = updateBits(blockStates[longIndex], paletteIndex, startBit, 64); + blockStates[longIndex + 1] = updateBits(blockStates[longIndex + 1], paletteIndex, startBit - 64, startBit + bits - 64); + } else { + blockStates[longIndex] = updateBits(blockStates[longIndex], paletteIndex, startBit, startBit + bits); + } } else { - blockStates[longIndex] = updateBits(blockStates[longIndex], paletteIndex, startBit, startBit + bits); + int indicesPerLong = (int) (64D / bits); + int blockStatesIndex = blockIndex / indicesPerLong; + int startBit = (blockIndex % indicesPerLong) * bits; + blockStates[blockStatesIndex] = updateBits(blockStates[blockStatesIndex], paletteIndex, startBit, startBit + bits); } } - /** - * Fetches the palette of this Section. - * @return The palette of this Section. - */ - public ListTag getPalette() { + ListTag getPalette() { return palette; } @@ -195,32 +166,27 @@ int addToPalette(CompoundTag data) { return palette.size() - 1; } - private int getBlockIndex(int blockX, int blockY, int blockZ) { + int getBlockIndex(int blockX, int blockY, int blockZ) { return (blockY & 0xF) * 256 + (blockZ & 0xF) * 16 + (blockX & 0xF); } - private static long updateBits(long n, long m, int i, int j) { + static long updateBits(long n, long m, int i, int j) { //replace i to j in n with j - i bits of m long mShifted = i > 0 ? (m & ((1L << j - i) - 1)) << i : (m & ((1L << j - i) - 1)) >>> -i; return ((n & ((j > 63 ? 0 : (~0L << j)) | (i < 0 ? 0 : ((1L << i) - 1L)))) | mShifted); } - private static long bitRange(long value, int from, int to) { + static long bitRange(long value, int from, int to) { int waste = 64 - to; return (value << waste) >>> (waste + from); } - /** - * This method recalculates the palette and its indices. - * This should only be used moderately to avoid unnecessary recalculation of the palette indices. - * Recalculating the Palette should only be executed once right before saving the Section to file. - */ public void cleanupPaletteAndBlockStates() { Map oldToNewMapping = cleanupPalette(); adjustBlockStateBits(oldToNewMapping, blockStates); } - private Map cleanupPalette() { + Map cleanupPalette() { //create index - palette mapping Map allIndices = new HashMap<>(); for (int i = 0; i < 4096; i++) { @@ -246,7 +212,7 @@ private Map cleanupPalette() { return allIndices; } - private void adjustBlockStateBits(Map oldToNewMapping, long[] blockStates) { + void adjustBlockStateBits(Map oldToNewMapping, long[] blockStates) { //increases or decreases the amount of bits used per BlockState //based on the size of the palette. oldToNewMapping can be used to update indices //if the palette had been cleaned up before using MCAFile#cleanupPalette(). @@ -267,18 +233,10 @@ private void adjustBlockStateBits(Map oldToNewMapping, long[] this.blockStates = newBlockStates; } - /** - * @return The block light array of this Section - */ public byte[] getBlockLight() { return blockLight; } - /** - * Sets the block light array for this section. - * @param blockLight The block light array - * @throws IllegalArgumentException When the length of the array is not 2048 - */ public void setBlockLight(byte[] blockLight) { if (blockLight != null && blockLight.length != 2048) { throw new IllegalArgumentException("BlockLight array must have a length of 2048"); @@ -286,19 +244,10 @@ public void setBlockLight(byte[] blockLight) { this.blockLight = blockLight; } - /** - * @return The indices of the block states of this Section. - */ public long[] getBlockStates() { return blockStates; } - /** - * Sets the block state indices to a custom value. - * @param blockStates The block state indices. - * @throws NullPointerException If blockStates is null - * @throws IllegalArgumentException When blockStates' length is < 256 or > 4096 and is not a multiple of 64 - */ public void setBlockStates(long[] blockStates) { if (blockStates == null) { throw new NullPointerException("BlockStates cannot be null"); @@ -308,18 +257,10 @@ public void setBlockStates(long[] blockStates) { this.blockStates = blockStates; } - /** - * @return The sky light values of this Section - */ public byte[] getSkyLight() { return skyLight; } - /** - * Sets the sky light values of this section. - * @param skyLight The custom sky light values - * @throws IllegalArgumentException If the length of the array is not 2048 - */ public void setSkyLight(byte[] skyLight) { if (skyLight != null && skyLight.length != 2048) { throw new IllegalArgumentException("SkyLight array must have a length of 2048"); @@ -327,10 +268,6 @@ public void setSkyLight(byte[] skyLight) { this.skyLight = skyLight; } - /** - * Creates an empty Section with base values. - * @return An empty Section - */ public static Section newSection() { Section s = new Section(); s.blockStates = new long[256]; @@ -342,13 +279,6 @@ public static Section newSection() { return s; } - /** - * Updates the raw CompoundTag that this Section is based on. - * This must be called before saving a Section to disk if the Section was manually created - * to set the Y of this Section. - * @param y The Y-value of this Section - * @return A reference to the raw CompoundTag this Section is based on - */ public CompoundTag updateHandle(int y) { data.putByte("Y", (byte) y); data.put("Palette", palette); diff --git a/src/main/java/net/querz/nbt/custom/CharTag.java b/src/main/java/net/querz/nbt/custom/CharTag.java deleted file mode 100644 index 56e8a3b7..00000000 --- a/src/main/java/net/querz/nbt/custom/CharTag.java +++ /dev/null @@ -1,66 +0,0 @@ -package net.querz.nbt.custom; - -import net.querz.nbt.Tag; -import net.querz.nbt.io.NBTInputStream; -import net.querz.nbt.io.NBTOutputStream; -import net.querz.nbt.io.NBTUtil; -import java.io.IOException; - -public class CharTag extends Tag implements Comparable { - - public static final char ZERO_VALUE = '\u0000'; - - public static void register() { - NBTUtil.registerCustomTag(110, (o, t, m) -> serialize(o, t), (i, m) -> deserialize(i), CharTag.class); - } - - public CharTag() { - super(ZERO_VALUE); - } - - public CharTag(char value) { - super(value); - } - - @Override - public byte getID() { - return 110; - } - - @Override - public Character getValue() { - return super.getValue(); - } - - public void setValue(char value) { - super.setValue(value); - } - - public static void serialize(NBTOutputStream dos, CharTag tag) throws IOException { - dos.writeChar(tag.getValue()); - } - - public static CharTag deserialize(NBTInputStream dis) throws IOException { - return new CharTag(dis.readChar()); - } - - @Override - public String valueToString(int maxDepth) { - return escapeString(getValue() + "", false); - } - - @Override - public boolean equals(Object other) { - return super.equals(other) && getValue() == ((CharTag) other).getValue(); - } - - @Override - public int compareTo(CharTag o) { - return Character.compare(getValue(), o.getValue()); - } - - @Override - public CharTag clone() { - return new CharTag(getValue()); - } -} diff --git a/src/main/java/net/querz/nbt/custom/ObjectTag.java b/src/main/java/net/querz/nbt/custom/ObjectTag.java deleted file mode 100644 index 9a9a057e..00000000 --- a/src/main/java/net/querz/nbt/custom/ObjectTag.java +++ /dev/null @@ -1,126 +0,0 @@ -package net.querz.nbt.custom; - -import net.querz.nbt.Tag; -import net.querz.nbt.io.NBTOutputStream; -import net.querz.nbt.io.NBTUtil; -import java.io.DataInputStream; -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 implements Comparable> { - - public static void register() { - NBTUtil.registerCustomTag(90, (o, t, m) -> serialize(o, t), (i, m) -> deserialize(i), ObjectTag.class); - } - - public ObjectTag() { - super(null); - } - - public ObjectTag(T value) { - super(value); - } - - @Override - public byte getID() { - return 90; - } - - private static ObjectTag createUnchecked(O value) { - return new ObjectTag<>(value); - } - - @Override - protected T checkValue(T value) { - return value; - } - - @Override - public T getValue() { - return super.getValue(); - } - - @Override - public void setValue(T value) { - super.setValue(value); - } - - @SuppressWarnings("unchecked") - public ObjectTag asTypedObjectTag(Class type) { - checkTypeClass(type); - return (ObjectTag) this; - } - - public static void serialize(NBTOutputStream dos, ObjectTag tag) throws IOException { - new ObjectOutputStream(dos).writeObject(tag.getValue()); - } - - @SuppressWarnings("unchecked") - public static ObjectTag deserialize(DataInputStream dis) throws IOException { - try { - return createUnchecked((Serializable) new ObjectInputStream(dis).readObject()); - } catch (InvalidClassException | ClassNotFoundException e) { - throw new IOException(e.getCause()); - } - } - - @Override - public String valueToString(int maxDepth) { - return getValue() == null ? "null" : escapeString(getValue().toString(), false); - } - - @Override - public boolean equals(Object other) { - return super.equals(other) && Objects.equals(getValue(), ((ObjectTag) other).getValue()); - } - - @Override - public int hashCode() { - if (getValue() == null) { - return 0; - } - return getValue().hashCode(); - } - - @SuppressWarnings("unchecked") - @Override - 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; - } - - @SuppressWarnings("unchecked") - @Override - public ObjectTag clone() { - if (getValue() == null) { - return new ObjectTag<>(); - } - try { - return new ObjectTag<>((T) getValue().getClass().getMethod("clone").invoke(getValue())); - } catch (IllegalAccessException | InvocationTargetException | NoSuchMethodException e) { - return new ObjectTag<>(getValue()); - } - } - - private void checkTypeClass(Class clazz) { - if (getValue() != null && (!clazz.isAssignableFrom(getValue().getClass()))) { - throw new ClassCastException(String.format( - "cannot cast ObjectTag<%s> to ObjectTag<%s>", - getValue().getClass().getSimpleName(), clazz.getSimpleName())); - } - } -} diff --git a/src/main/java/net/querz/nbt/custom/ShortArrayTag.java b/src/main/java/net/querz/nbt/custom/ShortArrayTag.java deleted file mode 100644 index 6b748d01..00000000 --- a/src/main/java/net/querz/nbt/custom/ShortArrayTag.java +++ /dev/null @@ -1,68 +0,0 @@ -package net.querz.nbt.custom; - -import net.querz.nbt.ArrayTag; -import net.querz.nbt.io.NBTInputStream; -import net.querz.nbt.io.NBTOutputStream; -import net.querz.nbt.io.NBTUtil; -import java.io.IOException; -import java.util.Arrays; - -public class ShortArrayTag extends ArrayTag implements Comparable { - - public static final short[] ZERO_VALUE = new short[0]; - - public static void register() { - NBTUtil.registerCustomTag(100, (o, t, m) -> serialize(o, t), (i, m) -> deserialize(i), ShortArrayTag.class); - } - - public ShortArrayTag() { - super(ZERO_VALUE); - } - - public ShortArrayTag(short[] value) { - super(value); - } - - @Override - public byte getID() { - return 100; - } - - public static void serialize(NBTOutputStream dos, ShortArrayTag tag) throws IOException { - dos.writeInt(tag.length()); - for (int i : tag.getValue()) { - dos.writeShort(i); - } - } - - public static ShortArrayTag deserialize(NBTInputStream dis) throws IOException { - ShortArrayTag tag = new ShortArrayTag(new short[dis.readInt()]); - for (int i = 0; i < tag.length(); i++) { - tag.getValue()[i] = dis.readShort(); - } - return tag; - } - - @Override - public boolean equals(Object other) { - return super.equals(other) - && (getValue() == ((ShortArrayTag) other).getValue() - || getValue().length == (((ShortArrayTag) other).length()) - && Arrays.equals(getValue(), ((ShortArrayTag) other).getValue())); - } - - @Override - 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 deleted file mode 100644 index 6218ab8f..00000000 --- a/src/main/java/net/querz/nbt/custom/StructTag.java +++ /dev/null @@ -1,300 +0,0 @@ -package net.querz.nbt.custom; - -import net.querz.nbt.ByteArrayTag; -import net.querz.nbt.ByteTag; -import net.querz.nbt.CompoundTag; -import net.querz.nbt.DoubleTag; -import net.querz.nbt.FloatTag; -import net.querz.nbt.IntArrayTag; -import net.querz.nbt.IntTag; -import net.querz.nbt.ListTag; -import net.querz.nbt.LongArrayTag; -import net.querz.nbt.LongTag; -import net.querz.nbt.ShortTag; -import net.querz.nbt.StringTag; -import net.querz.nbt.Tag; -import net.querz.io.MaxDepthIO; -import net.querz.nbt.io.NBTInputStream; -import net.querz.nbt.io.NBTOutputStream; -import net.querz.nbt.io.NBTUtil; -import java.io.IOException; -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>, Comparable, MaxDepthIO { - - public static void register() { - NBTUtil.registerCustomTag(120, StructTag::serialize, StructTag::deserialize, StructTag.class); - } - - public StructTag() { - super(createEmptyValue()); - } - - @Override - public byte getID() { - return 120; - } - - private static List> createEmptyValue() { - return new ArrayList<>(3); - } - - public int size() { - return getValue().size(); - } - - public Tag remove(int index) { - return getValue().remove(index); - } - - public boolean remove(Tag tag) { - return getValue().remove(tag); - } - - public void clear() { - getValue().clear(); - } - - public boolean contains(Tag tag) { - return getValue().contains(tag); - } - - public boolean containsAll(Collection> tags) { - return getValue().containsAll(tags); - } - - @Override - public Iterator> iterator() { - return getValue().iterator(); - } - - @Override - public void forEach(Consumer> action) { - getValue().forEach(action); - } - - public > S get(int index, Class type) { - Tag t = getValue().get(index); - return type.cast(t); - } - - public Tag get(int index) { - return getValue().get(index); - } - - public ByteTag getByteTag(int index) { - return get(index, ByteTag.class); - } - - public ShortTag getShortTag(int index) { - return get(index, ShortTag.class); - } - - public IntTag getIntTag(int index) { - return get(index, IntTag.class); - } - - public LongTag getLongTag(int index) { - return get(index, LongTag.class); - } - - public FloatTag getFloatTag(int index) { - return get(index, FloatTag.class); - } - - public DoubleTag getDoubleTag(int index) { - return get(index, DoubleTag.class); - } - - public StringTag getStringTag(int index) { - return get(index, StringTag.class); - } - - public ByteArrayTag getByteArrayTag(int index) { - return get(index, ByteArrayTag.class); - } - - public IntArrayTag getIntArrayTag(int index) { - return get(index, IntArrayTag.class); - } - - public LongArrayTag getLongArrayTag(int index) { - return get(index, LongArrayTag.class); - } - - public ListTag getListTag(int index) { - return get(index, ListTag.class); - } - - public CompoundTag getCompoundTag(int index) { - return get(index, CompoundTag.class); - } - - public boolean getBoolean(int index) { - Tag t = get(index); - return t instanceof ByteTag && ((ByteTag) t).asByte() > 0; - } - - public byte getByte(int index) { - return getByteTag(index).asByte(); - } - - public short getShort(int index) { - return getShortTag(index).asShort(); - } - - public int getInt(int index) { - return getIntTag(index).asInt(); - } - - public long getLong(int index) { - return getLongTag(index).asLong(); - } - - public float getFloat(int index) { - return getFloatTag(index).asFloat(); - } - - public double getDouble(int index) { - return getDoubleTag(index).asDouble(); - } - - public String getString(int index) { - return getStringTag(index).getValue(); - } - - public byte[] getByteArray(int index) { - return getByteArrayTag(index).getValue(); - } - - public int[] getIntArray(int index) { - return getIntArrayTag(index).getValue(); - } - - public long[] getLongArray(int index) { - return getLongArrayTag(index).getValue(); - } - - public Tag set(int index, Tag tag) { - return getValue().set(index, Objects.requireNonNull(tag)); - } - - public void add(Tag tag) { - getValue().add(Objects.requireNonNull(tag)); - } - - public void add(int index, Tag tag) { - getValue().add(index, Objects.requireNonNull(tag)); - } - - public void addBoolean(boolean value) { - add(new ByteTag(value)); - } - - public void addByte(byte value) { - add(new ByteTag(value)); - } - - public void addShort(short value) { - add(new ShortTag(value)); - } - - public void addInt(int value) { - add(new IntTag(value)); - } - - public void addLong(long value) { - add(new LongTag(value)); - } - - public void addFloat(float value) { - add(new FloatTag(value)); - } - - public void addDouble(double value) { - add(new DoubleTag(value)); - } - - public void addString(String value) { - add(new StringTag(value)); - } - - public void addByteArray(byte[] value) { - add(new ByteArrayTag(value)); - } - - public void addIntArray(int[] value) { - add(new IntArrayTag(value)); - } - - public void addLongArray(long[] value) { - add(new LongArrayTag(value)); - } - - public static void serialize(NBTOutputStream dos, StructTag tag, int maxDepth) throws IOException { - dos.writeInt(tag.size()); - for (Tag t : tag.getValue()) { - dos.writeByte(t.getID()); - dos.writeRawTag(t, dos.decrementMaxDepth(maxDepth)); - } - } - - public static StructTag deserialize(NBTInputStream dis, int maxDepth) throws IOException { - int size = dis.readInt(); - size = size < 0 ? 0 : size; - StructTag t = new StructTag(); - t.setValue(new ArrayList<>(size)); - for (int i = 0; i < size; i++) { - Tag tag = dis.readRawTag(dis.decrementMaxDepth(maxDepth)); - t.add(tag); - } - return t; - } - - @Override - public String valueToString(int maxDepth) { - StringBuilder sb = new StringBuilder("["); - for (int i = 0; i < size(); i++) { - sb.append(i > 0 ? "," : "").append(get(i).toString(decrementMaxDepth(maxDepth))); - } - sb.append("]"); - return sb.toString(); - } - - @Override - public boolean equals(Object other) { - if (!super.equals(other) || size() != ((StructTag) other).size()) { - return false; - } - for (int i = 0; i < size(); i++) { - if (!get(i).equals(((StructTag) other).get(i))) { - return false; - } - } - return true; - } - - @Override - public int hashCode() { - return getValue().hashCode(); - } - - @Override - public int compareTo(StructTag o) { - return Integer.compare(size(), o.size()); - } - - @Override - public StructTag clone() { - StructTag copy = new StructTag(); - for (Tag tag : getValue()) { - copy.add(tag.clone()); - } - return copy; - } -} diff --git a/src/main/java/net/querz/nbt/io/MSONDeserializer.java b/src/main/java/net/querz/nbt/io/MSONDeserializer.java index 071d6f4f..2ca11f2e 100644 --- a/src/main/java/net/querz/nbt/io/MSONDeserializer.java +++ b/src/main/java/net/querz/nbt/io/MSONDeserializer.java @@ -1,7 +1,7 @@ package net.querz.nbt.io; import net.querz.io.StringDeserializer; -import net.querz.nbt.Tag; +import net.querz.nbt.tag.Tag; import java.io.BufferedReader; import java.io.IOException; import java.io.Reader; diff --git a/src/main/java/net/querz/nbt/io/MSONParser.java b/src/main/java/net/querz/nbt/io/MSONParser.java index 6e28c160..feafce24 100644 --- a/src/main/java/net/querz/nbt/io/MSONParser.java +++ b/src/main/java/net/querz/nbt/io/MSONParser.java @@ -1,21 +1,21 @@ package net.querz.nbt.io; import net.querz.io.MaxDepthIO; -import net.querz.nbt.ArrayTag; -import net.querz.nbt.ByteArrayTag; -import net.querz.nbt.ByteTag; -import net.querz.nbt.CompoundTag; -import net.querz.nbt.DoubleTag; -import net.querz.nbt.EndTag; -import net.querz.nbt.FloatTag; -import net.querz.nbt.IntArrayTag; -import net.querz.nbt.IntTag; -import net.querz.nbt.ListTag; -import net.querz.nbt.LongArrayTag; -import net.querz.nbt.LongTag; -import net.querz.nbt.ShortTag; -import net.querz.nbt.StringTag; -import net.querz.nbt.Tag; +import net.querz.nbt.tag.ArrayTag; +import net.querz.nbt.tag.ByteArrayTag; +import net.querz.nbt.tag.ByteTag; +import net.querz.nbt.tag.CompoundTag; +import net.querz.nbt.tag.DoubleTag; +import net.querz.nbt.tag.EndTag; +import net.querz.nbt.tag.FloatTag; +import net.querz.nbt.tag.IntArrayTag; +import net.querz.nbt.tag.IntTag; +import net.querz.nbt.tag.ListTag; +import net.querz.nbt.tag.LongArrayTag; +import net.querz.nbt.tag.LongTag; +import net.querz.nbt.tag.ShortTag; +import net.querz.nbt.tag.StringTag; +import net.querz.nbt.tag.Tag; import java.util.ArrayList; import java.util.List; import java.util.regex.Pattern; diff --git a/src/main/java/net/querz/nbt/io/MSONSerializer.java b/src/main/java/net/querz/nbt/io/MSONSerializer.java index ad35f7b4..95f353b8 100644 --- a/src/main/java/net/querz/nbt/io/MSONSerializer.java +++ b/src/main/java/net/querz/nbt/io/MSONSerializer.java @@ -1,7 +1,7 @@ package net.querz.nbt.io; import net.querz.io.StringSerializer; -import net.querz.nbt.Tag; +import net.querz.nbt.tag.Tag; import java.io.IOException; import java.io.Writer; diff --git a/src/main/java/net/querz/nbt/io/MSONWriter.java b/src/main/java/net/querz/nbt/io/MSONWriter.java index ffde0923..e0c45502 100644 --- a/src/main/java/net/querz/nbt/io/MSONWriter.java +++ b/src/main/java/net/querz/nbt/io/MSONWriter.java @@ -1,20 +1,20 @@ package net.querz.nbt.io; import net.querz.io.MaxDepthIO; -import net.querz.nbt.ByteArrayTag; -import net.querz.nbt.ByteTag; -import net.querz.nbt.CompoundTag; -import net.querz.nbt.DoubleTag; -import net.querz.nbt.EndTag; -import net.querz.nbt.FloatTag; -import net.querz.nbt.IntArrayTag; -import net.querz.nbt.IntTag; -import net.querz.nbt.ListTag; -import net.querz.nbt.LongArrayTag; -import net.querz.nbt.LongTag; -import net.querz.nbt.ShortTag; -import net.querz.nbt.StringTag; -import net.querz.nbt.Tag; +import net.querz.nbt.tag.ByteArrayTag; +import net.querz.nbt.tag.ByteTag; +import net.querz.nbt.tag.CompoundTag; +import net.querz.nbt.tag.DoubleTag; +import net.querz.nbt.tag.EndTag; +import net.querz.nbt.tag.FloatTag; +import net.querz.nbt.tag.IntArrayTag; +import net.querz.nbt.tag.IntTag; +import net.querz.nbt.tag.ListTag; +import net.querz.nbt.tag.LongArrayTag; +import net.querz.nbt.tag.LongTag; +import net.querz.nbt.tag.ShortTag; +import net.querz.nbt.tag.StringTag; +import net.querz.nbt.tag.Tag; import java.io.IOException; import java.io.Writer; import java.lang.reflect.Array; diff --git a/src/main/java/net/querz/nbt/io/NBTDeserializer.java b/src/main/java/net/querz/nbt/io/NBTDeserializer.java index 97abaedf..2f7289e3 100644 --- a/src/main/java/net/querz/nbt/io/NBTDeserializer.java +++ b/src/main/java/net/querz/nbt/io/NBTDeserializer.java @@ -1,7 +1,7 @@ package net.querz.nbt.io; import net.querz.io.Deserializer; -import net.querz.nbt.Tag; +import net.querz.nbt.tag.Tag; import java.io.IOException; import java.io.InputStream; import java.util.zip.GZIPInputStream; diff --git a/src/main/java/net/querz/nbt/io/NBTInputStream.java b/src/main/java/net/querz/nbt/io/NBTInputStream.java index a97cbae8..c14a288f 100644 --- a/src/main/java/net/querz/nbt/io/NBTInputStream.java +++ b/src/main/java/net/querz/nbt/io/NBTInputStream.java @@ -2,20 +2,20 @@ import net.querz.io.ExceptionBiFunction; import net.querz.io.MaxDepthIO; -import net.querz.nbt.ByteArrayTag; -import net.querz.nbt.ByteTag; -import net.querz.nbt.CompoundTag; -import net.querz.nbt.DoubleTag; -import net.querz.nbt.EndTag; -import net.querz.nbt.FloatTag; -import net.querz.nbt.IntArrayTag; -import net.querz.nbt.IntTag; -import net.querz.nbt.ListTag; -import net.querz.nbt.LongArrayTag; -import net.querz.nbt.LongTag; -import net.querz.nbt.ShortTag; -import net.querz.nbt.StringTag; -import net.querz.nbt.Tag; +import net.querz.nbt.tag.ByteArrayTag; +import net.querz.nbt.tag.ByteTag; +import net.querz.nbt.tag.CompoundTag; +import net.querz.nbt.tag.DoubleTag; +import net.querz.nbt.tag.EndTag; +import net.querz.nbt.tag.FloatTag; +import net.querz.nbt.tag.IntArrayTag; +import net.querz.nbt.tag.IntTag; +import net.querz.nbt.tag.ListTag; +import net.querz.nbt.tag.LongArrayTag; +import net.querz.nbt.tag.LongTag; +import net.querz.nbt.tag.ShortTag; +import net.querz.nbt.tag.StringTag; +import net.querz.nbt.tag.Tag; import java.io.DataInputStream; import java.io.IOException; import java.io.InputStream; diff --git a/src/main/java/net/querz/nbt/io/NBTOutputStream.java b/src/main/java/net/querz/nbt/io/NBTOutputStream.java index 00f8af88..2fa13833 100644 --- a/src/main/java/net/querz/nbt/io/NBTOutputStream.java +++ b/src/main/java/net/querz/nbt/io/NBTOutputStream.java @@ -2,20 +2,20 @@ import net.querz.io.ExceptionTriConsumer; import net.querz.io.MaxDepthIO; -import net.querz.nbt.ByteArrayTag; -import net.querz.nbt.ByteTag; -import net.querz.nbt.CompoundTag; -import net.querz.nbt.DoubleTag; -import net.querz.nbt.EndTag; -import net.querz.nbt.FloatTag; -import net.querz.nbt.IntArrayTag; -import net.querz.nbt.IntTag; -import net.querz.nbt.ListTag; -import net.querz.nbt.LongArrayTag; -import net.querz.nbt.LongTag; -import net.querz.nbt.ShortTag; -import net.querz.nbt.StringTag; -import net.querz.nbt.Tag; +import net.querz.nbt.tag.ByteArrayTag; +import net.querz.nbt.tag.ByteTag; +import net.querz.nbt.tag.CompoundTag; +import net.querz.nbt.tag.DoubleTag; +import net.querz.nbt.tag.EndTag; +import net.querz.nbt.tag.FloatTag; +import net.querz.nbt.tag.IntArrayTag; +import net.querz.nbt.tag.IntTag; +import net.querz.nbt.tag.ListTag; +import net.querz.nbt.tag.LongArrayTag; +import net.querz.nbt.tag.LongTag; +import net.querz.nbt.tag.ShortTag; +import net.querz.nbt.tag.StringTag; +import net.querz.nbt.tag.Tag; import java.io.DataOutputStream; import java.io.IOException; import java.io.OutputStream; diff --git a/src/main/java/net/querz/nbt/io/NBTSerializer.java b/src/main/java/net/querz/nbt/io/NBTSerializer.java index f35998b4..1328ec77 100644 --- a/src/main/java/net/querz/nbt/io/NBTSerializer.java +++ b/src/main/java/net/querz/nbt/io/NBTSerializer.java @@ -1,7 +1,7 @@ package net.querz.nbt.io; import net.querz.io.Serializer; -import net.querz.nbt.Tag; +import net.querz.nbt.tag.Tag; import java.io.IOException; import java.io.OutputStream; import java.util.zip.GZIPOutputStream; diff --git a/src/main/java/net/querz/nbt/io/NBTUtil.java b/src/main/java/net/querz/nbt/io/NBTUtil.java index 819b91b3..f517e129 100644 --- a/src/main/java/net/querz/nbt/io/NBTUtil.java +++ b/src/main/java/net/querz/nbt/io/NBTUtil.java @@ -2,7 +2,7 @@ import net.querz.io.ExceptionBiFunction; import net.querz.io.ExceptionTriConsumer; -import net.querz.nbt.Tag; +import net.querz.nbt.tag.Tag; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; diff --git a/src/main/java/net/querz/nbt/io/NamedTag.java b/src/main/java/net/querz/nbt/io/NamedTag.java index c6f3f9ec..b1873087 100644 --- a/src/main/java/net/querz/nbt/io/NamedTag.java +++ b/src/main/java/net/querz/nbt/io/NamedTag.java @@ -1,6 +1,6 @@ package net.querz.nbt.io; -import net.querz.nbt.Tag; +import net.querz.nbt.tag.Tag; public class NamedTag { diff --git a/src/main/java/net/querz/nbt/io/StringPointer.java b/src/main/java/net/querz/nbt/io/StringPointer.java index ed6dc211..11bdddcc 100644 --- a/src/main/java/net/querz/nbt/io/StringPointer.java +++ b/src/main/java/net/querz/nbt/io/StringPointer.java @@ -1,7 +1,5 @@ package net.querz.nbt.io; -import net.querz.nbt.io.ParseException; - public class StringPointer { private String value; diff --git a/src/main/java/net/querz/nbt/ArrayTag.java b/src/main/java/net/querz/nbt/tag/ArrayTag.java similarity index 97% rename from src/main/java/net/querz/nbt/ArrayTag.java rename to src/main/java/net/querz/nbt/tag/ArrayTag.java index b08d9fc8..2842fa63 100644 --- a/src/main/java/net/querz/nbt/ArrayTag.java +++ b/src/main/java/net/querz/nbt/tag/ArrayTag.java @@ -1,4 +1,4 @@ -package net.querz.nbt; +package net.querz.nbt.tag; import java.lang.reflect.Array; diff --git a/src/main/java/net/querz/nbt/ByteArrayTag.java b/src/main/java/net/querz/nbt/tag/ByteArrayTag.java similarity index 96% rename from src/main/java/net/querz/nbt/ByteArrayTag.java rename to src/main/java/net/querz/nbt/tag/ByteArrayTag.java index cccb8e29..8fbcf8a3 100644 --- a/src/main/java/net/querz/nbt/ByteArrayTag.java +++ b/src/main/java/net/querz/nbt/tag/ByteArrayTag.java @@ -1,4 +1,4 @@ -package net.querz.nbt; +package net.querz.nbt.tag; import java.util.Arrays; diff --git a/src/main/java/net/querz/nbt/ByteTag.java b/src/main/java/net/querz/nbt/tag/ByteTag.java similarity index 96% rename from src/main/java/net/querz/nbt/ByteTag.java rename to src/main/java/net/querz/nbt/tag/ByteTag.java index 859086ca..207cefd2 100644 --- a/src/main/java/net/querz/nbt/ByteTag.java +++ b/src/main/java/net/querz/nbt/tag/ByteTag.java @@ -1,4 +1,4 @@ -package net.querz.nbt; +package net.querz.nbt.tag; public class ByteTag extends NumberTag implements Comparable { diff --git a/src/main/java/net/querz/nbt/CompoundTag.java b/src/main/java/net/querz/nbt/tag/CompoundTag.java similarity index 99% rename from src/main/java/net/querz/nbt/CompoundTag.java rename to src/main/java/net/querz/nbt/tag/CompoundTag.java index 4ca27707..3909a72c 100644 --- a/src/main/java/net/querz/nbt/CompoundTag.java +++ b/src/main/java/net/querz/nbt/tag/CompoundTag.java @@ -1,4 +1,4 @@ -package net.querz.nbt; +package net.querz.nbt.tag; import net.querz.io.MaxDepthIO; diff --git a/src/main/java/net/querz/nbt/DoubleTag.java b/src/main/java/net/querz/nbt/tag/DoubleTag.java similarity index 96% rename from src/main/java/net/querz/nbt/DoubleTag.java rename to src/main/java/net/querz/nbt/tag/DoubleTag.java index d2168755..28d08658 100644 --- a/src/main/java/net/querz/nbt/DoubleTag.java +++ b/src/main/java/net/querz/nbt/tag/DoubleTag.java @@ -1,4 +1,4 @@ -package net.querz.nbt; +package net.querz.nbt.tag; public class DoubleTag extends NumberTag implements Comparable { diff --git a/src/main/java/net/querz/nbt/EndTag.java b/src/main/java/net/querz/nbt/tag/EndTag.java similarity index 94% rename from src/main/java/net/querz/nbt/EndTag.java rename to src/main/java/net/querz/nbt/tag/EndTag.java index ac6fdabc..30b970b8 100644 --- a/src/main/java/net/querz/nbt/EndTag.java +++ b/src/main/java/net/querz/nbt/tag/EndTag.java @@ -1,4 +1,4 @@ -package net.querz.nbt; +package net.querz.nbt.tag; public final class EndTag extends Tag { diff --git a/src/main/java/net/querz/nbt/FloatTag.java b/src/main/java/net/querz/nbt/tag/FloatTag.java similarity index 96% rename from src/main/java/net/querz/nbt/FloatTag.java rename to src/main/java/net/querz/nbt/tag/FloatTag.java index c837d1a0..9d79204f 100644 --- a/src/main/java/net/querz/nbt/FloatTag.java +++ b/src/main/java/net/querz/nbt/tag/FloatTag.java @@ -1,4 +1,4 @@ -package net.querz.nbt; +package net.querz.nbt.tag; public class FloatTag extends NumberTag implements Comparable { diff --git a/src/main/java/net/querz/nbt/IntArrayTag.java b/src/main/java/net/querz/nbt/tag/IntArrayTag.java similarity index 96% rename from src/main/java/net/querz/nbt/IntArrayTag.java rename to src/main/java/net/querz/nbt/tag/IntArrayTag.java index 53371e2b..1799c93c 100644 --- a/src/main/java/net/querz/nbt/IntArrayTag.java +++ b/src/main/java/net/querz/nbt/tag/IntArrayTag.java @@ -1,4 +1,4 @@ -package net.querz.nbt; +package net.querz.nbt.tag; import java.util.Arrays; diff --git a/src/main/java/net/querz/nbt/IntTag.java b/src/main/java/net/querz/nbt/tag/IntTag.java similarity index 96% rename from src/main/java/net/querz/nbt/IntTag.java rename to src/main/java/net/querz/nbt/tag/IntTag.java index a11d71f5..57c1f2ba 100644 --- a/src/main/java/net/querz/nbt/IntTag.java +++ b/src/main/java/net/querz/nbt/tag/IntTag.java @@ -1,4 +1,4 @@ -package net.querz.nbt; +package net.querz.nbt.tag; public class IntTag extends NumberTag implements Comparable { diff --git a/src/main/java/net/querz/nbt/ListTag.java b/src/main/java/net/querz/nbt/tag/ListTag.java similarity index 99% rename from src/main/java/net/querz/nbt/ListTag.java rename to src/main/java/net/querz/nbt/tag/ListTag.java index 60170902..889ece91 100644 --- a/src/main/java/net/querz/nbt/ListTag.java +++ b/src/main/java/net/querz/nbt/tag/ListTag.java @@ -1,4 +1,4 @@ -package net.querz.nbt; +package net.querz.nbt.tag; import net.querz.io.MaxDepthIO; diff --git a/src/main/java/net/querz/nbt/LongArrayTag.java b/src/main/java/net/querz/nbt/tag/LongArrayTag.java similarity index 96% rename from src/main/java/net/querz/nbt/LongArrayTag.java rename to src/main/java/net/querz/nbt/tag/LongArrayTag.java index ae591ba6..e0528dd1 100644 --- a/src/main/java/net/querz/nbt/LongArrayTag.java +++ b/src/main/java/net/querz/nbt/tag/LongArrayTag.java @@ -1,4 +1,4 @@ -package net.querz.nbt; +package net.querz.nbt.tag; import java.util.Arrays; diff --git a/src/main/java/net/querz/nbt/LongTag.java b/src/main/java/net/querz/nbt/tag/LongTag.java similarity index 96% rename from src/main/java/net/querz/nbt/LongTag.java rename to src/main/java/net/querz/nbt/tag/LongTag.java index 249f84ca..8f40a325 100644 --- a/src/main/java/net/querz/nbt/LongTag.java +++ b/src/main/java/net/querz/nbt/tag/LongTag.java @@ -1,4 +1,4 @@ -package net.querz.nbt; +package net.querz.nbt.tag; public class LongTag extends NumberTag implements Comparable { diff --git a/src/main/java/net/querz/nbt/NonNullEntrySet.java b/src/main/java/net/querz/nbt/tag/NonNullEntrySet.java similarity index 98% rename from src/main/java/net/querz/nbt/NonNullEntrySet.java rename to src/main/java/net/querz/nbt/tag/NonNullEntrySet.java index 32d8614a..e157ba26 100644 --- a/src/main/java/net/querz/nbt/NonNullEntrySet.java +++ b/src/main/java/net/querz/nbt/tag/NonNullEntrySet.java @@ -1,4 +1,4 @@ -package net.querz.nbt; +package net.querz.nbt.tag; import java.util.Collection; import java.util.Iterator; diff --git a/src/main/java/net/querz/nbt/NumberTag.java b/src/main/java/net/querz/nbt/tag/NumberTag.java similarity index 95% rename from src/main/java/net/querz/nbt/NumberTag.java rename to src/main/java/net/querz/nbt/tag/NumberTag.java index d2fd9ec6..48faa024 100644 --- a/src/main/java/net/querz/nbt/NumberTag.java +++ b/src/main/java/net/querz/nbt/tag/NumberTag.java @@ -1,4 +1,4 @@ -package net.querz.nbt; +package net.querz.nbt.tag; public abstract class NumberTag> extends Tag { diff --git a/src/main/java/net/querz/nbt/ShortTag.java b/src/main/java/net/querz/nbt/tag/ShortTag.java similarity index 96% rename from src/main/java/net/querz/nbt/ShortTag.java rename to src/main/java/net/querz/nbt/tag/ShortTag.java index 60fe36d1..5f434c37 100644 --- a/src/main/java/net/querz/nbt/ShortTag.java +++ b/src/main/java/net/querz/nbt/tag/ShortTag.java @@ -1,4 +1,4 @@ -package net.querz.nbt; +package net.querz.nbt.tag; public class ShortTag extends NumberTag implements Comparable { diff --git a/src/main/java/net/querz/nbt/StringTag.java b/src/main/java/net/querz/nbt/tag/StringTag.java similarity index 97% rename from src/main/java/net/querz/nbt/StringTag.java rename to src/main/java/net/querz/nbt/tag/StringTag.java index 9d69684b..0d30c4b6 100644 --- a/src/main/java/net/querz/nbt/StringTag.java +++ b/src/main/java/net/querz/nbt/tag/StringTag.java @@ -1,4 +1,4 @@ -package net.querz.nbt; +package net.querz.nbt.tag; public class StringTag extends Tag implements Comparable { diff --git a/src/main/java/net/querz/nbt/Tag.java b/src/main/java/net/querz/nbt/tag/Tag.java similarity index 99% rename from src/main/java/net/querz/nbt/Tag.java rename to src/main/java/net/querz/nbt/tag/Tag.java index 29da5b67..d69356ea 100644 --- a/src/main/java/net/querz/nbt/Tag.java +++ b/src/main/java/net/querz/nbt/tag/Tag.java @@ -1,4 +1,4 @@ -package net.querz.nbt; +package net.querz.nbt.tag; import net.querz.io.MaxDepthReachedException; diff --git a/src/test/java/net/querz/nbt/mca/CompressionTypeTest.java b/src/test/java/net/querz/mca/CompressionTypeTest.java similarity index 94% rename from src/test/java/net/querz/nbt/mca/CompressionTypeTest.java rename to src/test/java/net/querz/mca/CompressionTypeTest.java index 24298df7..65f87125 100644 --- a/src/test/java/net/querz/nbt/mca/CompressionTypeTest.java +++ b/src/test/java/net/querz/mca/CompressionTypeTest.java @@ -1,4 +1,4 @@ -package net.querz.nbt.mca; +package net.querz.mca; public class CompressionTypeTest extends MCATestCase { diff --git a/src/test/java/net/querz/nbt/mca/MCAFileTest.java b/src/test/java/net/querz/mca/MCAFileTest.java similarity index 99% rename from src/test/java/net/querz/nbt/mca/MCAFileTest.java rename to src/test/java/net/querz/mca/MCAFileTest.java index e937fc71..b9a0b870 100644 --- a/src/test/java/net/querz/nbt/mca/MCAFileTest.java +++ b/src/test/java/net/querz/mca/MCAFileTest.java @@ -1,7 +1,7 @@ -package net.querz.nbt.mca; +package net.querz.mca; -import net.querz.nbt.CompoundTag; -import net.querz.nbt.ListTag; +import net.querz.nbt.tag.CompoundTag; +import net.querz.nbt.tag.ListTag; import java.io.File; import java.io.IOException; import java.io.RandomAccessFile; diff --git a/src/test/java/net/querz/nbt/mca/MCATestCase.java b/src/test/java/net/querz/mca/MCATestCase.java similarity index 91% rename from src/test/java/net/querz/nbt/mca/MCATestCase.java rename to src/test/java/net/querz/mca/MCATestCase.java index e2843b78..d8c4d462 100644 --- a/src/test/java/net/querz/nbt/mca/MCATestCase.java +++ b/src/test/java/net/querz/mca/MCATestCase.java @@ -1,8 +1,8 @@ -package net.querz.nbt.mca; +package net.querz.mca; -import net.querz.nbt.CompoundTag; -import net.querz.nbt.ListTag; -import net.querz.nbt.NBTTestCase; +import net.querz.nbt.tag.CompoundTag; +import net.querz.nbt.tag.ListTag; +import net.querz.nbt.tag.NBTTestCase; public abstract class MCATestCase extends NBTTestCase { diff --git a/src/test/java/net/querz/nbt/mca/MCAUtilTest.java b/src/test/java/net/querz/mca/MCAUtilTest.java similarity index 99% rename from src/test/java/net/querz/nbt/mca/MCAUtilTest.java rename to src/test/java/net/querz/mca/MCAUtilTest.java index c4982d10..05179d8f 100644 --- a/src/test/java/net/querz/nbt/mca/MCAUtilTest.java +++ b/src/test/java/net/querz/mca/MCAUtilTest.java @@ -1,4 +1,4 @@ -package net.querz.nbt.mca; +package net.querz.mca; import java.io.File; diff --git a/src/test/java/net/querz/nbt/custom/CharTagTest.java b/src/test/java/net/querz/nbt/custom/CharTagTest.java deleted file mode 100644 index df7f8aa1..00000000 --- a/src/test/java/net/querz/nbt/custom/CharTagTest.java +++ /dev/null @@ -1,47 +0,0 @@ -package net.querz.nbt.custom; - -import net.querz.nbt.NBTTestCase; -import java.util.Arrays; - -public class CharTagTest extends NBTTestCase { - - public void testStringConversion() { - CharTag.register(); - CharTag t = new CharTag('a'); - assertEquals('a', (char) t.getValue()); - assertEquals(110, t.getID()); -// assertEquals("a", t.toTagString()); - assertEquals("{\"type\":\"" + t.getClass().getSimpleName() + "\",\"value\":\"a\"}", t.toString()); - } - - public void testEquals() { - CharTag t = new CharTag('a'); - CharTag t2 = new CharTag('a'); - assertTrue(t.equals(t2)); - CharTag t3 = new CharTag('b'); - assertFalse(t.equals(t3)); - } - - public void testClone() { - CharTag t = new CharTag('a'); - CharTag tc = t.clone(); - assertTrue(t.equals(tc)); - assertFalse(t == tc); - } - - public void testSerializeDeserialize() { - CharTag t = new CharTag('a'); - CharTag.register(); - byte[] data = serialize(t); - assertTrue(Arrays.equals(new byte[]{110, 0, 0, 0, 97}, data)); - CharTag tt = (CharTag) deserialize(data); - assertTrue(t.equals(tt)); - } - - public void testCompareTo() { - assertEquals(0, new CharTag('a').compareTo(new CharTag('a'))); - assertTrue(0 < new CharTag('b').compareTo(new CharTag('a'))); - assertTrue(0 > new CharTag('a').compareTo(new CharTag('b'))); - - } -} diff --git a/src/test/java/net/querz/nbt/custom/ObjectTagTest.java b/src/test/java/net/querz/nbt/custom/ObjectTagTest.java deleted file mode 100644 index b1685887..00000000 --- a/src/test/java/net/querz/nbt/custom/ObjectTagTest.java +++ /dev/null @@ -1,201 +0,0 @@ -package net.querz.nbt.custom; - -import net.querz.nbt.NBTTestCase; -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; - -public class ObjectTagTest extends NBTTestCase { - - public void testStringConversion() { - ObjectTag.register(); - DummyObject d = new DummyObject(); - ObjectTag o = new ObjectTag<>(d); - assertEquals(90, o.getID()); - assertEquals("{\"type\":\"ObjectTag\",\"value\":\"" + d + "\"}", o.toString()); -// assertEquals("\"" + d + "\"", o.toTagString()); - } - - public void testEquals() { - DummyObject d = new DummyObject(); - ObjectTag o = new ObjectTag<>(d); - ObjectTag o2 = new ObjectTag<>(d); - assertTrue(o.equals(o2)); - ObjectTag o3 = new ObjectTag<>(new DummyObject()); - assertFalse(o.equals(o3)); - ObjectTag o4 = new ObjectTag<>(d.clone()); - assertTrue(o.equals(o4)); - } - - public void testHashCode() { - DummyObject d = new DummyObject(); - DummyObject d2 = new DummyObject(); - ObjectTag o = new ObjectTag<>(d); - ObjectTag o2 = new ObjectTag<>(d2); - assertNotEquals(o.hashCode(), o2.hashCode()); - assertEquals(o.hashCode(), o.clone().hashCode()); - assertEquals(0, new ObjectTag().hashCode()); - } - - public void testClone() { - DummyObject d = new DummyObject(); - ObjectTag o = new ObjectTag<>(d); - ObjectTag c = o.clone(); - assertTrue(o.equals(c)); - assertFalse(o == c); - assertFalse(o.getValue() == c.getValue()); - ObjectTag s = new ObjectTag<>("string"); - ObjectTag cs = s.clone(); - assertTrue(s.equals(cs)); - assertFalse(s == cs); - //String is immutable and not cloneable, so it still has the same reference - //noinspection StringEquality - assertTrue(s.getValue() == cs.getValue()); - } - - public void testSerializeDeserialize() { - DummyObject d = new DummyObject(); - ObjectTag o = new ObjectTag<>(d); - ObjectTag.register(); - byte[] data = serialize(o); - ObjectTag oo = ((ObjectTag) deserialize(data)); - assertNotNull(oo); - assertThrowsNoRuntimeException(() -> oo.asTypedObjectTag(AbstractDummyObject.class)); - assertThrowsRuntimeException(() -> oo.asTypedObjectTag(String.class), ClassCastException.class); - ObjectTag ooo = oo.asTypedObjectTag(AbstractDummyObject.class); - assertTrue(o.equals(ooo)); - } - - public void testNullValue() { - ObjectTag n = new ObjectTag<>(); - assertNull(n.getValue()); - assertEquals("{\"type\":\"ObjectTag\",\"value\":null}", n.toString()); -// assertEquals("null", n.toTagString()); - } - - public void testNullValueEquals() { - ObjectTag n = new ObjectTag<>(); - ObjectTag n2 = new ObjectTag<>(null); - assertTrue(n.equals(n2)); - ObjectTag n3 = new ObjectTag<>(null); - assertTrue(n.equals(n3)); - } - - public void testNullValueClone() { - ObjectTag n = new ObjectTag<>(); - ObjectTag nc = n.clone(); - assertTrue(n.equals(nc)); - assertFalse(n == nc); - assertTrue(n.getValue() == nc.getValue()); - } - - public void testNullValueSerializeDeserialize() { - ObjectTag n = new ObjectTag<>(); - ObjectTag.register(); - byte[] data = serialize(n); - ObjectTag nn = ((ObjectTag) deserialize(data)); - assertNotNull(nn); - ObjectTag nnn = nn.asTypedObjectTag(AbstractDummyObject.class); - assertTrue(n.equals(nnn)); - } - - public void testCompareTo() { - ObjectTag d = new ObjectTag<>(new DummyObject()); - ObjectTag d2 = new ObjectTag<>(new DummyObject()); - //not comparable - assertEquals(0, d.compareTo(d2)); - - ObjectTag d3 = new ObjectTag<>("abc"); - 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() { -// TagFactory.registerCustomTag(90, ObjectTag::new, ObjectTag.class); -// assertThrowsException(() -> NBTUtil.readTag(getResourceFile("unknown_object_tag.dat")), IOException.class); -// } - - public static abstract class AbstractDummyObject implements Serializable { - private static final long serialVersionUID = 1L; - - @Override - public String toString() { - return "AbstractDummyObject"; - } - } - - private static final Random RANDOM = new Random(); - - public static class DummyObject extends AbstractDummyObject implements Cloneable { - private static final long serialVersionUID = 1L; - - public byte a = (byte) RANDOM.nextInt(Byte.MAX_VALUE); - public short b = (short) RANDOM.nextInt(Short.MAX_VALUE); - public int c = RANDOM.nextInt(); - public long d = RANDOM.nextLong(); - public float e = RANDOM.nextFloat(); - public double f = RANDOM.nextDouble(); - - @Override - public DummyObject clone() { - try { - return (DummyObject) super.clone(); - } catch (CloneNotSupportedException ex) { - ex.printStackTrace(); - return null; - } - } - - @Override - public String toString() { - return String.format("%d/%d/%d/%d/%f/%f", a, b, c, d, e, f); - } - - @Override - public boolean equals(Object other) { - if (other instanceof DummyObject) { - DummyObject t = (DummyObject) other; - return t.a == a - && t.b == b - && t.c == c - && t.d == d - && t.e == e - && t.f == f; - } - return false; - } - - @Override - public int hashCode() { - return Objects.hash(a, b, c, d, e, f); - } - } -} diff --git a/src/test/java/net/querz/nbt/custom/ShortArrayTagTest.java b/src/test/java/net/querz/nbt/custom/ShortArrayTagTest.java deleted file mode 100644 index 2d380305..00000000 --- a/src/test/java/net/querz/nbt/custom/ShortArrayTagTest.java +++ /dev/null @@ -1,61 +0,0 @@ -package net.querz.nbt.custom; - -import net.querz.nbt.NBTTestCase; -import java.util.Arrays; -import static org.junit.Assert.assertNotEquals; - -public class ShortArrayTagTest extends NBTTestCase { - - public void testStringConversion() { - ShortArrayTag.register(); - ShortArrayTag t = new ShortArrayTag(new short[]{Short.MIN_VALUE, 0, Short.MAX_VALUE}); - assertTrue(Arrays.equals(new short[]{Short.MIN_VALUE, 0, Short.MAX_VALUE}, t.getValue())); - assertEquals(100, t.getID()); -// assertEquals("[S;-32768s,0s,32767s]", t.toTagString()); - assertEquals("{\"type\":\"" + t.getClass().getSimpleName() + "\",\"value\":[-32768,0,32767]}", t.toString()); - } - - public void testEquals() { - ShortArrayTag t = new ShortArrayTag(new short[]{Short.MIN_VALUE, 0, Short.MAX_VALUE}); - ShortArrayTag t2 = new ShortArrayTag(new short[]{Short.MIN_VALUE, 0, Short.MAX_VALUE}); - assertTrue(t.equals(t2)); - ShortArrayTag t3 = new ShortArrayTag(new short[]{Short.MAX_VALUE, 0, Short.MIN_VALUE}); - assertFalse(t.equals(t3)); - } - - public void testHashCode() { - ShortArrayTag t = new ShortArrayTag(new short[]{Short.MIN_VALUE, 0, Short.MAX_VALUE}); - ShortArrayTag t2 = new ShortArrayTag(new short[]{Short.MAX_VALUE, 0, Short.MIN_VALUE}); - assertNotEquals(t.hashCode(), t2.hashCode()); - assertEquals(t.hashCode(), t.clone().hashCode()); - } - - public void testClone() { - ShortArrayTag t = new ShortArrayTag(new short[]{Short.MIN_VALUE, 0, Short.MAX_VALUE}); - ShortArrayTag tc = t.clone(); - assertTrue(t.equals(tc)); - assertFalse(t == tc); - assertFalse(t.getValue() == tc.getValue()); - } - - public void testSerializeDeserialize() { - ShortArrayTag t = new ShortArrayTag(new short[]{Short.MIN_VALUE, 0, Short.MAX_VALUE}); - ShortArrayTag.register(); - byte[] data = serialize(t); - assertTrue(Arrays.equals(new byte[]{100, 0, 0, 0, 0, 0, 3, -128, 0, 0, 0, 127, -1}, data)); - ShortArrayTag tt = (ShortArrayTag) deserialize(data); - assertTrue(t.equals(tt)); - } - - public void testCompareTo() { - ShortArrayTag t = new ShortArrayTag(new short[]{Short.MIN_VALUE, 0, Short.MAX_VALUE}); - ShortArrayTag t2 = new ShortArrayTag(new short[]{Short.MIN_VALUE, 0, Short.MAX_VALUE}); - ShortArrayTag t3 = new ShortArrayTag(new short[]{Short.MAX_VALUE, 0, Short.MIN_VALUE}); - ShortArrayTag t4 = new ShortArrayTag(new short[]{0, Short.MIN_VALUE}); - assertEquals(0, t.compareTo(t2)); - assertEquals(0, t.compareTo(t3)); - assertTrue(0 < t.compareTo(t4)); - assertTrue(0 > t4.compareTo(t)); - 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 deleted file mode 100644 index fa885eae..00000000 --- a/src/test/java/net/querz/nbt/custom/StructTagTest.java +++ /dev/null @@ -1,196 +0,0 @@ -package net.querz.nbt.custom; - -import junit.framework.TestCase; -import net.querz.nbt.ByteTag; -import net.querz.nbt.CompoundTag; -import net.querz.nbt.IntTag; -import net.querz.nbt.ListTag; -import net.querz.nbt.LongTag; -import net.querz.nbt.NBTTestCase; -import net.querz.nbt.StringTag; -import net.querz.nbt.Tag; -import java.util.Arrays; -import static org.junit.Assert.assertNotEquals; - -public class StructTagTest extends NBTTestCase { - - private StructTag createStructTag() { - StructTag s = new StructTag(); - s.add(new ByteTag(Byte.MAX_VALUE)); - s.add(new IntTag(Integer.MAX_VALUE)); - return s; - } - - public void testStringConversion() { - StructTag.register(); - StructTag s = createStructTag(); - assertEquals(120, s.getID()); -// assertEquals("[127b,2147483647]", s.toTagString()); - assertEquals("{\"type\":\"StructTag\",\"value\":[{\"type\":\"ByteTag\",\"value\":127},{\"type\":\"IntTag\",\"value\":2147483647}]}", s.toString()); - } - - public void testEquals() { - StructTag s = createStructTag(); - - StructTag s2 = new StructTag(); - s2.add(new ByteTag(Byte.MAX_VALUE)); - s2.add(new IntTag(Integer.MAX_VALUE)); - assertTrue(s.equals(s2)); - - StructTag s3 = new StructTag(); - s3.add(new IntTag(Integer.MAX_VALUE)); - s3.add(new ByteTag(Byte.MAX_VALUE)); - assertFalse(s.equals(s3)); - - StructTag s4 = new StructTag(); - s4.add(new ByteTag(Byte.MAX_VALUE)); - assertFalse(s.equals(s4)); - } - - public void testHashCode() { - StructTag s = createStructTag(); - StructTag s2 = createStructTag(); - s2.addInt(123); - assertNotEquals(s.hashCode(), s2.hashCode()); - assertEquals(s.hashCode(), s.clone().hashCode()); - - } - - public void testClone() { - StructTag s = createStructTag(); - StructTag c = s.clone(); - assertTrue(s.equals(c)); - assertFalse(s == c); - assertFalse(invokeGetValue(s) == invokeGetValue(c)); - } - - public void testSerializeDeserialize() { - StructTag s = createStructTag(); - StructTag.register(); - byte[] data = serialize(s); - System.out.println(Arrays.toString(data)); - assertTrue(Arrays.equals(new byte[]{120, 0, 0, 0, 0, 0, 2, 1, 127, 3, 127, -1, -1, -1}, data)); - StructTag ss = (StructTag) deserialize(data); - assertTrue(s.equals(ss)); - } - - public void testCompareTo() { - StructTag st = new StructTag(); - st.addInt(1); - st.addInt(2); - StructTag so = new StructTag(); - so.addInt(3); - so.addInt(4); - assertEquals(0, st.compareTo(so)); - so.addInt(5); - assertEquals(-1, st.compareTo(so)); - so.remove(2); - so.remove(1); - assertEquals(1, st.compareTo(so)); - assertThrowsRuntimeException(() -> st.compareTo(null), NullPointerException.class); - } - - public void testContains() { - StructTag l = new StructTag(); - l.addInt(1); - l.addLong(2); - assertTrue(l.contains(new IntTag(1))); - assertFalse(l.contains(new IntTag(2))); - assertTrue(l.containsAll(Arrays.asList(new IntTag(1), new LongTag(2)))); - assertFalse(l.containsAll(Arrays.asList(new IntTag(1), new IntTag(2)))); - } - - public void testIterator() { - StructTag l = new StructTag(); - l.addInt(1); - l.addLong(2); - for (Tag t : l) { - assertNotNull(t); - } - l.forEach(TestCase::assertNotNull); - } - - public void testSet() { - StructTag l = createStructTag(); - l.set(1, new ByteTag((byte) 5)); - assertEquals(2, l.size()); - assertEquals(5, l.getByte(1)); - assertThrowsRuntimeException(() -> l.set(0, null), NullPointerException.class); - } - - public void testAdd() { - StructTag l = new StructTag(); - l.addBoolean(true); - assertThrowsNoRuntimeException(() -> l.addShort((short) 5)); - assertEquals(2, l.size()); - assertEquals(1, l.getByte(0)); - l.addByte(Byte.MAX_VALUE); - assertEquals(3, l.size()); - assertEquals(Byte.MAX_VALUE, l.getByte(2)); - l.addBoolean(true); - assertEquals(1, l.getByte(3)); - l.addBoolean(false); - assertEquals(0, l.getByte(4)); - assertTrue(l.getBoolean(3)); - assertFalse(l.getBoolean(4)); - l.remove(new ByteTag(Byte.MAX_VALUE)); - assertEquals(4, l.size()); - assertThrowsRuntimeException(() -> l.remove(-1), ArrayIndexOutOfBoundsException.class); - assertThrowsRuntimeException(() -> l.remove(4), IndexOutOfBoundsException.class); - assertEquals(new ByteTag(true), assertThrowsNoRuntimeException(() -> l.remove(2))); - l.clear(); - assertEquals(0, l.size()); - - StructTag s = new StructTag(); - s.addShort(Short.MAX_VALUE); - assertEquals(1, s.size()); - assertEquals(Short.MAX_VALUE, s.getShort(0)); - StructTag i = new StructTag(); - i.addInt(Integer.MAX_VALUE); - assertEquals(1, i.size()); - assertEquals(Integer.MAX_VALUE, i.getInt(0)); - StructTag lo = new StructTag(); - lo.addLong(Long.MAX_VALUE); - assertEquals(1, lo.size()); - assertEquals(Long.MAX_VALUE, lo.getLong(0)); - StructTag f = new StructTag(); - f.addFloat(Float.MAX_VALUE); - assertEquals(1, f.size()); - assertEquals(Float.MAX_VALUE, f.getFloat(0)); - StructTag d = new StructTag(); - d.addDouble(Double.MAX_VALUE); - assertEquals(1, d.size()); - assertEquals(Double.MAX_VALUE, d.getDouble(0)); - StructTag st = new StructTag(); - st.addString("foo"); - assertEquals(1, st.size()); - assertEquals("foo", st.getString(0)); - StructTag ba = new StructTag(); - ba.addByteArray(new byte[] {Byte.MIN_VALUE, 0, Byte.MAX_VALUE}); - assertEquals(1, ba.size()); - assertTrue(Arrays.equals(new byte[] {Byte.MIN_VALUE, 0, Byte.MAX_VALUE}, ba.getByteArray(0))); - StructTag ia = new StructTag(); - ia.addIntArray(new int[] {Integer.MIN_VALUE, 0, Integer.MAX_VALUE}); - assertEquals(1, ia.size()); - assertTrue(Arrays.equals(new int[] {Integer.MIN_VALUE, 0, Integer.MAX_VALUE}, ia.getIntArray(0))); - StructTag la = new StructTag(); - la.addLongArray(new long[] {Long.MIN_VALUE, 0, Long.MAX_VALUE}); - assertEquals(1, la.size()); - assertTrue(Arrays.equals(new long[] {Long.MIN_VALUE, 0, Long.MAX_VALUE}, la.getLongArray(0))); - StructTag co = new StructTag(); - co.add(new CompoundTag()); - assertEquals(1, co.size()); - assertEquals(new CompoundTag(), co.getCompoundTag(0)); - StructTag li = new StructTag(); - li.add(new ListTag<>(IntTag.class)); - assertEquals(1, li.size()); - assertEquals(new ListTag<>(IntTag.class), li.getListTag(0)); - - StructTag t = new StructTag(); - t.add(0, new StringTag("foo")); - t.add(0, new IntTag(Integer.MAX_VALUE)); - assertEquals(2, t.size()); - assertEquals(new IntTag(Integer.MAX_VALUE), t.get(0)); - assertEquals(new StringTag("foo"), t.get(1)); - } -} diff --git a/src/test/java/net/querz/nbt/io/MSONWriterTest.java b/src/test/java/net/querz/nbt/io/MSONWriterTest.java index f81e275f..43b32f06 100644 --- a/src/test/java/net/querz/nbt/io/MSONWriterTest.java +++ b/src/test/java/net/querz/nbt/io/MSONWriterTest.java @@ -1,7 +1,7 @@ package net.querz.nbt.io; -import net.querz.nbt.EndTag; -import net.querz.nbt.NBTTestCase; +import net.querz.nbt.tag.EndTag; +import net.querz.nbt.tag.NBTTestCase; import java.io.StringWriter; @@ -10,6 +10,6 @@ public class MSONWriterTest extends NBTTestCase { public void testWrite() { StringWriter sw = new StringWriter(); assertThrowsNoException(() -> MSONWriter.write(EndTag.INSTANCE, sw)); - System.out.println(sw.toString()); +// System.out.println(sw.toString()); } } diff --git a/src/test/java/net/querz/nbt/ByteArrayTagTest.java b/src/test/java/net/querz/nbt/tag/ByteArrayTagTest.java similarity index 97% rename from src/test/java/net/querz/nbt/ByteArrayTagTest.java rename to src/test/java/net/querz/nbt/tag/ByteArrayTagTest.java index ba6e3334..b97ca492 100644 --- a/src/test/java/net/querz/nbt/ByteArrayTagTest.java +++ b/src/test/java/net/querz/nbt/tag/ByteArrayTagTest.java @@ -1,4 +1,4 @@ -package net.querz.nbt; +package net.querz.nbt.tag; import java.util.Arrays; @@ -31,7 +31,6 @@ public void testClone() { public void testSerializeDeserialize() { ByteArrayTag t = new ByteArrayTag(new byte[]{Byte.MIN_VALUE, 0, Byte.MAX_VALUE}); byte[] data = serialize(t); - System.out.println(Arrays.toString(data)); assertTrue(Arrays.equals(new byte[]{7, 0, 0, 0, 0, 0, 3, -128, 0, 127}, data)); ByteArrayTag tt = (ByteArrayTag) deserialize(data); assertTrue(t.equals(tt)); diff --git a/src/test/java/net/querz/nbt/ByteTagTest.java b/src/test/java/net/querz/nbt/tag/ByteTagTest.java similarity index 98% rename from src/test/java/net/querz/nbt/ByteTagTest.java rename to src/test/java/net/querz/nbt/tag/ByteTagTest.java index 56a346f6..5b8409f6 100644 --- a/src/test/java/net/querz/nbt/ByteTagTest.java +++ b/src/test/java/net/querz/nbt/tag/ByteTagTest.java @@ -1,4 +1,4 @@ -package net.querz.nbt; +package net.querz.nbt.tag; import java.util.Arrays; diff --git a/src/test/java/net/querz/nbt/CompoundTagTest.java b/src/test/java/net/querz/nbt/tag/CompoundTagTest.java similarity index 99% rename from src/test/java/net/querz/nbt/CompoundTagTest.java rename to src/test/java/net/querz/nbt/tag/CompoundTagTest.java index 7c5b783d..f3b52632 100644 --- a/src/test/java/net/querz/nbt/CompoundTagTest.java +++ b/src/test/java/net/querz/nbt/tag/CompoundTagTest.java @@ -1,4 +1,4 @@ -package net.querz.nbt; +package net.querz.nbt.tag; import net.querz.io.MaxDepthReachedException; @@ -154,7 +154,6 @@ public void testClear() { public void testSerializeDeserialize() { CompoundTag ct = createCompoundTag(); byte[] data = serialize(ct); - System.out.println(Arrays.toString(data)); assertTrue(Arrays.equals(new byte[]{10, 0, 0, 1, 0, 1, 98, 127, 8, 0, 3, 115, 116, 114, 0, 3, 102, 111, 111, 9, 0, 4, 108, 105, 115, 116, 1, 0, 0, 0, 1, 123, 0}, data)); CompoundTag tt = (CompoundTag) deserialize(data); assertTrue(ct.equals(tt)); diff --git a/src/test/java/net/querz/nbt/DoubleTagTest.java b/src/test/java/net/querz/nbt/tag/DoubleTagTest.java similarity index 98% rename from src/test/java/net/querz/nbt/DoubleTagTest.java rename to src/test/java/net/querz/nbt/tag/DoubleTagTest.java index 8309d72e..0736b315 100644 --- a/src/test/java/net/querz/nbt/DoubleTagTest.java +++ b/src/test/java/net/querz/nbt/tag/DoubleTagTest.java @@ -1,4 +1,4 @@ -package net.querz.nbt; +package net.querz.nbt.tag; import static org.junit.Assert.assertNotEquals; import java.util.Arrays; diff --git a/src/test/java/net/querz/nbt/EndTagTest.java b/src/test/java/net/querz/nbt/tag/EndTagTest.java similarity index 96% rename from src/test/java/net/querz/nbt/EndTagTest.java rename to src/test/java/net/querz/nbt/tag/EndTagTest.java index a128515c..6b2ddc66 100644 --- a/src/test/java/net/querz/nbt/EndTagTest.java +++ b/src/test/java/net/querz/nbt/tag/EndTagTest.java @@ -1,4 +1,4 @@ -package net.querz.nbt; +package net.querz.nbt.tag; public class EndTagTest extends NBTTestCase { diff --git a/src/test/java/net/querz/nbt/ExceptionRunnable.java b/src/test/java/net/querz/nbt/tag/ExceptionRunnable.java similarity index 79% rename from src/test/java/net/querz/nbt/ExceptionRunnable.java rename to src/test/java/net/querz/nbt/tag/ExceptionRunnable.java index 1c1292ce..bfc2afe7 100644 --- a/src/test/java/net/querz/nbt/ExceptionRunnable.java +++ b/src/test/java/net/querz/nbt/tag/ExceptionRunnable.java @@ -1,4 +1,4 @@ -package net.querz.nbt; +package net.querz.nbt.tag; @FunctionalInterface public interface ExceptionRunnable { diff --git a/src/test/java/net/querz/nbt/ExceptionSupplier.java b/src/test/java/net/querz/nbt/tag/ExceptionSupplier.java similarity index 79% rename from src/test/java/net/querz/nbt/ExceptionSupplier.java rename to src/test/java/net/querz/nbt/tag/ExceptionSupplier.java index ba87ef04..e4b8aaee 100644 --- a/src/test/java/net/querz/nbt/ExceptionSupplier.java +++ b/src/test/java/net/querz/nbt/tag/ExceptionSupplier.java @@ -1,4 +1,4 @@ -package net.querz.nbt; +package net.querz.nbt.tag; @FunctionalInterface public interface ExceptionSupplier { diff --git a/src/test/java/net/querz/nbt/FloatTagTest.java b/src/test/java/net/querz/nbt/tag/FloatTagTest.java similarity index 98% rename from src/test/java/net/querz/nbt/FloatTagTest.java rename to src/test/java/net/querz/nbt/tag/FloatTagTest.java index 71074e29..afec5f36 100644 --- a/src/test/java/net/querz/nbt/FloatTagTest.java +++ b/src/test/java/net/querz/nbt/tag/FloatTagTest.java @@ -1,4 +1,4 @@ -package net.querz.nbt; +package net.querz.nbt.tag; import static org.junit.Assert.assertNotEquals; import java.util.Arrays; diff --git a/src/test/java/net/querz/nbt/IntArrayTagTest.java b/src/test/java/net/querz/nbt/tag/IntArrayTagTest.java similarity index 98% rename from src/test/java/net/querz/nbt/IntArrayTagTest.java rename to src/test/java/net/querz/nbt/tag/IntArrayTagTest.java index 00e992d0..a112793e 100644 --- a/src/test/java/net/querz/nbt/IntArrayTagTest.java +++ b/src/test/java/net/querz/nbt/tag/IntArrayTagTest.java @@ -1,4 +1,4 @@ -package net.querz.nbt; +package net.querz.nbt.tag; import java.util.Arrays; diff --git a/src/test/java/net/querz/nbt/IntTagTest.java b/src/test/java/net/querz/nbt/tag/IntTagTest.java similarity index 98% rename from src/test/java/net/querz/nbt/IntTagTest.java rename to src/test/java/net/querz/nbt/tag/IntTagTest.java index a7eba303..dbf3c9dd 100644 --- a/src/test/java/net/querz/nbt/IntTagTest.java +++ b/src/test/java/net/querz/nbt/tag/IntTagTest.java @@ -1,4 +1,4 @@ -package net.querz.nbt; +package net.querz.nbt.tag; import java.util.Arrays; diff --git a/src/test/java/net/querz/nbt/ListTagTest.java b/src/test/java/net/querz/nbt/tag/ListTagTest.java similarity index 99% rename from src/test/java/net/querz/nbt/ListTagTest.java rename to src/test/java/net/querz/nbt/tag/ListTagTest.java index 447d1663..852038c9 100644 --- a/src/test/java/net/querz/nbt/ListTagTest.java +++ b/src/test/java/net/querz/nbt/tag/ListTagTest.java @@ -1,4 +1,4 @@ -package net.querz.nbt; +package net.querz.nbt.tag; import junit.framework.TestCase; import net.querz.io.MaxDepthReachedException; @@ -119,7 +119,6 @@ public void testSerializeDeserializeEmptyList() { // empty list can't have type assertTrue(Arrays.equals(new byte[]{9, 0, 0, 0, 0, 0, 0, 0}, data)); ListTag et = (ListTag) deserialize(data); - System.out.println(et.getTypeClass()); assertNotNull(et); // doesn't make sense as there's no type // assertThrowsRuntimeException(et::asByteTagList, ClassCastException.class); diff --git a/src/test/java/net/querz/nbt/LongArrayTagTest.java b/src/test/java/net/querz/nbt/tag/LongArrayTagTest.java similarity index 98% rename from src/test/java/net/querz/nbt/LongArrayTagTest.java rename to src/test/java/net/querz/nbt/tag/LongArrayTagTest.java index f0509cc1..a617a01f 100644 --- a/src/test/java/net/querz/nbt/LongArrayTagTest.java +++ b/src/test/java/net/querz/nbt/tag/LongArrayTagTest.java @@ -1,4 +1,4 @@ -package net.querz.nbt; +package net.querz.nbt.tag; import java.util.Arrays; diff --git a/src/test/java/net/querz/nbt/LongTagTest.java b/src/test/java/net/querz/nbt/tag/LongTagTest.java similarity index 98% rename from src/test/java/net/querz/nbt/LongTagTest.java rename to src/test/java/net/querz/nbt/tag/LongTagTest.java index 1bc285f3..f01c65e2 100644 --- a/src/test/java/net/querz/nbt/LongTagTest.java +++ b/src/test/java/net/querz/nbt/tag/LongTagTest.java @@ -1,4 +1,4 @@ -package net.querz.nbt; +package net.querz.nbt.tag; import java.util.Arrays; diff --git a/src/test/java/net/querz/nbt/NBTTestCase.java b/src/test/java/net/querz/nbt/tag/NBTTestCase.java similarity index 99% rename from src/test/java/net/querz/nbt/NBTTestCase.java rename to src/test/java/net/querz/nbt/tag/NBTTestCase.java index b5934c5e..6b7130c9 100644 --- a/src/test/java/net/querz/nbt/NBTTestCase.java +++ b/src/test/java/net/querz/nbt/tag/NBTTestCase.java @@ -1,4 +1,4 @@ -package net.querz.nbt; +package net.querz.nbt.tag; import junit.framework.TestCase; import net.querz.nbt.io.NBTDeserializer; diff --git a/src/test/java/net/querz/nbt/NoNullEntrySetTest.java b/src/test/java/net/querz/nbt/tag/NoNullEntrySetTest.java similarity index 98% rename from src/test/java/net/querz/nbt/NoNullEntrySetTest.java rename to src/test/java/net/querz/nbt/tag/NoNullEntrySetTest.java index 6492300a..33d8df01 100644 --- a/src/test/java/net/querz/nbt/NoNullEntrySetTest.java +++ b/src/test/java/net/querz/nbt/tag/NoNullEntrySetTest.java @@ -1,4 +1,4 @@ -package net.querz.nbt; +package net.querz.nbt.tag; import java.util.Arrays; import java.util.Map; diff --git a/src/test/java/net/querz/nbt/ShortTagTest.java b/src/test/java/net/querz/nbt/tag/ShortTagTest.java similarity index 98% rename from src/test/java/net/querz/nbt/ShortTagTest.java rename to src/test/java/net/querz/nbt/tag/ShortTagTest.java index 009d9708..9ac28e74 100644 --- a/src/test/java/net/querz/nbt/ShortTagTest.java +++ b/src/test/java/net/querz/nbt/tag/ShortTagTest.java @@ -1,4 +1,4 @@ -package net.querz.nbt; +package net.querz.nbt.tag; import java.util.Arrays; diff --git a/src/test/java/net/querz/nbt/StringTagTest.java b/src/test/java/net/querz/nbt/tag/StringTagTest.java similarity index 98% rename from src/test/java/net/querz/nbt/StringTagTest.java rename to src/test/java/net/querz/nbt/tag/StringTagTest.java index f1fc425a..b26784d1 100644 --- a/src/test/java/net/querz/nbt/StringTagTest.java +++ b/src/test/java/net/querz/nbt/tag/StringTagTest.java @@ -1,4 +1,4 @@ -package net.querz.nbt; +package net.querz.nbt.tag; import java.util.Arrays; From f7996c0bb7106e017fb9487e9cd469b7a450ecbc Mon Sep 17 00:00:00 2001 From: Querz Date: Fri, 24 Apr 2020 15:17:03 +0200 Subject: [PATCH 07/51] rename mson to snbt / fix tag unit tests --- .../java/net/querz/nbt/io/NBTInputStream.java | 11 ----- .../net/querz/nbt/io/NBTOutputStream.java | 11 ----- src/main/java/net/querz/nbt/io/NBTUtil.java | 39 ++---------------- ...eserializer.java => SNBTDeserializer.java} | 4 +- .../io/{MSONParser.java => SNBTParser.java} | 6 +-- ...SONSerializer.java => SNBTSerializer.java} | 8 ++-- .../io/{MSONWriter.java => SNBTWriter.java} | 9 ++-- src/main/java/net/querz/nbt/tag/Tag.java | 10 ++++- .../java/net/querz/nbt/io/MSONWriterTest.java | 15 ------- .../net/querz/nbt/tag/ByteArrayTagTest.java | 14 ++++++- .../java/net/querz/nbt/tag/ByteTagTest.java | 14 ++++++- .../net/querz/nbt/tag/CompoundTagTest.java | 1 - .../java/net/querz/nbt/tag/DoubleTagTest.java | 14 ++++++- .../java/net/querz/nbt/tag/EndTagTest.java | 6 --- .../java/net/querz/nbt/tag/FloatTagTest.java | 14 ++++++- .../net/querz/nbt/tag/IntArrayTagTest.java | 14 ++++++- .../java/net/querz/nbt/tag/IntTagTest.java | 14 ++++++- .../java/net/querz/nbt/tag/ListTagTest.java | 2 - .../net/querz/nbt/tag/LongArrayTagTest.java | 14 ++++++- .../java/net/querz/nbt/tag/LongTagTest.java | 14 ++++++- .../java/net/querz/nbt/tag/NBTTestCase.java | 10 +---- .../java/net/querz/nbt/tag/ShortTagTest.java | 14 ++++++- .../java/net/querz/nbt/tag/StringTagTest.java | 9 ++-- src/test/resources/unknown_object_tag.dat | Bin 134 -> 0 bytes 24 files changed, 148 insertions(+), 119 deletions(-) rename src/main/java/net/querz/nbt/io/{MSONDeserializer.java => SNBTDeserializer.java} (83%) rename src/main/java/net/querz/nbt/io/{MSONParser.java => SNBTParser.java} (97%) rename src/main/java/net/querz/nbt/io/{MSONSerializer.java => SNBTSerializer.java} (72%) rename src/main/java/net/querz/nbt/io/{MSONWriter.java => SNBTWriter.java} (94%) delete mode 100644 src/test/java/net/querz/nbt/io/MSONWriterTest.java delete mode 100644 src/test/resources/unknown_object_tag.dat diff --git a/src/main/java/net/querz/nbt/io/NBTInputStream.java b/src/main/java/net/querz/nbt/io/NBTInputStream.java index c14a288f..05ede87e 100644 --- a/src/main/java/net/querz/nbt/io/NBTInputStream.java +++ b/src/main/java/net/querz/nbt/io/NBTInputStream.java @@ -70,17 +70,6 @@ private Tag readTag(byte type, int maxDepth) throws IOException { return f.accept(this, maxDepth); } - static > void registerCustomTag(byte id, ExceptionBiFunction f) { - if (readers.containsKey(id)) { - throw new IllegalArgumentException("custom tag already registered"); - } - readers.put(id, f); - } - - static void unregisterCustomTag(byte id) { - readers.remove(id); - } - private static ByteTag readByte(NBTInputStream in) throws IOException { return new ByteTag(in.readByte()); } diff --git a/src/main/java/net/querz/nbt/io/NBTOutputStream.java b/src/main/java/net/querz/nbt/io/NBTOutputStream.java index 2fa13833..4879052e 100644 --- a/src/main/java/net/querz/nbt/io/NBTOutputStream.java +++ b/src/main/java/net/querz/nbt/io/NBTOutputStream.java @@ -76,17 +76,6 @@ public void writeRawTag(Tag tag, int maxDepth) throws IOException { f.accept(this, tag, maxDepth); } - static void registerCustomTag(byte id, ExceptionTriConsumer, Integer, IOException> f, Class clazz) { - if (writers.containsKey(id)) { - throw new IllegalArgumentException("custom tag already registered"); - } - put(id, f, clazz); - } - - static void unregisterCustomTag(byte id) { - writers.remove(id); - } - static byte idFromClass(Class clazz) { Byte id = classIdMapping.get(clazz); if (id == null) { diff --git a/src/main/java/net/querz/nbt/io/NBTUtil.java b/src/main/java/net/querz/nbt/io/NBTUtil.java index f517e129..8ae1414f 100644 --- a/src/main/java/net/querz/nbt/io/NBTUtil.java +++ b/src/main/java/net/querz/nbt/io/NBTUtil.java @@ -1,7 +1,5 @@ package net.querz.nbt.io; -import net.querz.io.ExceptionBiFunction; -import net.querz.io.ExceptionTriConsumer; import net.querz.nbt.tag.Tag; import java.io.File; import java.io.FileInputStream; @@ -15,35 +13,6 @@ public final class NBTUtil { private NBTUtil() {} - @SuppressWarnings("unchecked") - public static > void registerCustomTag( - int id, - ExceptionTriConsumer serializer, - ExceptionBiFunction deserializer, - Class clazz) { - checkID(id); - NBTInputStream.registerCustomTag((byte) id, deserializer); - NBTOutputStream.registerCustomTag((byte) id, (ExceptionTriConsumer, Integer, IOException>) serializer, clazz); - } - - public static void unregisterCustomTag(int id) { - checkID(id); - NBTInputStream.unregisterCustomTag((byte) id); - NBTOutputStream.unregisterCustomTag((byte) id); - } - - private static void checkID(int id) { - if (id < 0) { - throw new IllegalArgumentException("id cannot be negative"); - } - if (id <= 12) { - throw new IllegalArgumentException("cannot change default tags"); - } - if (id > Byte.MAX_VALUE) { - throw new IllegalArgumentException("id out of bounds: " + id); - } - } - public static void write(NamedTag tag, File file, boolean compressed) throws IOException { try (FileOutputStream fos = new FileOutputStream(file)) { new NBTSerializer(compressed).toStream(tag, fos); @@ -109,11 +78,11 @@ private static InputStream detectDecompression(InputStream is) throws IOExceptio return pbis; } - public static String toMSONString(Tag tag) throws IOException { - return new MSONSerializer().toString(tag); + public static String toSNBT(Tag tag) throws IOException { + return new SNBTSerializer().toString(tag); } - public static Tag fromMSONString(String string) throws IOException { - return new MSONDeserializer().fromString(string); + public static Tag fromSNBT(String string) throws IOException { + return new SNBTDeserializer().fromString(string); } } diff --git a/src/main/java/net/querz/nbt/io/MSONDeserializer.java b/src/main/java/net/querz/nbt/io/SNBTDeserializer.java similarity index 83% rename from src/main/java/net/querz/nbt/io/MSONDeserializer.java rename to src/main/java/net/querz/nbt/io/SNBTDeserializer.java index 2ca11f2e..05a98fef 100644 --- a/src/main/java/net/querz/nbt/io/MSONDeserializer.java +++ b/src/main/java/net/querz/nbt/io/SNBTDeserializer.java @@ -7,7 +7,7 @@ import java.io.Reader; import java.util.stream.Collectors; -public class MSONDeserializer implements StringDeserializer> { +public class SNBTDeserializer implements StringDeserializer> { @Override public Tag fromReader(Reader reader) throws IOException { @@ -21,6 +21,6 @@ public Tag fromReader(Reader reader, int maxDepth) throws IOException { } else { bufferedReader = new BufferedReader(reader); } - return MSONParser.parse(bufferedReader.lines().collect(Collectors.joining()), maxDepth); + return SNBTParser.parse(bufferedReader.lines().collect(Collectors.joining()), maxDepth); } } diff --git a/src/main/java/net/querz/nbt/io/MSONParser.java b/src/main/java/net/querz/nbt/io/SNBTParser.java similarity index 97% rename from src/main/java/net/querz/nbt/io/MSONParser.java rename to src/main/java/net/querz/nbt/io/SNBTParser.java index feafce24..26ab513b 100644 --- a/src/main/java/net/querz/nbt/io/MSONParser.java +++ b/src/main/java/net/querz/nbt/io/SNBTParser.java @@ -20,7 +20,7 @@ import java.util.List; import java.util.regex.Pattern; -public final class MSONParser implements MaxDepthIO { +public final class SNBTParser implements MaxDepthIO { private static final Pattern FLOAT_PATTERN = Pattern.compile("^[-+]?(?:\\d+\\.?|\\d*\\.\\d+)(?:e[-+]?\\d+)?f$", Pattern.CASE_INSENSITIVE), @@ -33,12 +33,12 @@ public final class MSONParser implements MaxDepthIO { private StringPointer ptr; - private MSONParser(String string) { + private SNBTParser(String string) { this.ptr = new StringPointer(string); } public static Tag parse(String string, int maxDepth) throws ParseException { - return new MSONParser(string).parseAnything(maxDepth); + return new SNBTParser(string).parseAnything(maxDepth); } public static Tag parse(String string) throws ParseException { diff --git a/src/main/java/net/querz/nbt/io/MSONSerializer.java b/src/main/java/net/querz/nbt/io/SNBTSerializer.java similarity index 72% rename from src/main/java/net/querz/nbt/io/MSONSerializer.java rename to src/main/java/net/querz/nbt/io/SNBTSerializer.java index 95f353b8..523c9d41 100644 --- a/src/main/java/net/querz/nbt/io/MSONSerializer.java +++ b/src/main/java/net/querz/nbt/io/SNBTSerializer.java @@ -5,15 +5,15 @@ import java.io.IOException; import java.io.Writer; -public class MSONSerializer implements StringSerializer> { +public class SNBTSerializer implements StringSerializer> { private final boolean pretty; - public MSONSerializer() { + public SNBTSerializer() { this(false); } - public MSONSerializer(boolean pretty) { + public SNBTSerializer(boolean pretty) { this.pretty = pretty; } @@ -23,6 +23,6 @@ public void toWriter(Tag tag, Writer writer) throws IOException { } public void toWriter(Tag tag, Writer writer, int maxDepth) throws IOException { - MSONWriter.write(tag, writer, maxDepth); + SNBTWriter.write(tag, writer, maxDepth); } } diff --git a/src/main/java/net/querz/nbt/io/MSONWriter.java b/src/main/java/net/querz/nbt/io/SNBTWriter.java similarity index 94% rename from src/main/java/net/querz/nbt/io/MSONWriter.java rename to src/main/java/net/querz/nbt/io/SNBTWriter.java index e0c45502..6fb17eab 100644 --- a/src/main/java/net/querz/nbt/io/MSONWriter.java +++ b/src/main/java/net/querz/nbt/io/SNBTWriter.java @@ -22,22 +22,21 @@ import java.util.regex.Pattern; /** - * MSONWriter creates an MSON String. - * This does not support custom tags, because + * SNBTWriter creates an SNBT String. * * */ -public final class MSONWriter implements MaxDepthIO { +public final class SNBTWriter implements MaxDepthIO { private static final Pattern NON_QUOTE_PATTERN = Pattern.compile("[a-zA-Z0-9_.+\\-]+"); private Writer writer; - private MSONWriter(Writer writer) { + private SNBTWriter(Writer writer) { this.writer = writer; } public static void write(Tag tag, Writer writer, int maxDepth) throws IOException { - new MSONWriter(writer).writeAnything(tag, maxDepth); + new SNBTWriter(writer).writeAnything(tag, maxDepth); } public static void write(Tag tag, Writer writer) throws IOException { diff --git a/src/main/java/net/querz/nbt/tag/Tag.java b/src/main/java/net/querz/nbt/tag/Tag.java index d69356ea..dd1c8d55 100644 --- a/src/main/java/net/querz/nbt/tag/Tag.java +++ b/src/main/java/net/querz/nbt/tag/Tag.java @@ -1,7 +1,6 @@ package net.querz.nbt.tag; import net.querz.io.MaxDepthReachedException; - import java.util.Collections; import java.util.HashMap; import java.util.Map; @@ -117,6 +116,15 @@ public String toString(int maxDepth) { "\"value\":" + valueToString(maxDepth) + "}"; } + /** + * Calls {@link Tag#valueToString(int)} with {@link Tag#DEFAULT_MAX_DEPTH}. + * @return The string representation of the value of this Tag. + * @throws MaxDepthReachedException If the maximum nesting depth is exceeded. + * */ + public String valueToString() { + return valueToString(DEFAULT_MAX_DEPTH); + } + /** * Returns a JSON representation of the value of this Tag. * @param maxDepth The maximum nesting depth. diff --git a/src/test/java/net/querz/nbt/io/MSONWriterTest.java b/src/test/java/net/querz/nbt/io/MSONWriterTest.java deleted file mode 100644 index 43b32f06..00000000 --- a/src/test/java/net/querz/nbt/io/MSONWriterTest.java +++ /dev/null @@ -1,15 +0,0 @@ -package net.querz.nbt.io; - -import net.querz.nbt.tag.EndTag; -import net.querz.nbt.tag.NBTTestCase; - -import java.io.StringWriter; - -public class MSONWriterTest extends NBTTestCase { - - public void testWrite() { - StringWriter sw = new StringWriter(); - assertThrowsNoException(() -> MSONWriter.write(EndTag.INSTANCE, sw)); -// System.out.println(sw.toString()); - } -} diff --git a/src/test/java/net/querz/nbt/tag/ByteArrayTagTest.java b/src/test/java/net/querz/nbt/tag/ByteArrayTagTest.java index b97ca492..bd06956a 100644 --- a/src/test/java/net/querz/nbt/tag/ByteArrayTagTest.java +++ b/src/test/java/net/querz/nbt/tag/ByteArrayTagTest.java @@ -3,12 +3,24 @@ import java.util.Arrays; public class ByteArrayTagTest extends NBTTestCase { + + public void testCreate() { + ByteArrayTag t = new ByteArrayTag(new byte[]{Byte.MIN_VALUE, 0, Byte.MAX_VALUE}); + assertTrue(Arrays.equals(new byte[]{Byte.MIN_VALUE, 0, Byte.MAX_VALUE}, t.getValue())); + t = new ByteArrayTag(); + assertTrue(Arrays.equals(ByteArrayTag.ZERO_VALUE, t.getValue())); + } + + public void testSetValue() { + ByteArrayTag t = new ByteArrayTag(); + t.setValue(new byte[]{Byte.MIN_VALUE, 0, Byte.MAX_VALUE}); + assertTrue(Arrays.equals(new byte[]{Byte.MIN_VALUE, 0, Byte.MAX_VALUE}, t.getValue())); + } public void testStringConversion() { ByteArrayTag t = new ByteArrayTag(new byte[]{Byte.MIN_VALUE, 0, Byte.MAX_VALUE}); assertTrue(Arrays.equals(new byte[]{Byte.MIN_VALUE, 0, Byte.MAX_VALUE}, t.getValue())); assertEquals(7, t.getID()); -// assertEquals("[B;-128b,0b,127b]", t.toTagString()); assertEquals("{\"type\":\"" + t.getClass().getSimpleName() + "\",\"value\":[-128,0,127]}", t.toString()); } diff --git a/src/test/java/net/querz/nbt/tag/ByteTagTest.java b/src/test/java/net/querz/nbt/tag/ByteTagTest.java index 5b8409f6..aa491087 100644 --- a/src/test/java/net/querz/nbt/tag/ByteTagTest.java +++ b/src/test/java/net/querz/nbt/tag/ByteTagTest.java @@ -3,6 +3,19 @@ import java.util.Arrays; public class ByteTagTest extends NBTTestCase { + + public void testCreate() { + ByteTag t = new ByteTag(Byte.MAX_VALUE); + assertEquals(Byte.MAX_VALUE, t.asByte()); + t = new ByteTag(); + assertEquals(ByteTag.ZERO_VALUE, t.asByte()); + } + + public void testSetValue() { + ByteTag t = new ByteTag(); + t.setValue((byte) 123); + assertEquals(123, t.asByte()); + } public void testStringConversion() { ByteTag t = new ByteTag(Byte.MAX_VALUE); @@ -11,7 +24,6 @@ public void testStringConversion() { assertEquals(Byte.MAX_VALUE, t.asInt()); assertEquals(Byte.MAX_VALUE, t.asLong()); assertEquals(1, t.getID()); -// assertEquals(Byte.MAX_VALUE + "b", t.toTagString()); assertEquals("{\"type\":\"" + t.getClass().getSimpleName() + "\",\"value\":" + Byte.MAX_VALUE + "}", t.toString()); } diff --git a/src/test/java/net/querz/nbt/tag/CompoundTagTest.java b/src/test/java/net/querz/nbt/tag/CompoundTagTest.java index f3b52632..06316dcb 100644 --- a/src/test/java/net/querz/nbt/tag/CompoundTagTest.java +++ b/src/test/java/net/querz/nbt/tag/CompoundTagTest.java @@ -21,7 +21,6 @@ private CompoundTag createCompoundTag() { public void testStringConversion() { CompoundTag ct = createCompoundTag(); -// assertEquals("{b:127b,str:foo,list:[123b]}", ct.toTagString()); assertEquals("{\"type\":\"CompoundTag\"," + "\"value\":{" + "\"b\":{\"type\":\"ByteTag\",\"value\":127}," + diff --git a/src/test/java/net/querz/nbt/tag/DoubleTagTest.java b/src/test/java/net/querz/nbt/tag/DoubleTagTest.java index 0736b315..a87e484b 100644 --- a/src/test/java/net/querz/nbt/tag/DoubleTagTest.java +++ b/src/test/java/net/querz/nbt/tag/DoubleTagTest.java @@ -4,12 +4,24 @@ import java.util.Arrays; public class DoubleTagTest extends NBTTestCase { + + public void testCreate() { + DoubleTag t = new DoubleTag(Double.MAX_VALUE); + assertEquals(Double.MAX_VALUE, t.asDouble()); + t = new DoubleTag(); + assertEquals(DoubleTag.ZERO_VALUE, t.asDouble()); + } + + public void testSetValue() { + DoubleTag t = new DoubleTag(); + t.setValue(123.4); + assertEquals(123.4, t.asDouble()); + } public void testStringConversion() { DoubleTag t = new DoubleTag(Double.MAX_VALUE); assertEquals(Double.MAX_VALUE, t.asDouble()); assertEquals(6, t.getID()); -// assertEquals(Double.MAX_VALUE + "d", t.toTagString()); assertEquals("{\"type\":\"" + t.getClass().getSimpleName() + "\",\"value\":" + Double.MAX_VALUE + "}", t.toString()); } diff --git a/src/test/java/net/querz/nbt/tag/EndTagTest.java b/src/test/java/net/querz/nbt/tag/EndTagTest.java index 6b2ddc66..f479b34d 100644 --- a/src/test/java/net/querz/nbt/tag/EndTagTest.java +++ b/src/test/java/net/querz/nbt/tag/EndTagTest.java @@ -7,15 +7,9 @@ public void testStringConversion() { assertEquals(0, e.getID()); assertNull(e.getValue()); assertEquals("{\"type\":\"" + e.getClass().getSimpleName() + "\",\"value\":\"end\"}", e.toString()); -// assertThrowsRuntimeException(e::toTagString, UnsupportedOperationException.class); } public void testClone() { assertTrue(EndTag.INSTANCE == EndTag.INSTANCE.clone()); } - -// public void testSerializeDeserialize() { -// assertThrowsNoRuntimeException(() -> EndTag.INSTANCE.serializeValue(null, 0)); -// assertThrowsNoRuntimeException(() -> EndTag.INSTANCE.deserializeValue(null, 0)); -// } } diff --git a/src/test/java/net/querz/nbt/tag/FloatTagTest.java b/src/test/java/net/querz/nbt/tag/FloatTagTest.java index afec5f36..9b93e0d2 100644 --- a/src/test/java/net/querz/nbt/tag/FloatTagTest.java +++ b/src/test/java/net/querz/nbt/tag/FloatTagTest.java @@ -4,12 +4,24 @@ import java.util.Arrays; public class FloatTagTest extends NBTTestCase { + + public void testCreate() { + FloatTag t = new FloatTag(Float.MAX_VALUE); + assertEquals(Float.MAX_VALUE, t.asFloat()); + t = new FloatTag(); + assertEquals(FloatTag.ZERO_VALUE, t.asFloat()); + } + + public void testSetValue() { + FloatTag t = new FloatTag(); + t.setValue(123.4f); + assertEquals(123.4f, t.asFloat()); + } public void testStringConversion() { FloatTag t = new FloatTag(Float.MAX_VALUE); assertEquals(Float.MAX_VALUE, t.asFloat()); assertEquals(5, t.getID()); -// assertEquals(Float.MAX_VALUE + "f", t.toTagString()); assertEquals("{\"type\":\"" + t.getClass().getSimpleName() + "\",\"value\":" + Float.MAX_VALUE + "}", t.toString()); } diff --git a/src/test/java/net/querz/nbt/tag/IntArrayTagTest.java b/src/test/java/net/querz/nbt/tag/IntArrayTagTest.java index a112793e..51b82442 100644 --- a/src/test/java/net/querz/nbt/tag/IntArrayTagTest.java +++ b/src/test/java/net/querz/nbt/tag/IntArrayTagTest.java @@ -4,11 +4,23 @@ public class IntArrayTagTest extends NBTTestCase { + public void testCreate() { + IntArrayTag t = new IntArrayTag(new int[]{Integer.MIN_VALUE, 0, Integer.MAX_VALUE}); + assertTrue(Arrays.equals(new int[]{Integer.MIN_VALUE, 0, Integer.MAX_VALUE}, t.getValue())); + t = new IntArrayTag(); + assertTrue(Arrays.equals(IntArrayTag.ZERO_VALUE, t.getValue())); + } + + public void testSetValue() { + IntArrayTag t = new IntArrayTag(); + t.setValue(new int[]{Integer.MIN_VALUE, 0, Integer.MAX_VALUE}); + assertTrue(Arrays.equals(new int[]{Integer.MIN_VALUE, 0, Integer.MAX_VALUE}, t.getValue())); + } + public void testStringConversion() { IntArrayTag t = new IntArrayTag(new int[]{Integer.MIN_VALUE, 0, Integer.MAX_VALUE}); assertTrue(Arrays.equals(new int[]{Integer.MIN_VALUE, 0, Integer.MAX_VALUE}, t.getValue())); assertEquals(11, t.getID()); -// assertEquals("[I;-2147483648,0,2147483647]", t.toTagString()); assertEquals("{\"type\":\"" + t.getClass().getSimpleName() + "\",\"value\":[-2147483648,0,2147483647]}", t.toString()); } diff --git a/src/test/java/net/querz/nbt/tag/IntTagTest.java b/src/test/java/net/querz/nbt/tag/IntTagTest.java index dbf3c9dd..57f1030c 100644 --- a/src/test/java/net/querz/nbt/tag/IntTagTest.java +++ b/src/test/java/net/querz/nbt/tag/IntTagTest.java @@ -4,12 +4,24 @@ public class IntTagTest extends NBTTestCase { + public void testCreate() { + IntTag t = new IntTag(Integer.MAX_VALUE); + assertEquals(Integer.MAX_VALUE, t.asInt()); + t = new IntTag(); + assertEquals(IntTag.ZERO_VALUE, t.asInt()); + } + + public void testSetValue() { + IntTag t = new IntTag(); + t.setValue(123); + assertEquals(123, t.asInt()); + } + public void testStringConversion() { IntTag t = new IntTag(Integer.MAX_VALUE); assertEquals(Integer.MAX_VALUE, t.asInt()); assertEquals(Integer.MAX_VALUE, t.asLong()); assertEquals(3, t.getID()); -// assertEquals(Integer.MAX_VALUE + "", t.toTagString()); assertEquals("{\"type\":\"" + t.getClass().getSimpleName() + "\",\"value\":" + Integer.MAX_VALUE + "}", t.toString()); } diff --git a/src/test/java/net/querz/nbt/tag/ListTagTest.java b/src/test/java/net/querz/nbt/tag/ListTagTest.java index 852038c9..64dc5de7 100644 --- a/src/test/java/net/querz/nbt/tag/ListTagTest.java +++ b/src/test/java/net/querz/nbt/tag/ListTagTest.java @@ -2,7 +2,6 @@ import junit.framework.TestCase; import net.querz.io.MaxDepthReachedException; - import java.util.Arrays; import java.util.Comparator; import static org.junit.Assert.assertNotEquals; @@ -28,7 +27,6 @@ public void testStringConversion() { assertEquals(Byte.MIN_VALUE, bl.get(0).asByte()); assertEquals(0, bl.get(1).asByte()); assertEquals(Byte.MAX_VALUE, bl.get(2).asByte()); -// assertEquals("[-128b,0b,127b]", bl.toTagString()); assertEquals("{\"type\":\"ListTag\"," + "\"value\":{" + "\"type\":\"ByteTag\"," + diff --git a/src/test/java/net/querz/nbt/tag/LongArrayTagTest.java b/src/test/java/net/querz/nbt/tag/LongArrayTagTest.java index a617a01f..d251d49f 100644 --- a/src/test/java/net/querz/nbt/tag/LongArrayTagTest.java +++ b/src/test/java/net/querz/nbt/tag/LongArrayTagTest.java @@ -4,11 +4,23 @@ public class LongArrayTagTest extends NBTTestCase { + public void testCreate() { + LongArrayTag t = new LongArrayTag(new long[]{Long.MIN_VALUE, 0, Long.MAX_VALUE}); + assertTrue(Arrays.equals(new long[]{Long.MIN_VALUE, 0, Long.MAX_VALUE}, t.getValue())); + t = new LongArrayTag(); + assertTrue(Arrays.equals(LongArrayTag.ZERO_VALUE, t.getValue())); + } + + public void testSetValue() { + LongArrayTag t = new LongArrayTag(); + t.setValue(new long[]{Long.MIN_VALUE, 0, Long.MAX_VALUE}); + assertTrue(Arrays.equals(new long[]{Long.MIN_VALUE, 0, Long.MAX_VALUE}, t.getValue())); + } + public void testStringConversion() { LongArrayTag t = new LongArrayTag(new long[]{Long.MIN_VALUE, 0, Long.MAX_VALUE}); assertTrue(Arrays.equals(new long[]{Long.MIN_VALUE, 0, Long.MAX_VALUE}, t.getValue())); assertEquals(12, t.getID()); -// assertEquals("[L;-9223372036854775808l,0l,9223372036854775807l]", t.toTagString()); assertEquals("{\"type\":\"" + t.getClass().getSimpleName() + "\",\"value\":[-9223372036854775808,0,9223372036854775807]}", t.toString()); } diff --git a/src/test/java/net/querz/nbt/tag/LongTagTest.java b/src/test/java/net/querz/nbt/tag/LongTagTest.java index f01c65e2..02b4cf67 100644 --- a/src/test/java/net/querz/nbt/tag/LongTagTest.java +++ b/src/test/java/net/querz/nbt/tag/LongTagTest.java @@ -4,11 +4,23 @@ public class LongTagTest extends NBTTestCase { + public void testCreate() { + LongTag t = new LongTag(Long.MAX_VALUE); + assertEquals(Long.MAX_VALUE, t.asLong()); + t = new LongTag(); + assertEquals(LongTag.ZERO_VALUE, t.asLong()); + } + + public void testSetValue() { + LongTag t = new LongTag(); + t.setValue(123); + assertEquals(123, t.asLong()); + } + public void testStringConversion() { LongTag t = new LongTag(Long.MAX_VALUE); assertEquals(Long.MAX_VALUE, t.asLong()); assertEquals(4, t.getID()); -// assertEquals(Long.MAX_VALUE + "l", t.toTagString()); assertEquals("{\"type\":\"" + t.getClass().getSimpleName() + "\",\"value\":" + Long.MAX_VALUE + "}", t.toString()); } diff --git a/src/test/java/net/querz/nbt/tag/NBTTestCase.java b/src/test/java/net/querz/nbt/tag/NBTTestCase.java index 6b7130c9..5efe2bdc 100644 --- a/src/test/java/net/querz/nbt/tag/NBTTestCase.java +++ b/src/test/java/net/querz/nbt/tag/NBTTestCase.java @@ -29,15 +29,7 @@ public abstract class NBTTestCase extends TestCase { @Override public void tearDown() throws Exception { super.tearDown(); -// TagFactory.unregisterCustomTag(90); -// TagFactory.unregisterCustomTag(100); -// TagFactory.unregisterCustomTag(110); -// TagFactory.unregisterCustomTag(120); - NBTUtil.unregisterCustomTag(90); - NBTUtil.unregisterCustomTag(100); - NBTUtil.unregisterCustomTag(110); - NBTUtil.unregisterCustomTag(120); -// cleanupTmpDir(); + cleanupTmpDir(); } protected byte[] serialize(Tag tag) { diff --git a/src/test/java/net/querz/nbt/tag/ShortTagTest.java b/src/test/java/net/querz/nbt/tag/ShortTagTest.java index 9ac28e74..c1af1fb4 100644 --- a/src/test/java/net/querz/nbt/tag/ShortTagTest.java +++ b/src/test/java/net/querz/nbt/tag/ShortTagTest.java @@ -4,13 +4,25 @@ public class ShortTagTest extends NBTTestCase { + public void testCreate() { + ShortTag t = new ShortTag(Short.MAX_VALUE); + assertEquals(Short.MAX_VALUE, t.asShort()); + t = new ShortTag(); + assertEquals(ShortTag.ZERO_VALUE, t.asShort()); + } + + public void testSetValue() { + ShortTag t = new ShortTag(); + t.setValue((short) 123); + assertEquals(123, t.asShort()); + } + public void testStringConversion() { ShortTag t = new ShortTag(Short.MAX_VALUE); assertEquals(Short.MAX_VALUE, t.asShort()); assertEquals(Short.MAX_VALUE, t.asInt()); assertEquals(Short.MAX_VALUE, t.asLong()); assertEquals(2, t.getID()); -// assertEquals(Short.MAX_VALUE + "s", t.toTagString()); assertEquals("{\"type\":\"" + t.getClass().getSimpleName() + "\",\"value\":" + Short.MAX_VALUE + "}", t.toString()); } diff --git a/src/test/java/net/querz/nbt/tag/StringTagTest.java b/src/test/java/net/querz/nbt/tag/StringTagTest.java index b26784d1..2d50ee97 100644 --- a/src/test/java/net/querz/nbt/tag/StringTagTest.java +++ b/src/test/java/net/querz/nbt/tag/StringTagTest.java @@ -8,7 +8,6 @@ public void testStringConversion() { StringTag t = new StringTag("foo"); assertEquals("foo", t.getValue()); assertEquals(8, t.getID()); -// assertEquals("foo", t.toTagString()); assertEquals("{\"type\":\"" + t.getClass().getSimpleName() + "\",\"value\":\"foo\"}", t.toString()); } @@ -37,13 +36,13 @@ public void testSerializeDeserialize() { public void testEscape() { StringTag allValue = new StringTag("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXZY0123456789_-+"); -// assertEquals("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXZY0123456789_-+", allValue.toTagString()); + assertEquals("\"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXZY0123456789_-+\"", allValue.valueToString()); StringTag escapeValue = new StringTag("öäü"); -// assertEquals("\"öäü\"", escapeValue.toTagString()); + assertEquals("\"öäü\"", escapeValue.valueToString()); StringTag escapeSpecialChars = new StringTag("\\\n\r\t\""); -// assertEquals("\"\\\\\\n\\r\\t\\\"\"", escapeSpecialChars.toTagString()); + assertEquals("\"\\\\\\n\\r\\t\\\"\"", escapeSpecialChars.valueToString()); StringTag escapeEmpty = new StringTag(""); -// assertEquals("\"\"", escapeEmpty.toTagString()); + assertEquals("\"\"", escapeEmpty.valueToString()); //no null values allowed assertThrowsRuntimeException(() -> new StringTag().setValue(null), NullPointerException.class); diff --git a/src/test/resources/unknown_object_tag.dat b/src/test/resources/unknown_object_tag.dat deleted file mode 100644 index 2f2ed2c37e427f6bedb8171e8f53999f99bab856..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 134 zcmb2|=3sz;?xci>&(j*W?&xsT)!*u}euK%@6<+%KD_!(=ZrPz?AAa6mQ`>Z>ii)AF z%GMn&sy0W~COo>`eqO1b)yUAm($GNdr@@A)DJf#BC8ijx_`%?NfqnJ@eiKLeq$ayA l&TFEqUmbQ&;kZ3@X3Ub0XWnUCkZbE=VsHs7YFh@h9spH|G*bWo From f8e6b9b0c08116101742ed79f365e5d88993d271 Mon Sep 17 00:00:00 2001 From: Querz Date: Fri, 24 Apr 2020 17:50:55 +0200 Subject: [PATCH 08/51] unit tests --- .../java/net/querz/nbt/io/SNBTParser.java | 99 ++++++++---- .../{nbt/tag => }/ExceptionRunnable.java | 2 +- .../{nbt/tag => }/ExceptionSupplier.java | 2 +- .../net/querz/{nbt/tag => }/NBTTestCase.java | 4 +- src/test/java/net/querz/mca/MCATestCase.java | 2 +- .../java/net/querz/nbt/io/NamedTagTest.java | 25 +++ .../java/net/querz/nbt/io/SNBTParserTest.java | 145 ++++++++++++++++++ .../net/querz/nbt/io/StringPointerTest.java | 90 +++++++++++ .../net/querz/nbt/tag/ByteArrayTagTest.java | 2 + .../java/net/querz/nbt/tag/ByteTagTest.java | 2 + .../net/querz/nbt/tag/CompoundTagTest.java | 1 + .../java/net/querz/nbt/tag/DoubleTagTest.java | 2 + .../java/net/querz/nbt/tag/EndTagTest.java | 2 + .../java/net/querz/nbt/tag/FloatTagTest.java | 2 + .../net/querz/nbt/tag/IntArrayTagTest.java | 2 + .../java/net/querz/nbt/tag/IntTagTest.java | 2 + .../java/net/querz/nbt/tag/ListTagTest.java | 2 + .../net/querz/nbt/tag/LongArrayTagTest.java | 2 + .../java/net/querz/nbt/tag/LongTagTest.java | 2 + .../net/querz/nbt/tag/NoNullEntrySetTest.java | 2 + .../java/net/querz/nbt/tag/ShortTagTest.java | 2 + .../java/net/querz/nbt/tag/StringTagTest.java | 2 + 22 files changed, 357 insertions(+), 39 deletions(-) rename src/test/java/net/querz/{nbt/tag => }/ExceptionRunnable.java (79%) rename src/test/java/net/querz/{nbt/tag => }/ExceptionSupplier.java (79%) rename src/test/java/net/querz/{nbt/tag => }/NBTTestCase.java (99%) create mode 100644 src/test/java/net/querz/nbt/io/NamedTagTest.java create mode 100644 src/test/java/net/querz/nbt/io/SNBTParserTest.java create mode 100644 src/test/java/net/querz/nbt/io/StringPointerTest.java diff --git a/src/main/java/net/querz/nbt/io/SNBTParser.java b/src/main/java/net/querz/nbt/io/SNBTParser.java index 26ab513b..088540f7 100644 --- a/src/main/java/net/querz/nbt/io/SNBTParser.java +++ b/src/main/java/net/querz/nbt/io/SNBTParser.java @@ -23,13 +23,14 @@ public final class SNBTParser implements MaxDepthIO { private static final Pattern - FLOAT_PATTERN = Pattern.compile("^[-+]?(?:\\d+\\.?|\\d*\\.\\d+)(?:e[-+]?\\d+)?f$", Pattern.CASE_INSENSITIVE), - DOUBLE_PATTERN = Pattern.compile("^[-+]?(?:\\d+\\.?|\\d*\\.\\d+)(?:e[-+]?\\d+)?d$", Pattern.CASE_INSENSITIVE), - DOUBLE_NO_SUFFIX_PATTERN = Pattern.compile("^[-+]?(?:\\d+\\.|\\d*\\.\\d+)(?:e[-+]?\\d+)?$", Pattern.CASE_INSENSITIVE), - BYTE_PATTERN = Pattern.compile("^[-+]?\\d+b$", Pattern.CASE_INSENSITIVE), - SHORT_PATTERN = Pattern.compile("^[-+]?\\d+s$", Pattern.CASE_INSENSITIVE), - INT_PATTERN = Pattern.compile("^[-+]?\\d+$", Pattern.CASE_INSENSITIVE), - LONG_PATTERN = Pattern.compile("^[-+]?\\d+l$", Pattern.CASE_INSENSITIVE); + FLOAT_LITERAL_PATTERN = Pattern.compile("^[-+]?(?:\\d+\\.?|\\d*\\.\\d+)(?:e[-+]?\\d+)?f$", Pattern.CASE_INSENSITIVE), + DOUBLE_LITERAL_PATTERN = Pattern.compile("^[-+]?(?:\\d+\\.?|\\d*\\.\\d+)(?:e[-+]?\\d+)?d$", Pattern.CASE_INSENSITIVE), + DOUBLE_LITERAL_NO_SUFFIX_PATTERN = Pattern.compile("^[-+]?(?:\\d+\\.|\\d*\\.\\d+)(?:e[-+]?\\d+)?$", Pattern.CASE_INSENSITIVE), + BYTE_LITERAL_PATTERN = Pattern.compile("^[-+]?\\d+b$", Pattern.CASE_INSENSITIVE), + SHORT_LITERAL_PATTERN = Pattern.compile("^[-+]?\\d+s$", Pattern.CASE_INSENSITIVE), + INT_LITERAL_PATTERN = Pattern.compile("^[-+]?\\d+$", Pattern.CASE_INSENSITIVE), + LONG_LITERAL_PATTERN = Pattern.compile("^[-+]?\\d+l$", Pattern.CASE_INSENSITIVE), + NUMBER_PATTERN = Pattern.compile("^[-+]?\\d+$"); private StringPointer ptr; @@ -38,7 +39,13 @@ private SNBTParser(String string) { } public static Tag parse(String string, int maxDepth) throws ParseException { - return new SNBTParser(string).parseAnything(maxDepth); + SNBTParser parser = new SNBTParser(string); + Tag tag = parser.parseAnything(maxDepth); + parser.ptr.skipWhitespace(); + if (parser.ptr.hasNext()) { + throw parser.ptr.parseException("invalid characters after end of snbt"); + } + return tag; } public static Tag parse(String string) throws ParseException { @@ -68,28 +75,40 @@ private Tag parseStringOrLiteral() throws ParseException { if (s.isEmpty()) { throw new ParseException("expected non empty value"); } - try { - if (FLOAT_PATTERN.matcher(s).matches()) { - return new FloatTag(Float.parseFloat(s.substring(0, s.length() - 1))); - } else if (BYTE_PATTERN.matcher(s).matches()) { + if (FLOAT_LITERAL_PATTERN.matcher(s).matches()) { + return new FloatTag(Float.parseFloat(s.substring(0, s.length() - 1))); + } else if (BYTE_LITERAL_PATTERN.matcher(s).matches()) { + try { return new ByteTag(Byte.parseByte(s.substring(0, s.length() - 1))); - } else if (SHORT_PATTERN.matcher(s).matches()) { + } catch (NumberFormatException ex) { + throw ptr.parseException("byte not in range: \"" + s.substring(0, s.length() - 1) + "\""); + } + } else if (SHORT_LITERAL_PATTERN.matcher(s).matches()) { + try { return new ShortTag(Short.parseShort(s.substring(0, s.length() - 1))); - } else if (LONG_PATTERN.matcher(s).matches()) { + } catch (NumberFormatException ex) { + throw ptr.parseException("short not in range: \"" + s.substring(0, s.length() - 1) + "\""); + } + } else if (LONG_LITERAL_PATTERN.matcher(s).matches()) { + try { return new LongTag(Long.parseLong(s.substring(0, s.length() - 1))); - } else if (INT_PATTERN.matcher(s).matches()) { + } catch (NumberFormatException ex) { + throw ptr.parseException("long not in range: \"" + s.substring(0, s.length() - 1) + "\""); + } + } else if (INT_LITERAL_PATTERN.matcher(s).matches()) { + try { return new IntTag(Integer.parseInt(s)); - } else if (DOUBLE_PATTERN.matcher(s).matches()) { - return new DoubleTag(Double.parseDouble(s.substring(0, s.length() - 1))); - } else if (DOUBLE_NO_SUFFIX_PATTERN.matcher(s).matches()) { - return new DoubleTag(Double.parseDouble(s)); - } else if ("true".equalsIgnoreCase(s)) { - return new ByteTag(true); - } else if ("false".equalsIgnoreCase(s)) { - return new ByteTag(false); + } catch (NumberFormatException ex) { + throw ptr.parseException("int not in range: \"" + s.substring(0, s.length() - 1) + "\""); } - } catch (NumberFormatException ex) { - return new StringTag(s); + } else if (DOUBLE_LITERAL_PATTERN.matcher(s).matches()) { + return new DoubleTag(Double.parseDouble(s.substring(0, s.length() - 1))); + } else if (DOUBLE_LITERAL_NO_SUFFIX_PATTERN.matcher(s).matches()) { + return new DoubleTag(Double.parseDouble(s)); + } else if ("true".equalsIgnoreCase(s)) { + return new ByteTag(true); + } else if ("false".equalsIgnoreCase(s)) { + return new ByteTag(false); } return new StringTag(s); } @@ -154,10 +173,14 @@ private ByteArrayTag parseByteArrayTag() throws ParseException { while (ptr.currentChar() != ']') { String s = ptr.parseSimpleString(); ptr.skipWhitespace(); - if (BYTE_PATTERN.matcher(s).matches()) { - byteList.add(Byte.parseByte(s.substring(0, s.length() - 1))); + if (NUMBER_PATTERN.matcher(s).matches()) { + try { + byteList.add(Byte.parseByte(s)); + } catch (NumberFormatException ex) { + throw ptr.parseException("byte not in range: \"" + s + "\""); + } } else { - throw ptr.parseException("invalid byte literal in ByteArrayTag: \"" + s + "\""); + throw ptr.parseException("invalid byte in ByteArrayTag: \"" + s + "\""); } if (!ptr.nextArrayElement()) { break; @@ -176,10 +199,14 @@ private IntArrayTag parseIntArrayTag() throws ParseException { while (ptr.currentChar() != ']') { String s = ptr.parseSimpleString(); ptr.skipWhitespace(); - if (INT_PATTERN.matcher(s).matches()) { - intList.add(Integer.parseInt(s.substring(0, s.length() - 1))); + if (NUMBER_PATTERN.matcher(s).matches()) { + try { + intList.add(Integer.parseInt(s)); + } catch (NumberFormatException ex) { + throw ptr.parseException("int not in range: \"" + s + "\""); + } } else { - throw ptr.parseException("invalid int literal in IntArrayTag: \"" + s + "\""); + throw ptr.parseException("invalid int in IntArrayTag: \"" + s + "\""); } if (!ptr.nextArrayElement()) { break; @@ -194,10 +221,14 @@ private LongArrayTag parseLongArrayTag() throws ParseException { while (ptr.currentChar() != ']') { String s = ptr.parseSimpleString(); ptr.skipWhitespace(); - if (LONG_PATTERN.matcher(s).matches()) { - longList.add(Long.parseLong(s.substring(0, s.length() - 1))); + if (NUMBER_PATTERN.matcher(s).matches()) { + try { + longList.add(Long.parseLong(s)); + } catch (NumberFormatException ex) { + throw ptr.parseException("long not in range: \"" + s + "\""); + } } else { - throw ptr.parseException("invalid long literal in LongArrayTag: \"" + s + "\""); + throw ptr.parseException("invalid long in LongArrayTag: \"" + s + "\""); } if (!ptr.nextArrayElement()) { break; diff --git a/src/test/java/net/querz/nbt/tag/ExceptionRunnable.java b/src/test/java/net/querz/ExceptionRunnable.java similarity index 79% rename from src/test/java/net/querz/nbt/tag/ExceptionRunnable.java rename to src/test/java/net/querz/ExceptionRunnable.java index bfc2afe7..ebf57da8 100644 --- a/src/test/java/net/querz/nbt/tag/ExceptionRunnable.java +++ b/src/test/java/net/querz/ExceptionRunnable.java @@ -1,4 +1,4 @@ -package net.querz.nbt.tag; +package net.querz; @FunctionalInterface public interface ExceptionRunnable { diff --git a/src/test/java/net/querz/nbt/tag/ExceptionSupplier.java b/src/test/java/net/querz/ExceptionSupplier.java similarity index 79% rename from src/test/java/net/querz/nbt/tag/ExceptionSupplier.java rename to src/test/java/net/querz/ExceptionSupplier.java index e4b8aaee..98de04ed 100644 --- a/src/test/java/net/querz/nbt/tag/ExceptionSupplier.java +++ b/src/test/java/net/querz/ExceptionSupplier.java @@ -1,4 +1,4 @@ -package net.querz.nbt.tag; +package net.querz; @FunctionalInterface public interface ExceptionSupplier { diff --git a/src/test/java/net/querz/nbt/tag/NBTTestCase.java b/src/test/java/net/querz/NBTTestCase.java similarity index 99% rename from src/test/java/net/querz/nbt/tag/NBTTestCase.java rename to src/test/java/net/querz/NBTTestCase.java index 5efe2bdc..d1935be5 100644 --- a/src/test/java/net/querz/nbt/tag/NBTTestCase.java +++ b/src/test/java/net/querz/NBTTestCase.java @@ -1,10 +1,10 @@ -package net.querz.nbt.tag; +package net.querz; import junit.framework.TestCase; import net.querz.nbt.io.NBTDeserializer; import net.querz.nbt.io.NBTSerializer; -import net.querz.nbt.io.NBTUtil; import net.querz.nbt.io.NamedTag; +import net.querz.nbt.tag.Tag; import java.io.BufferedInputStream; import java.io.ByteArrayInputStream; diff --git a/src/test/java/net/querz/mca/MCATestCase.java b/src/test/java/net/querz/mca/MCATestCase.java index d8c4d462..1c87e4c7 100644 --- a/src/test/java/net/querz/mca/MCATestCase.java +++ b/src/test/java/net/querz/mca/MCATestCase.java @@ -2,7 +2,7 @@ import net.querz.nbt.tag.CompoundTag; import net.querz.nbt.tag.ListTag; -import net.querz.nbt.tag.NBTTestCase; +import net.querz.NBTTestCase; public abstract class MCATestCase extends NBTTestCase { diff --git a/src/test/java/net/querz/nbt/io/NamedTagTest.java b/src/test/java/net/querz/nbt/io/NamedTagTest.java new file mode 100644 index 00000000..2dd2d11c --- /dev/null +++ b/src/test/java/net/querz/nbt/io/NamedTagTest.java @@ -0,0 +1,25 @@ +package net.querz.nbt.io; + +import net.querz.NBTTestCase; +import net.querz.nbt.tag.ByteTag; +import net.querz.nbt.tag.ShortTag; + +public class NamedTagTest extends NBTTestCase { + + public void testCreate() { + ByteTag t = new ByteTag(); + NamedTag n = new NamedTag("name", t); + assertEquals("name", n.getName()); + assertTrue(n.getTag() == t); + } + + public void testSet() { + ByteTag t = new ByteTag(); + NamedTag n = new NamedTag("name", t); + n.setName("blah"); + assertEquals("blah", n.getName()); + ShortTag s = new ShortTag(); + n.setTag(s); + assertTrue(n.getTag() == s); + } +} diff --git a/src/test/java/net/querz/nbt/io/SNBTParserTest.java b/src/test/java/net/querz/nbt/io/SNBTParserTest.java new file mode 100644 index 00000000..b156f1e5 --- /dev/null +++ b/src/test/java/net/querz/nbt/io/SNBTParserTest.java @@ -0,0 +1,145 @@ +package net.querz.nbt.io; + +import net.querz.NBTTestCase; +import net.querz.nbt.tag.ByteArrayTag; +import net.querz.nbt.tag.ByteTag; +import net.querz.nbt.tag.CompoundTag; +import net.querz.nbt.tag.DoubleTag; +import net.querz.nbt.tag.FloatTag; +import net.querz.nbt.tag.IntArrayTag; +import net.querz.nbt.tag.IntTag; +import net.querz.nbt.tag.LongArrayTag; +import net.querz.nbt.tag.LongTag; +import net.querz.nbt.tag.ShortTag; +import net.querz.nbt.tag.StringTag; +import net.querz.nbt.tag.Tag; + +import java.util.Arrays; + +public class SNBTParserTest extends NBTTestCase { + + public void testParse() { + Tag t = assertThrowsNoException(() -> SNBTParser.parse("{abc: def, blah: 4b, blubb: \"string\", \"foo\": 2s}")); + assertEquals(CompoundTag.class, t.getClass()); + CompoundTag c = (CompoundTag) t; + assertEquals(4, c.size()); + assertEquals("def", c.getString("abc")); + assertEquals((byte) 4, c.getByte("blah")); + assertEquals("string", c.getString("blubb")); + assertEquals((short) 2, c.getShort("foo")); + assertFalse(c.containsKey("invalid")); + + // ------------------------------------------------- number tags + + Tag tb = assertThrowsNoException(() -> SNBTParser.parse("16b")); + assertEquals(ByteTag.class, tb.getClass()); + assertEquals((byte) 16, ((ByteTag) tb).asByte()); + + tb = assertThrowsNoException(() -> SNBTParser.parse("16B")); + assertEquals(ByteTag.class, tb.getClass()); + assertEquals((byte) 16, ((ByteTag) tb).asByte()); + + assertThrowsException((() -> SNBTParser.parse("-129b")), ParseException.class); + + Tag ts = assertThrowsNoException(() -> SNBTParser.parse("17s")); + assertEquals(ShortTag.class, ts.getClass()); + assertEquals((short) 17, ((ShortTag) ts).asShort()); + + ts = assertThrowsNoException(() -> SNBTParser.parse("17S")); + assertEquals(ShortTag.class, ts.getClass()); + assertEquals((short) 17, ((ShortTag) ts).asShort()); + + assertThrowsException((() -> SNBTParser.parse("-32769s")), ParseException.class); + + Tag ti = assertThrowsNoException(() -> SNBTParser.parse("18")); + assertEquals(IntTag.class, ti.getClass()); + assertEquals(18, ((IntTag) ti).asInt()); + + assertThrowsException((() -> SNBTParser.parse("-2147483649")), ParseException.class); + + Tag tl = assertThrowsNoException(() -> SNBTParser.parse("19l")); + assertEquals(LongTag.class, tl.getClass()); + assertEquals(19L, ((LongTag) tl).asLong()); + + tl = assertThrowsNoException(() -> SNBTParser.parse("19L")); + assertEquals(LongTag.class, tl.getClass()); + assertEquals(19L, ((LongTag) tl).asLong()); + + assertThrowsException((() -> SNBTParser.parse("-9223372036854775809l")), ParseException.class); + + Tag tf = assertThrowsNoException(() -> SNBTParser.parse("20.3f")); + assertEquals(FloatTag.class, tf.getClass()); + assertEquals(20.3f, ((FloatTag) tf).asFloat()); + + tf = assertThrowsNoException(() -> SNBTParser.parse("20.3F")); + assertEquals(FloatTag.class, tf.getClass()); + assertEquals(20.3f, ((FloatTag) tf).asFloat()); + + Tag td = assertThrowsNoException(() -> SNBTParser.parse("21.3d")); + assertEquals(DoubleTag.class, td.getClass()); + assertEquals(21.3d, ((DoubleTag) td).asDouble()); + + td = assertThrowsNoException(() -> SNBTParser.parse("21.3D")); + assertEquals(DoubleTag.class, td.getClass()); + assertEquals(21.3d, ((DoubleTag) td).asDouble()); + + td = assertThrowsNoException(() -> SNBTParser.parse("21.3")); + assertEquals(DoubleTag.class, td.getClass()); + assertEquals(21.3d, ((DoubleTag) td).asDouble()); + + Tag tbo = assertThrowsNoException(() -> SNBTParser.parse("true")); + assertEquals(ByteTag.class, tbo.getClass()); + assertEquals((byte) 1, ((ByteTag) tbo).asByte()); + + tbo = assertThrowsNoException(() -> SNBTParser.parse("false")); + assertEquals(ByteTag.class, tbo.getClass()); + assertEquals((byte) 0, ((ByteTag) tbo).asByte()); + + // ------------------------------------------------- arrays + + Tag ba = assertThrowsNoException(() -> SNBTParser.parse("[B; -128,0, 127]")); + assertEquals(ByteArrayTag.class, ba.getClass()); + assertEquals(3, ((ByteArrayTag) ba).length()); + assertTrue(Arrays.equals(new byte[]{-128, 0, 127}, ((ByteArrayTag) ba).getValue())); + + Tag ia = assertThrowsNoException(() -> SNBTParser.parse("[I; -2147483648, 0,2147483647]")); + assertEquals(IntArrayTag.class, ia.getClass()); + assertEquals(3, ((IntArrayTag) ia).length()); + assertTrue(Arrays.equals(new int[]{-2147483648, 0, 2147483647}, ((IntArrayTag) ia).getValue())); + + Tag la = assertThrowsNoException(() -> SNBTParser.parse("[L; -9223372036854775808, 0, 9223372036854775807 ]")); + assertEquals(LongArrayTag.class, la.getClass()); + assertEquals(3, ((LongArrayTag) la).length()); + assertTrue(Arrays.equals(new long[]{-9223372036854775808L, 0, 9223372036854775807L}, ((LongArrayTag) la).getValue())); + + // ------------------------------------------------- invalid arrays + + assertThrowsException((() -> SNBTParser.parse("[B; -129]")), ParseException.class); + assertThrowsException((() -> SNBTParser.parse("[I; -2147483649]")), ParseException.class); + assertThrowsException((() -> SNBTParser.parse("[L; -9223372036854775809]")), ParseException.class); + assertThrowsException((() -> SNBTParser.parse("[B; 123b]")), ParseException.class); + assertThrowsException((() -> SNBTParser.parse("[I; 123i]")), ParseException.class); + assertThrowsException((() -> SNBTParser.parse("[L; 123l]")), ParseException.class); + assertThrowsException((() -> SNBTParser.parse("[K; -129]")), ParseException.class); + + // ------------------------------------------------- high level errors + + assertThrowsException(() -> SNBTParser.parse("{20:10} {blah:blubb}"), ParseException.class); + + // ------------------------------------------------- string tag + + Tag st = assertThrowsNoException(() -> SNBTParser.parse("abc")); + assertEquals(StringTag.class, st.getClass()); + assertEquals("abc", ((StringTag) st).getValue()); + + st = assertThrowsNoException(() -> SNBTParser.parse("\"abc\"")); + assertEquals(StringTag.class, st.getClass()); + assertEquals("abc", ((StringTag) st).getValue()); + + st = assertThrowsNoException(() -> SNBTParser.parse("123a")); + assertEquals(StringTag.class, st.getClass()); + assertEquals("123a", ((StringTag) st).getValue()); + + // ------------------------------------------------- + } +} diff --git a/src/test/java/net/querz/nbt/io/StringPointerTest.java b/src/test/java/net/querz/nbt/io/StringPointerTest.java new file mode 100644 index 00000000..1030c114 --- /dev/null +++ b/src/test/java/net/querz/nbt/io/StringPointerTest.java @@ -0,0 +1,90 @@ +package net.querz.nbt.io; + +import net.querz.NBTTestCase; + +public class StringPointerTest extends NBTTestCase { + + public void testLookAhead() { + StringPointer ptr = new StringPointer("abcdefg"); + char c = ptr.lookAhead(3); + assertEquals('d', c); + assertEquals('a', ptr.currentChar()); + } + + public void testSkip() { + StringPointer ptr = new StringPointer("abcdefg"); + ptr.skip(3); + assertEquals('d', ptr.currentChar()); + } + + public void testNext() { + StringPointer ptr = new StringPointer("abcdefg"); + assertEquals('a', ptr.next()); + assertEquals('b', ptr.currentChar()); + } + + public void testCurrentChar() { + StringPointer ptr = new StringPointer("abcdefg"); + assertEquals('a', ptr.currentChar()); + ptr.skip(3); + assertEquals('d', ptr.currentChar()); + } + + public void testHasCharsLeft() { + StringPointer ptr = new StringPointer("abcdefg"); + assertTrue(ptr.hasCharsLeft(1)); + assertTrue(ptr.hasCharsLeft(6)); + assertFalse(ptr.hasCharsLeft(7)); + } + + public void testHasNext() { + StringPointer ptr = new StringPointer("abcdefg"); + assertTrue(ptr.hasNext()); + ptr.skip(6); + assertTrue(ptr.hasNext()); + ptr.skip(1); + assertFalse(ptr.hasNext()); + } + + public void testSkipWhitespace() { + StringPointer ptr = new StringPointer("abc \t defg"); + ptr.skip(3); + assertEquals(' ', ptr.currentChar()); + ptr.skipWhitespace(); + assertEquals('d', ptr.currentChar()); + } + + public void testExpectChar() { + StringPointer ptr = new StringPointer("abcdefg"); + assertThrowsNoException(() -> ptr.expectChar('a')); + assertThrowsException(() -> ptr.expectChar('a'), ParseException.class); + } + + public void testNextArrayElement() { + StringPointer ptr = new StringPointer("1, 2, 3"); + ptr.next(); + assertTrue(ptr.nextArrayElement()); + ptr.next(); + assertTrue(ptr.nextArrayElement()); + ptr.next(); + assertFalse(ptr.nextArrayElement()); + } + + public void testParseQuotedString() { + StringPointer ptr = new StringPointer("\"abcdefg\""); + assertEquals("abcdefg", assertThrowsNoException(ptr::parseQuotedString)); + ptr = new StringPointer("\"abc\\def\""); + assertThrowsException(ptr::parseQuotedString, ParseException.class); + ptr = new StringPointer("\"abc\\\\def\\\\\""); + assertEquals("abc\\def\\", assertThrowsNoException(ptr::parseQuotedString)); + ptr = new StringPointer("\"abc"); + assertThrowsException(ptr::parseQuotedString, ParseException.class); + } + + public void testParseSimpleString() { + StringPointer ptr = new StringPointer("abcdefg},{something else}"); + assertEquals("abcdefg", ptr.parseSimpleString()); + ptr = new StringPointer("abcdefg"); + assertEquals("abcdefg", ptr.parseSimpleString()); + } +} diff --git a/src/test/java/net/querz/nbt/tag/ByteArrayTagTest.java b/src/test/java/net/querz/nbt/tag/ByteArrayTagTest.java index bd06956a..1d10e033 100644 --- a/src/test/java/net/querz/nbt/tag/ByteArrayTagTest.java +++ b/src/test/java/net/querz/nbt/tag/ByteArrayTagTest.java @@ -1,5 +1,7 @@ package net.querz.nbt.tag; +import net.querz.NBTTestCase; + import java.util.Arrays; public class ByteArrayTagTest extends NBTTestCase { diff --git a/src/test/java/net/querz/nbt/tag/ByteTagTest.java b/src/test/java/net/querz/nbt/tag/ByteTagTest.java index aa491087..ff9451d0 100644 --- a/src/test/java/net/querz/nbt/tag/ByteTagTest.java +++ b/src/test/java/net/querz/nbt/tag/ByteTagTest.java @@ -1,5 +1,7 @@ package net.querz.nbt.tag; +import net.querz.NBTTestCase; + import java.util.Arrays; public class ByteTagTest extends NBTTestCase { diff --git a/src/test/java/net/querz/nbt/tag/CompoundTagTest.java b/src/test/java/net/querz/nbt/tag/CompoundTagTest.java index 06316dcb..4e6ff446 100644 --- a/src/test/java/net/querz/nbt/tag/CompoundTagTest.java +++ b/src/test/java/net/querz/nbt/tag/CompoundTagTest.java @@ -1,6 +1,7 @@ package net.querz.nbt.tag; import net.querz.io.MaxDepthReachedException; +import net.querz.NBTTestCase; import java.util.Arrays; import java.util.LinkedHashMap; diff --git a/src/test/java/net/querz/nbt/tag/DoubleTagTest.java b/src/test/java/net/querz/nbt/tag/DoubleTagTest.java index a87e484b..024fca99 100644 --- a/src/test/java/net/querz/nbt/tag/DoubleTagTest.java +++ b/src/test/java/net/querz/nbt/tag/DoubleTagTest.java @@ -1,5 +1,7 @@ package net.querz.nbt.tag; +import net.querz.NBTTestCase; + import static org.junit.Assert.assertNotEquals; import java.util.Arrays; diff --git a/src/test/java/net/querz/nbt/tag/EndTagTest.java b/src/test/java/net/querz/nbt/tag/EndTagTest.java index f479b34d..0044f78d 100644 --- a/src/test/java/net/querz/nbt/tag/EndTagTest.java +++ b/src/test/java/net/querz/nbt/tag/EndTagTest.java @@ -1,5 +1,7 @@ package net.querz.nbt.tag; +import net.querz.NBTTestCase; + public class EndTagTest extends NBTTestCase { public void testStringConversion() { diff --git a/src/test/java/net/querz/nbt/tag/FloatTagTest.java b/src/test/java/net/querz/nbt/tag/FloatTagTest.java index 9b93e0d2..006b40bf 100644 --- a/src/test/java/net/querz/nbt/tag/FloatTagTest.java +++ b/src/test/java/net/querz/nbt/tag/FloatTagTest.java @@ -1,5 +1,7 @@ package net.querz.nbt.tag; +import net.querz.NBTTestCase; + import static org.junit.Assert.assertNotEquals; import java.util.Arrays; diff --git a/src/test/java/net/querz/nbt/tag/IntArrayTagTest.java b/src/test/java/net/querz/nbt/tag/IntArrayTagTest.java index 51b82442..1458a732 100644 --- a/src/test/java/net/querz/nbt/tag/IntArrayTagTest.java +++ b/src/test/java/net/querz/nbt/tag/IntArrayTagTest.java @@ -1,5 +1,7 @@ package net.querz.nbt.tag; +import net.querz.NBTTestCase; + import java.util.Arrays; public class IntArrayTagTest extends NBTTestCase { diff --git a/src/test/java/net/querz/nbt/tag/IntTagTest.java b/src/test/java/net/querz/nbt/tag/IntTagTest.java index 57f1030c..6c15fa42 100644 --- a/src/test/java/net/querz/nbt/tag/IntTagTest.java +++ b/src/test/java/net/querz/nbt/tag/IntTagTest.java @@ -1,5 +1,7 @@ package net.querz.nbt.tag; +import net.querz.NBTTestCase; + import java.util.Arrays; public class IntTagTest extends NBTTestCase { diff --git a/src/test/java/net/querz/nbt/tag/ListTagTest.java b/src/test/java/net/querz/nbt/tag/ListTagTest.java index 64dc5de7..d7e42690 100644 --- a/src/test/java/net/querz/nbt/tag/ListTagTest.java +++ b/src/test/java/net/querz/nbt/tag/ListTagTest.java @@ -2,6 +2,8 @@ import junit.framework.TestCase; import net.querz.io.MaxDepthReachedException; +import net.querz.NBTTestCase; + import java.util.Arrays; import java.util.Comparator; import static org.junit.Assert.assertNotEquals; diff --git a/src/test/java/net/querz/nbt/tag/LongArrayTagTest.java b/src/test/java/net/querz/nbt/tag/LongArrayTagTest.java index d251d49f..dfa2a3f3 100644 --- a/src/test/java/net/querz/nbt/tag/LongArrayTagTest.java +++ b/src/test/java/net/querz/nbt/tag/LongArrayTagTest.java @@ -1,5 +1,7 @@ package net.querz.nbt.tag; +import net.querz.NBTTestCase; + import java.util.Arrays; public class LongArrayTagTest extends NBTTestCase { diff --git a/src/test/java/net/querz/nbt/tag/LongTagTest.java b/src/test/java/net/querz/nbt/tag/LongTagTest.java index 02b4cf67..86ef1ff2 100644 --- a/src/test/java/net/querz/nbt/tag/LongTagTest.java +++ b/src/test/java/net/querz/nbt/tag/LongTagTest.java @@ -1,5 +1,7 @@ package net.querz.nbt.tag; +import net.querz.NBTTestCase; + import java.util.Arrays; public class LongTagTest extends NBTTestCase { diff --git a/src/test/java/net/querz/nbt/tag/NoNullEntrySetTest.java b/src/test/java/net/querz/nbt/tag/NoNullEntrySetTest.java index 33d8df01..74d628b2 100644 --- a/src/test/java/net/querz/nbt/tag/NoNullEntrySetTest.java +++ b/src/test/java/net/querz/nbt/tag/NoNullEntrySetTest.java @@ -1,5 +1,7 @@ package net.querz.nbt.tag; +import net.querz.NBTTestCase; + import java.util.Arrays; import java.util.Map; import java.util.TreeMap; diff --git a/src/test/java/net/querz/nbt/tag/ShortTagTest.java b/src/test/java/net/querz/nbt/tag/ShortTagTest.java index c1af1fb4..9668c248 100644 --- a/src/test/java/net/querz/nbt/tag/ShortTagTest.java +++ b/src/test/java/net/querz/nbt/tag/ShortTagTest.java @@ -1,5 +1,7 @@ package net.querz.nbt.tag; +import net.querz.NBTTestCase; + import java.util.Arrays; public class ShortTagTest extends NBTTestCase { diff --git a/src/test/java/net/querz/nbt/tag/StringTagTest.java b/src/test/java/net/querz/nbt/tag/StringTagTest.java index 2d50ee97..e008a55a 100644 --- a/src/test/java/net/querz/nbt/tag/StringTagTest.java +++ b/src/test/java/net/querz/nbt/tag/StringTagTest.java @@ -1,5 +1,7 @@ package net.querz.nbt.tag; +import net.querz.NBTTestCase; + import java.util.Arrays; public class StringTagTest extends NBTTestCase { From 44457a55381b59c84f4afd2a9d3d8109bd821f72 Mon Sep 17 00:00:00 2001 From: Querz Date: Sat, 25 Apr 2020 19:01:16 +0200 Subject: [PATCH 09/51] more unit tests for snbt parser and writer --- src/main/java/net/querz/mca/Chunk.java | 6 +- src/main/java/net/querz/mca/Section.java | 4 +- src/main/java/net/querz/nbt/io/NBTUtil.java | 8 --- .../java/net/querz/nbt/io/SNBTParser.java | 6 +- .../java/net/querz/nbt/io/SNBTSerializer.java | 12 +--- src/main/java/net/querz/nbt/io/SNBTUtil.java | 15 +++++ .../java/net/querz/nbt/io/SNBTWriter.java | 12 ++-- src/main/java/net/querz/nbt/tag/ListTag.java | 2 +- .../java/net/querz/nbt/io/SNBTParserTest.java | 42 +++++++++----- .../java/net/querz/nbt/io/SNBTWriterTest.java | 57 +++++++++++++++++++ .../net/querz/nbt/tag/CompoundTagTest.java | 4 -- .../java/net/querz/nbt/tag/ListTagTest.java | 3 - 12 files changed, 120 insertions(+), 51 deletions(-) create mode 100644 src/main/java/net/querz/nbt/io/SNBTUtil.java create mode 100644 src/test/java/net/querz/nbt/io/SNBTWriterTest.java diff --git a/src/main/java/net/querz/mca/Chunk.java b/src/main/java/net/querz/mca/Chunk.java index 364e0703..1d22fb04 100644 --- a/src/main/java/net/querz/mca/Chunk.java +++ b/src/main/java/net/querz/mca/Chunk.java @@ -386,7 +386,11 @@ public CompoundTag updateHandle(int xPos, int zPos) { level.putInt("zPos", zPos); level.putLong("LastUpdate", lastUpdate); level.putLong("InhabitedTime", inhabitedTime); - if (biomes != null && biomes.length == 256) level.putIntArray("Biomes", biomes); + if (dataVersion < 2202) { + if (biomes != null && biomes.length == 256) level.putIntArray("Biomes", biomes); + } else { + if (biomes != null && biomes.length == 1024) level.putIntArray("Biomes", biomes); + } if (heightMaps != null) level.put("HeightMaps", heightMaps); if (carvingMasks != null) level.put("CarvingMasks", carvingMasks); if (entities != null) level.put("Entities", entities); diff --git a/src/main/java/net/querz/mca/Section.java b/src/main/java/net/querz/mca/Section.java index 1e8c9541..742ec617 100644 --- a/src/main/java/net/querz/mca/Section.java +++ b/src/main/java/net/querz/mca/Section.java @@ -63,7 +63,7 @@ PaletteIndex getValueIndexedPalette(CompoundTag data) { return null; } - private class PaletteIndex { + private static class PaletteIndex { CompoundTag data; int index; @@ -218,7 +218,7 @@ void adjustBlockStateBits(Map oldToNewMapping, long[] blockSta //if the palette had been cleaned up before using MCAFile#cleanupPalette(). int newBits = 32 - Integer.numberOfLeadingZeros(palette.size() - 1); - newBits = newBits < 4 ? 4 : newBits; + newBits = Math.max(newBits, 4); long[] newBlockStates = newBits == blockStates.length / 64 ? blockStates : new long[newBits * 64]; if (oldToNewMapping != null) { diff --git a/src/main/java/net/querz/nbt/io/NBTUtil.java b/src/main/java/net/querz/nbt/io/NBTUtil.java index 8ae1414f..d8efc150 100644 --- a/src/main/java/net/querz/nbt/io/NBTUtil.java +++ b/src/main/java/net/querz/nbt/io/NBTUtil.java @@ -77,12 +77,4 @@ private static InputStream detectDecompression(InputStream is) throws IOExceptio } return pbis; } - - public static String toSNBT(Tag tag) throws IOException { - return new SNBTSerializer().toString(tag); - } - - public static Tag fromSNBT(String string) throws IOException { - return new SNBTDeserializer().fromString(string); - } } diff --git a/src/main/java/net/querz/nbt/io/SNBTParser.java b/src/main/java/net/querz/nbt/io/SNBTParser.java index 088540f7..7a9ac5d2 100644 --- a/src/main/java/net/querz/nbt/io/SNBTParser.java +++ b/src/main/java/net/querz/nbt/io/SNBTParser.java @@ -143,7 +143,11 @@ private ListTag parseListTag(int maxDepth) throws ParseException { ListTag list = ListTag.createUnchecked(EndTag.class); while (ptr.currentChar() != ']') { Tag element = parseAnything(decrementMaxDepth(maxDepth)); - list.addUnchecked(element); + try { + list.addUnchecked(element); + } catch (IllegalArgumentException ex) { + throw ptr.parseException(ex.getMessage()); + } if (!ptr.nextArrayElement()) { break; } diff --git a/src/main/java/net/querz/nbt/io/SNBTSerializer.java b/src/main/java/net/querz/nbt/io/SNBTSerializer.java index 523c9d41..50ea44a6 100644 --- a/src/main/java/net/querz/nbt/io/SNBTSerializer.java +++ b/src/main/java/net/querz/nbt/io/SNBTSerializer.java @@ -7,19 +7,9 @@ public class SNBTSerializer implements StringSerializer> { - private final boolean pretty; - - public SNBTSerializer() { - this(false); - } - - public SNBTSerializer(boolean pretty) { - this.pretty = pretty; - } - @Override public void toWriter(Tag tag, Writer writer) throws IOException { - toWriter(tag, writer, Tag.DEFAULT_MAX_DEPTH); + SNBTWriter.write(tag, writer); } public void toWriter(Tag tag, Writer writer, int maxDepth) throws IOException { diff --git a/src/main/java/net/querz/nbt/io/SNBTUtil.java b/src/main/java/net/querz/nbt/io/SNBTUtil.java new file mode 100644 index 00000000..e29981fc --- /dev/null +++ b/src/main/java/net/querz/nbt/io/SNBTUtil.java @@ -0,0 +1,15 @@ +package net.querz.nbt.io; + +import net.querz.nbt.tag.Tag; +import java.io.IOException; + +public class SNBTUtil { + + public static String toSNBT(Tag tag) throws IOException { + return new SNBTSerializer().toString(tag); + } + + public static Tag fromSNBT(String string) throws IOException { + return new SNBTDeserializer().fromString(string); + } +} diff --git a/src/main/java/net/querz/nbt/io/SNBTWriter.java b/src/main/java/net/querz/nbt/io/SNBTWriter.java index 6fb17eab..c6cffcb7 100644 --- a/src/main/java/net/querz/nbt/io/SNBTWriter.java +++ b/src/main/java/net/querz/nbt/io/SNBTWriter.java @@ -27,7 +27,7 @@ * */ public final class SNBTWriter implements MaxDepthIO { - private static final Pattern NON_QUOTE_PATTERN = Pattern.compile("[a-zA-Z0-9_.+\\-]+"); + private static final Pattern NON_QUOTE_PATTERN = Pattern.compile("[a-zA-Z_.+\\-]+"); private Writer writer; @@ -67,7 +67,7 @@ private void writeAnything(Tag tag, int maxDepth) throws IOException { writer.append(Double.toString(((DoubleTag) tag).asDouble())).write('d'); break; case ByteArrayTag.ID: - writeArray(((ByteArrayTag) tag).getValue(), ((ByteArrayTag) tag).length(), "B", "b"); + writeArray(((ByteArrayTag) tag).getValue(), ((ByteArrayTag) tag).length(), "B"); break; case StringTag.ID: writer.write(escapeString(((StringTag) tag).getValue())); @@ -92,20 +92,20 @@ private void writeAnything(Tag tag, int maxDepth) throws IOException { writer.write('}'); break; case IntArrayTag.ID: - writeArray(((IntArrayTag) tag).getValue(), ((IntArrayTag) tag).length(), "I", ""); + writeArray(((IntArrayTag) tag).getValue(), ((IntArrayTag) tag).length(), "I"); break; case LongArrayTag.ID: - writeArray(((LongArrayTag) tag).getValue(), ((LongArrayTag) tag).length(), "L", "l"); + writeArray(((LongArrayTag) tag).getValue(), ((LongArrayTag) tag).length(), "L"); break; default: throw new IOException("unknown tag with id \"" + tag.getID() + "\""); } } - private void writeArray(Object array, int length, String prefix, String suffix) throws IOException { + private void writeArray(Object array, int length, String prefix) throws IOException { writer.append('[').append(prefix).write(';'); for (int i = 0; i < length; i++) { - writer.append(i == 0 ? "" : ",").append(Integer.toString((int) Array.get(array, i))).write(suffix); + writer.append(i == 0 ? "" : ",").write(Array.get(array, i).toString()); } writer.write(']'); } diff --git a/src/main/java/net/querz/nbt/tag/ListTag.java b/src/main/java/net/querz/nbt/tag/ListTag.java index 889ece91..b1fd3ade 100644 --- a/src/main/java/net/querz/nbt/tag/ListTag.java +++ b/src/main/java/net/querz/nbt/tag/ListTag.java @@ -304,7 +304,7 @@ public ListTag clone() { //TODO: make private @SuppressWarnings("unchecked") public void addUnchecked(Tag tag) { - if (typeClass != null && typeClass != tag.getClass()) { + if (typeClass != null && typeClass != tag.getClass() && typeClass != EndTag.class) { throw new IllegalArgumentException(String.format( "cannot add %s to ListTag<%s>", tag.getClass().getSimpleName(), typeClass.getSimpleName())); diff --git a/src/test/java/net/querz/nbt/io/SNBTParserTest.java b/src/test/java/net/querz/nbt/io/SNBTParserTest.java index b156f1e5..a345333c 100644 --- a/src/test/java/net/querz/nbt/io/SNBTParserTest.java +++ b/src/test/java/net/querz/nbt/io/SNBTParserTest.java @@ -1,19 +1,7 @@ package net.querz.nbt.io; import net.querz.NBTTestCase; -import net.querz.nbt.tag.ByteArrayTag; -import net.querz.nbt.tag.ByteTag; -import net.querz.nbt.tag.CompoundTag; -import net.querz.nbt.tag.DoubleTag; -import net.querz.nbt.tag.FloatTag; -import net.querz.nbt.tag.IntArrayTag; -import net.querz.nbt.tag.IntTag; -import net.querz.nbt.tag.LongArrayTag; -import net.querz.nbt.tag.LongTag; -import net.querz.nbt.tag.ShortTag; -import net.querz.nbt.tag.StringTag; -import net.querz.nbt.tag.Tag; - +import net.querz.nbt.tag.*; import java.util.Arrays; public class SNBTParserTest extends NBTTestCase { @@ -140,6 +128,32 @@ public void testParse() { assertEquals(StringTag.class, st.getClass()); assertEquals("123a", ((StringTag) st).getValue()); - // ------------------------------------------------- + // ------------------------------------------------- list tag + + Tag lt = assertThrowsNoException(() -> SNBTParser.parse("[abc, \"def\", \"123\" ]")); + assertEquals(ListTag.class, lt.getClass()); + assertEquals(StringTag.class, ((ListTag) lt).getTypeClass()); + assertEquals(3, ((ListTag) lt).size()); + assertEquals("abc", ((ListTag) lt).asStringTagList().get(0).getValue()); + assertEquals("def", ((ListTag) lt).asStringTagList().get(1).getValue()); + assertEquals("123", ((ListTag) lt).asStringTagList().get(2).getValue()); + + assertThrowsException(() -> SNBTParser.parse("[123, 456"), ParseException.class); + assertThrowsException(() -> SNBTParser.parse("[123, 456d]"), ParseException.class); + + // ------------------------------------------------- compound tag + + Tag ct = assertThrowsNoException(() -> SNBTParser.parse("{abc: def,\"key\": 123d, blah: [L;123, 456], blubb: [123, 456]}")); + assertEquals(CompoundTag.class, ct.getClass()); + assertEquals(4, ((CompoundTag) ct).size()); + assertEquals("def", assertThrowsNoException(() -> ((CompoundTag) ct).getString("abc"))); + assertEquals(123D, assertThrowsNoException(() -> ((CompoundTag) ct).getDouble("key"))); + assertTrue(Arrays.equals(new long[]{123, 456}, assertThrowsNoException(() -> ((CompoundTag) ct).getLongArray("blah")))); + assertEquals(2, assertThrowsNoException(() -> ((CompoundTag) ct).getListTag("blubb")).size()); + assertEquals(IntTag.class, ((CompoundTag) ct).getListTag("blubb").getTypeClass()); + + assertThrowsException(() -> SNBTParser.parse("{abc: def"), ParseException.class); + assertThrowsException(() -> SNBTParser.parse("{\"\":empty}"), ParseException.class); + assertThrowsException(() -> SNBTParser.parse("{empty:}"), ParseException.class); } } diff --git a/src/test/java/net/querz/nbt/io/SNBTWriterTest.java b/src/test/java/net/querz/nbt/io/SNBTWriterTest.java new file mode 100644 index 00000000..2553802e --- /dev/null +++ b/src/test/java/net/querz/nbt/io/SNBTWriterTest.java @@ -0,0 +1,57 @@ +package net.querz.nbt.io; + +import net.querz.NBTTestCase; +import net.querz.nbt.tag.*; + +import java.util.LinkedHashMap; + +public class SNBTWriterTest extends NBTTestCase { + + public void testWrite() { + + // write number tags + + assertEquals("127b", assertThrowsNoException(() -> SNBTUtil.toSNBT(new ByteTag(Byte.MAX_VALUE)))); + assertEquals("-32768s", assertThrowsNoException(() -> SNBTUtil.toSNBT(new ShortTag(Short.MIN_VALUE)))); + assertEquals("-2147483648", assertThrowsNoException(() -> SNBTUtil.toSNBT(new IntTag(Integer.MIN_VALUE)))); + assertEquals("-9223372036854775808l", assertThrowsNoException(() -> SNBTUtil.toSNBT(new LongTag(Long.MIN_VALUE)))); + assertEquals("123.456f", assertThrowsNoException(() -> SNBTUtil.toSNBT(new FloatTag(123.456F)))); + assertEquals("123.456d", assertThrowsNoException(() -> SNBTUtil.toSNBT(new DoubleTag(123.456D)))); + + // write array tags + + assertEquals("[B;-128,0,127]", assertThrowsNoException(() -> SNBTUtil.toSNBT(new ByteArrayTag(new byte[]{Byte.MIN_VALUE, 0, Byte.MAX_VALUE})))); + assertEquals("[I;-2147483648,0,2147483647]", assertThrowsNoException(() -> SNBTUtil.toSNBT(new IntArrayTag(new int[]{Integer.MIN_VALUE, 0, Integer.MAX_VALUE})))); + assertEquals("[L;-9223372036854775808,0,9223372036854775807]", assertThrowsNoException(() -> SNBTUtil.toSNBT(new LongArrayTag(new long[]{Long.MIN_VALUE, 0, Long.MAX_VALUE})))); + + // write string tag + + assertEquals("abc", assertThrowsNoException(() -> SNBTUtil.toSNBT(new StringTag("abc")))); + assertEquals("\"123\"", assertThrowsNoException(() -> SNBTUtil.toSNBT(new StringTag("123")))); + assertEquals("\"123.456\"", assertThrowsNoException(() -> SNBTUtil.toSNBT(new StringTag("123.456")))); + assertEquals("\"-123\"", assertThrowsNoException(() -> SNBTUtil.toSNBT(new StringTag("-123")))); + assertEquals("\"-1.23e14\"", assertThrowsNoException(() -> SNBTUtil.toSNBT(new StringTag("-1.23e14")))); + assertEquals("\"äöü\\\\\"", assertThrowsNoException(() -> SNBTUtil.toSNBT(new StringTag("äöü\\")))); + + // write list tag + + ListTag lt = new ListTag<>(StringTag.class); + lt.addString("blah"); + lt.addString("blubb"); + lt.addString("123"); + assertEquals("[blah,blubb,\"123\"]", assertThrowsNoException(() -> SNBTUtil.toSNBT(lt))); + + // write compound tag + CompoundTag ct = new CompoundTag(); + invokeSetValue(ct, new LinkedHashMap<>()); + ct.putString("key", "value"); + ct.putByte("byte", Byte.MAX_VALUE); + ct.putByteArray("array", new byte[]{Byte.MIN_VALUE, 0, Byte.MAX_VALUE}); + ListTag clt = new ListTag<>(StringTag.class); + clt.addString("foo"); + clt.addString("bar"); + ct.put("list", clt); + String ctExpected = "{key:value,byte:127b,array:[B;-128,0,127],list:[foo,bar]}"; + assertEquals(ctExpected, assertThrowsNoException(() -> SNBTUtil.toSNBT(ct))); + } +} diff --git a/src/test/java/net/querz/nbt/tag/CompoundTagTest.java b/src/test/java/net/querz/nbt/tag/CompoundTagTest.java index 4e6ff446..fcfd73ca 100644 --- a/src/test/java/net/querz/nbt/tag/CompoundTagTest.java +++ b/src/test/java/net/querz/nbt/tag/CompoundTagTest.java @@ -279,10 +279,7 @@ public void testMaxDepth() { 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); } public void testRecursion() { @@ -290,7 +287,6 @@ public void testRecursion() { recursive.put("recursive", recursive); assertThrowsRuntimeException(() -> serialize(recursive), MaxDepthReachedException.class); assertThrowsRuntimeException(recursive::toString, MaxDepthReachedException.class); -// assertThrowsRuntimeException(recursive::toTagString, MaxDepthReachedException.class); } public void testEntrySet() { diff --git a/src/test/java/net/querz/nbt/tag/ListTagTest.java b/src/test/java/net/querz/nbt/tag/ListTagTest.java index d7e42690..bb1485e8 100644 --- a/src/test/java/net/querz/nbt/tag/ListTagTest.java +++ b/src/test/java/net/querz/nbt/tag/ListTagTest.java @@ -248,9 +248,7 @@ public void testMaxDepth() { assertThrowsRuntimeException(() -> serialize(root), MaxDepthReachedException.class); assertThrowsRuntimeException(() -> deserializeFromFile("max_depth_reached.dat"), MaxDepthReachedException.class); assertThrowsRuntimeException(root::toString, MaxDepthReachedException.class); -// assertThrowsRuntimeException(root::toTagString, MaxDepthReachedException.class); assertThrowsRuntimeException(() -> root.valueToString(-1), IllegalArgumentException.class); -// assertThrowsRuntimeException(() -> root.valueToTagString(-1), IllegalArgumentException.class); } public void testRecursion() { @@ -258,7 +256,6 @@ public void testRecursion() { recursive.add(recursive); assertThrowsRuntimeException(() -> serialize(recursive), MaxDepthReachedException.class); assertThrowsRuntimeException(recursive::toString, MaxDepthReachedException.class); -// assertThrowsRuntimeException(recursive::toTagString, MaxDepthReachedException.class); } public void testContains() { From 82190ca933f1b5c60c46f48048e863b0f0dda2a8 Mon Sep 17 00:00:00 2001 From: Querz Date: Sat, 25 Apr 2020 19:14:03 +0200 Subject: [PATCH 10/51] update version, util classes and readme --- LICENSE | 2 +- README.md | 38 ++++---------------- build.gradle | 24 +------------ src/main/java/net/querz/mca/MCAUtil.java | 32 ++++++++--------- src/test/java/net/querz/mca/MCAFileTest.java | 28 +++++++-------- src/test/java/net/querz/mca/MCAUtilTest.java | 14 ++++---- 6 files changed, 46 insertions(+), 92 deletions(-) diff --git a/LICENSE b/LICENSE index a786dfd1..3b203f48 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2016 Querz +Copyright (c) 2016 - 2020 Querz Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md index 004c51a2..5c7aad69 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # NBT [![Build Status](https://travis-ci.org/Querz/NBT.svg?branch=master)](https://travis-ci.org/Querz/NBT) [![Coverage Status](https://img.shields.io/coveralls/github/Querz/NBT/master.svg)](https://coveralls.io/github/Querz/NBT?branch=master) [![Release](https://jitpack.io/v/Querz/NBT.svg)](https://jitpack.io/#Querz/NBT) -#### A java implementation of the [NBT protocol](http://minecraft.gamepedia.com/NBT_format), including a way to implement custom tags. +#### A java implementation of the [NBT protocol](http://minecraft.gamepedia.com/NBT_format) for Minecraft Java Edition. --- ### Specification According to the [specification](https://minecraft.gamepedia.com/NBT_format), there are currently 13 different types of tags: @@ -56,33 +56,31 @@ Some methods do not provide a parameter to specify the maximum depth, but instea ### Utility There are several utility methods to make your life easier if you use this library. #### NBTUtil -`NBTUtil.writeTag()` lets you write a Tag into a gzip compressed or uncompressed file in one line (not counting exception handling). Files are gzip compressed by default. +`NBTUtil.write()` lets you write a Tag into a gzip compressed or uncompressed file in one line (not counting exception handling). Files are gzip compressed by default. Example usage: ```java -NBTUtil.writeTag(tag, "filename.dat"); +NBTUtil.write(namedTag, "filename.dat"); ``` -`NBTUtil.readTag()` reads any file containing NBT data. No worry about compression, it will automatically uncompress gzip compressed files. +`NBTUtil.read()` reads any file containing NBT data. No worry about compression, it will automatically uncompress gzip compressed files. Example usage: ```java -Tag tag = NBTUtil.readTag("filename.dat"); +NamedTag namedTag = NBTUtil.read("filename.dat"); ``` #### Playing Minecraft? -Each tag can be converted into a JSON-like NBT String used in Minecraft commands. +Each tag can be converted into an NBT String (SNBT) used in Minecraft commands. Example usage: ```java CompoundTag c = new CompoundTag(); c.putByte("blah", (byte) 5); c.putString("foo", "bär"); -System.out.println(c.toTagString()); // {blah:5b,foo:"bär"} - ListTag s = new ListTag<>(StringTag.class); s.addString("test"); s.add(new StringTag("text")); c.add("list", s); -System.out.println(c.toTagString()); // {blah:5b,foo:"bär",list:[test,text]} +System.out.println(SNBTUtil.toSNBT(c)); // {blah:5b,foo:"bär",list:[test,text]} ``` There is also a tool to read, change and write MCA files. @@ -117,25 +115,3 @@ mcaFile.cleanupPalettesAndBlockStates(); chunk.cleanupPalettesAndBlockStates(); section.cleanupPaletteAndBlockStates(); ``` - ---- -### Custom tags -Interested in more advanced features, and the default NBT protocol just isn't enough? Simply create your own tags! -There are 4 example classes in `net.querz.nbt.custom` that show how to implement custom tags: - -| Class | ID | Description | -| ------------- | :-: | ----------- | -| [ObjectTag](src/main/java/net/querz/nbt/custom/ObjectTag.java) | 90 | A wrapper tag that serializes and deserializes any object using the default java serialization. | -| [ShortArrayTag](src/main/java/net/querz/nbt/custom/ShortArrayTag.java) | 100 | In addition to the already existing `ByteArrayTag`, `IntArrayTag` and `LongArrayTag`. | -| [CharTag](src/main/java/net/querz/nbt/custom/CharTag.java) | 110 | `Character` (char) tag. | -| [StructTag](src/main/java/net/querz/nbt/custom/StructTag.java) | 120 | Similar to the `ListTag`, but with the ability to store multiple types. | - -To be able to use a custom tag with deserialization, a `Supplier` and the custom tag class must be registered at runtime alongside its id with `TagFactory.registerCustomTag()`. The `Supplier` can be anything that returns a new instance of this custom tag. Here is an example using the custom tags no-args constructor: -```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 4a37cb5e..7babd463 100644 --- a/build.gradle +++ b/build.gradle @@ -1,6 +1,5 @@ plugins { id 'com.github.kt3k.coveralls' version '2.4.0' - id 'maven-publish' } apply plugin: 'java' @@ -10,7 +9,7 @@ apply plugin: 'jacoco' group = 'net.querz.nbt' archivesBaseName = 'nbt' -version = '4.1' +version = '5.0' sourceCompatibility = '1.8' targetCompatibility = '1.8' compileJava.options.encoding = 'UTF-8' @@ -54,24 +53,3 @@ artifacts { archives sourcesJar archives javadocJar } - -jar { - manifest { - attributes('Automatic-Module-Name': 'net.querz.nbt') - } -} - -// for convenience, for pushing to local -publishing { - publications { - maven(MavenPublication) { - groupId = group - artifactId = archivesBaseName - version = version - artifact sourcesJar - artifact javadocJar - - from components.java - } - } -} diff --git a/src/main/java/net/querz/mca/MCAUtil.java b/src/main/java/net/querz/mca/MCAUtil.java index 8dfcd058..392773ba 100644 --- a/src/main/java/net/querz/mca/MCAUtil.java +++ b/src/main/java/net/querz/mca/MCAUtil.java @@ -17,13 +17,13 @@ public final class MCAUtil { private MCAUtil() {} /** - * @see MCAUtil#readMCAFile(File) + * @see MCAUtil#read(File) * @param file The file to read the data from. * @return An in-memory representation of the MCA file with decompressed chunk data. * @throws IOException if something during deserialization goes wrong. * */ - public static MCAFile readMCAFile(String file) throws IOException { - return readMCAFile(new File(file)); + public static MCAFile read(String file) throws IOException { + return read(new File(file)); } /** @@ -32,7 +32,7 @@ public static MCAFile readMCAFile(String file) throws IOException { * @return An in-memory representation of the MCA file with decompressed chunk data. * @throws IOException if something during deserialization goes wrong. * */ - public static MCAFile readMCAFile(File file) throws IOException { + public static MCAFile read(File file) throws IOException { MCAFile mcaFile = newMCAFile(file); try (RandomAccessFile raf = new RandomAccessFile(file, "r")) { mcaFile.deserialize(raf); @@ -41,39 +41,39 @@ public static MCAFile readMCAFile(File file) throws IOException { } /** - * Calls {@link MCAUtil#writeMCAFile(MCAFile, File, boolean)} without changing the timestamps. - * @see MCAUtil#writeMCAFile(MCAFile, File, boolean) + * Calls {@link MCAUtil#write(MCAFile, File, boolean)} without changing the timestamps. + * @see MCAUtil#write(MCAFile, File, boolean) * @param file The file to write to. * @param mcaFile The data of the MCA file to write. * @return The amount of chunks written to the file. * @throws IOException If something goes wrong during serialization. * */ - public static int writeMCAFile(MCAFile mcaFile, String file) throws IOException { - return writeMCAFile(mcaFile, new File(file), false); + public static int write(MCAFile mcaFile, String file) throws IOException { + return write(mcaFile, new File(file), false); } /** - * Calls {@link MCAUtil#writeMCAFile(MCAFile, File, boolean)} without changing the timestamps. - * @see MCAUtil#writeMCAFile(MCAFile, File, boolean) + * Calls {@link MCAUtil#write(MCAFile, File, boolean)} without changing the timestamps. + * @see MCAUtil#write(MCAFile, File, boolean) * @param file The file to write to. * @param mcaFile The data of the MCA file to write. * @return The amount of chunks written to the file. * @throws IOException If something goes wrong during serialization. * */ - public static int writeMCAFile(MCAFile mcaFile, File file) throws IOException { - return writeMCAFile(mcaFile, file, false); + public static int write(MCAFile mcaFile, File file) throws IOException { + return write(mcaFile, file, false); } /** - * @see MCAUtil#writeMCAFile(MCAFile, File, boolean) + * @see MCAUtil#write(MCAFile, File, boolean) * @param file The file to write to. * @param mcaFile The data of the MCA file to write. * @param changeLastUpdate Whether to adjust the timestamps of when the file was saved. * @return The amount of chunks written to the file. * @throws IOException If something goes wrong during serialization. * */ - public static int writeMCAFile(MCAFile mcaFile, String file, boolean changeLastUpdate) throws IOException { - return writeMCAFile(mcaFile, new File(file), changeLastUpdate); + public static int write(MCAFile mcaFile, String file, boolean changeLastUpdate) throws IOException { + return write(mcaFile, new File(file), changeLastUpdate); } /** @@ -87,7 +87,7 @@ public static int writeMCAFile(MCAFile mcaFile, String file, boolean changeLastU * @return The amount of chunks written to the file. * @throws IOException If something goes wrong during serialization. * */ - public static int writeMCAFile(MCAFile mcaFile, File file, boolean changeLastUpdate) throws IOException { + public static int write(MCAFile mcaFile, File file, boolean changeLastUpdate) throws IOException { File to = file; if (file.exists()) { to = File.createTempFile(to.getName(), null); diff --git a/src/test/java/net/querz/mca/MCAFileTest.java b/src/test/java/net/querz/mca/MCAFileTest.java index b9a0b870..0a7c3b71 100644 --- a/src/test/java/net/querz/mca/MCAFileTest.java +++ b/src/test/java/net/querz/mca/MCAFileTest.java @@ -28,14 +28,14 @@ public void testGetChunkIndex() { } public void testChangeData() { - MCAFile mcaFile = assertThrowsNoException(() -> MCAUtil.readMCAFile(copyResourceToTmp("r.2.2.mca"))); + MCAFile mcaFile = assertThrowsNoException(() -> MCAUtil.read(copyResourceToTmp("r.2.2.mca"))); assertNotNull(mcaFile); mcaFile.setChunk(0, null); File tmpFile = getNewTmpFile("r.2.2.mca"); - Integer x = assertThrowsNoException(() -> MCAUtil.writeMCAFile(mcaFile, tmpFile, true)); + Integer x = assertThrowsNoException(() -> MCAUtil.write(mcaFile, tmpFile, true)); assertNotNull(x); assertEquals(2, x.intValue()); - MCAFile again = assertThrowsNoException(() -> MCAUtil.readMCAFile(tmpFile)); + MCAFile again = assertThrowsNoException(() -> MCAUtil.read(tmpFile)); assertNotNull(again); for (int i = 0; i < 1024; i++) { if (i != 512 && i != 1023) { @@ -47,11 +47,11 @@ public void testChangeData() { } public void testChangeLastUpdate() { - MCAFile from = assertThrowsNoException(() -> MCAUtil.readMCAFile(copyResourceToTmp("r.2.2.mca"))); + MCAFile from = assertThrowsNoException(() -> MCAUtil.read(copyResourceToTmp("r.2.2.mca"))); assertNotNull(from); File tmpFile = getNewTmpFile("r.2.2.mca"); - assertThrowsNoException(() -> MCAUtil.writeMCAFile(from, tmpFile, true)); - MCAFile to = assertThrowsNoException(() -> MCAUtil.readMCAFile(tmpFile)); + assertThrowsNoException(() -> MCAUtil.write(from, tmpFile, true)); + MCAFile to = assertThrowsNoException(() -> MCAUtil.read(tmpFile)); assertNotNull(to); assertFalse(from.getChunk(0).getLastMCAUpdate() == to.getChunk(0).getLastMCAUpdate()); assertFalse(from.getChunk(512).getLastMCAUpdate() == to.getChunk(512).getLastMCAUpdate()); @@ -61,7 +61,7 @@ public void testChangeLastUpdate() { } public void testGetters() { - MCAFile f = assertThrowsNoException(() -> MCAUtil.readMCAFile(copyResourceToTmp("r.2.2.mca"))); + MCAFile f = assertThrowsNoException(() -> MCAUtil.read(copyResourceToTmp("r.2.2.mca"))); assertNotNull(f); assertThrowsRuntimeException(() -> f.getChunk(-1), IndexOutOfBoundsException.class); @@ -180,7 +180,7 @@ public void testSetters() { } public void testGetBiomeAt() { - MCAFile f = assertThrowsNoException(() -> MCAUtil.readMCAFile(copyResourceToTmp("r.2.2.mca"))); + MCAFile f = assertThrowsNoException(() -> MCAUtil.read(copyResourceToTmp("r.2.2.mca"))); assertEquals(21, f.getBiomeAt(1024, 1024)); assertEquals(-1, f.getBiomeAt(1040, 1024)); f.setChunk(0, 1, Chunk.newChunk()); @@ -189,7 +189,7 @@ public void testGetBiomeAt() { } public void testSetBiomeAt() { - MCAFile f = assertThrowsNoException(() -> MCAUtil.readMCAFile(copyResourceToTmp("r.2.2.mca")), true); + MCAFile f = assertThrowsNoException(() -> MCAUtil.read(copyResourceToTmp("r.2.2.mca")), true); f.setBiomeAt(1024, 1024, 20); assertEquals(20, f.getChunk(64, 64).updateHandle(64, 64).getCompoundTag("Level").getIntArray("Biomes")[0]); f.setBiomeAt(1039, 1039, 47); @@ -203,7 +203,7 @@ public void testSetBiomeAt() { } public void testCleanupPaletteAndBlockStates() { - MCAFile f = assertThrowsNoException(() -> MCAUtil.readMCAFile(copyResourceToTmp("r.2.2.mca"))); + MCAFile f = assertThrowsNoException(() -> MCAUtil.read(copyResourceToTmp("r.2.2.mca"))); assertThrowsNoRuntimeException(f::cleanupPalettesAndBlockStates); Chunk c = f.getChunk(0, 0); Section s = c.getSection(0); @@ -233,7 +233,7 @@ public void testCleanupPaletteAndBlockStates() { } public void testSetBlockDataAt() { - MCAFile f = assertThrowsNoException(() -> MCAUtil.readMCAFile(copyResourceToTmp("r.2.2.mca"))); + MCAFile f = assertThrowsNoException(() -> MCAUtil.read(copyResourceToTmp("r.2.2.mca"))); Section section = f.getChunk(0, 0).getSection(0); assertEquals(10, section.getPalette().size()); assertEquals(0b0001000100010001000100010001000100010001000100010001000100010001L, section.getBlockStates()[0]); @@ -299,7 +299,7 @@ public void testSetBlockDataAt() { } public void testGetBlockDataAt() { - MCAFile f = assertThrowsNoException(() -> MCAUtil.readMCAFile(copyResourceToTmp("r.2.2.mca"))); + MCAFile f = assertThrowsNoException(() -> MCAUtil.read(copyResourceToTmp("r.2.2.mca"))); assertEquals(block("minecraft:bedrock"), f.getBlockStateAt(0, 0, 0)); assertNull(f.getBlockStateAt(16, 0, 0)); assertEquals(block("minecraft:dirt"), f.getBlockStateAt(0, 62, 0)); @@ -308,12 +308,12 @@ public void testGetBlockDataAt() { } public void testGetChunkStatus() { - MCAFile f = assertThrowsNoException(() -> MCAUtil.readMCAFile(copyResourceToTmp("r.2.2.mca"))); + MCAFile f = assertThrowsNoException(() -> MCAUtil.read(copyResourceToTmp("r.2.2.mca"))); assertEquals("mobs_spawned", f.getChunk(0, 0).getStatus()); } public void testSetChunkStatus() { - MCAFile f = assertThrowsNoException(() -> MCAUtil.readMCAFile(copyResourceToTmp("r.2.2.mca"))); + MCAFile f = assertThrowsNoException(() -> MCAUtil.read(copyResourceToTmp("r.2.2.mca"))); assertThrowsNoRuntimeException(() -> f.getChunk(0, 0).setStatus("base")); assertEquals("base", f.getChunk(0, 0).updateHandle(64, 64).getCompoundTag("Level").getString("Status")); assertNull(f.getChunk(1, 0)); diff --git a/src/test/java/net/querz/mca/MCAUtilTest.java b/src/test/java/net/querz/mca/MCAUtilTest.java index 05179d8f..a63dde4e 100644 --- a/src/test/java/net/querz/mca/MCAUtilTest.java +++ b/src/test/java/net/querz/mca/MCAUtilTest.java @@ -75,17 +75,17 @@ public void testCreateNameFromLocation() { } public void testMakeMyCoverageGreatAgain() { - assertThrowsException(() -> MCAUtil.readMCAFile((String) null), NullPointerException.class); - assertThrowsException(() -> MCAUtil.writeMCAFile(null, (String) null), NullPointerException.class); - assertThrowsException(() -> MCAUtil.writeMCAFile(null, (File) null), NullPointerException.class); - assertThrowsException(() -> MCAUtil.writeMCAFile(null, (String) null, false), NullPointerException.class); - assertThrowsException(() -> MCAUtil.readMCAFile("r.a.b.mca"), IllegalArgumentException.class); + assertThrowsException(() -> MCAUtil.read((String) null), NullPointerException.class); + assertThrowsException(() -> MCAUtil.write(null, (String) null), NullPointerException.class); + assertThrowsException(() -> MCAUtil.write(null, (File) null), NullPointerException.class); + assertThrowsException(() -> MCAUtil.write(null, (String) null, false), NullPointerException.class); + assertThrowsException(() -> MCAUtil.read("r.a.b.mca"), IllegalArgumentException.class); assertThrowsNoException(() -> new MCAFile(0, 0).serialize(null)); // empty MCAFile will not even attempt to write to file // test overwriting file MCAFile m = new MCAFile(0, 0); m.setChunk(0, Chunk.newChunk()); - assertThrowsNoException(() -> MCAUtil.writeMCAFile(m, getTmpFile("r.0.0.mca"), false), true); - assertThrowsNoException(() -> MCAUtil.writeMCAFile(m, getTmpFile("r.0.0.mca"), false), true); + assertThrowsNoException(() -> MCAUtil.write(m, getTmpFile("r.0.0.mca"), false), true); + assertThrowsNoException(() -> MCAUtil.write(m, getTmpFile("r.0.0.mca"), false), true); } } From 6a27b448f14f74c854d3f254c42f07c3c6d5fe56 Mon Sep 17 00:00:00 2001 From: Querz Date: Mon, 27 Apr 2020 11:02:14 +0200 Subject: [PATCH 11/51] reintroduce fixes from 4.1 / fix wrong increase of block states array for 20w17a / fix setBiomes method throwing an exception for biome array of length 1024 --- src/main/java/net/querz/mca/Chunk.java | 196 ++++++++++++++++++- src/main/java/net/querz/mca/MCAFile.java | 40 ++++ src/main/java/net/querz/mca/Section.java | 90 ++++++++- src/main/java/net/querz/nbt/tag/ListTag.java | 7 +- 4 files changed, 320 insertions(+), 13 deletions(-) diff --git a/src/main/java/net/querz/mca/Chunk.java b/src/main/java/net/querz/mca/Chunk.java index 1d22fb04..1ed9ca1e 100644 --- a/src/main/java/net/querz/mca/Chunk.java +++ b/src/main/java/net/querz/mca/Chunk.java @@ -43,6 +43,10 @@ public class Chunk { this.lastMCAUpdate = lastMCAUpdate; } + /** + * Create a new chunk based on raw base data from a region file. + * @param data The raw base data to be used. + */ public Chunk(CompoundTag data) { this.data = data; initReferences(); @@ -74,11 +78,28 @@ private void initReferences() { this.structures = level.getCompoundTag("Structures"); if (level.containsKey("Sections")) { for (CompoundTag section : level.getListTag("Sections").asCompoundTagList()) { - this.sections[section.getByte("Y")] = new Section(section, dataVersion); + int sectionIndex = section.getByte("Y"); + if (sectionIndex > 15 || sectionIndex < 0) { + continue; + } + Section newSection = new Section(section, dataVersion); + if (newSection.isEmpty()) { + continue; + } + + this.sections[sectionIndex] = newSection; } } } + /** + * Serializes this chunk to a RandomAccessFile. + * @param raf The RandomAccessFile to be written to. + * @param xPos The x-coordinate of the chunk. + * @param zPos The z-coodrinate of the chunk. + * @return The amount of bytes written to the RandomAccessFile. + * @throws IOException When something went wrong during writing. + */ public int serialize(RandomAccessFile raf, int xPos, int zPos) throws IOException { ByteArrayOutputStream baos = new ByteArrayOutputStream(4096); try (BufferedOutputStream nbtOut = new BufferedOutputStream(CompressionType.ZLIB.compress(baos))) { @@ -91,6 +112,11 @@ public int serialize(RandomAccessFile raf, int xPos, int zPos) throws IOExceptio return rawData.length + 5; } + /** + * Reads chunk data from a RandomAccessFile. The RandomAccessFile must already be at the correct position. + * @param raf The RandomAccessFile to read the chunk data from. + * @throws IOException When something went wrong during reading. + */ public void deserialize(RandomAccessFile raf) throws IOException { byte compressionTypeByte = raf.readByte(); CompressionType compressionType = CompressionType.getFromID(compressionTypeByte); @@ -123,11 +149,12 @@ public int getBiomeAt(int blockX, int blockZ) { } /** - * - * @param blockX The x-location of the block - * @param blockY The y-location of the block - * @param blockZ The z-location of the block - * @return The biome ID + * Fetches a biome id at a specific block in this chunk. + * The coordinates can be absolute coordinates or relative to the region or chunk. + * @param blockX The x-coordinate of the block. + * @param blockY The y-coordinate of the block. + * @param blockZ The z-coordinate of the block. + * @return The biome id or -1 if the biomes are not correctly initialized. */ public int getBiomeAt(int blockX, int blockY, int blockZ) { if (dataVersion < 2202) { @@ -170,6 +197,14 @@ public void setBiomeAt(int blockX, int blockZ, int biomeID) { } } + /** + * Sets a biome id at a specific block column. + * The coordinates can be absolute coordinates or relative to the region or chunk. + * @param blockX The x-coordinate of the block column. + * @param blockZ The z-coordinate of the block column. + * @param biomeID The biome id to be set. + * When set to a negative number, Minecraft will replace it with the block column's default biome. + */ public void setBiomeAt(int blockX, int blockY, int blockZ, int biomeID) { if (dataVersion < 2202) { if (biomes == null || biomes.length != 256) { @@ -202,6 +237,17 @@ public CompoundTag getBlockStateAt(int blockX, int blockY, int blockZ) { return section.getBlockStateAt(blockX, blockY, blockZ); } + /** + * Sets a block state at a specific location. + * The block coordinates can be absolute or relative to the region or chunk. + * @param blockX The x-coordinate of the block. + * @param blockY The y-coordinate of the block. + * @param blockZ The z-coordinate of the block. + * @param state The block state to be set. + * @param cleanup When true, it will cleanup all palettes of this chunk. + * This option should only be used moderately to avoid unnecessary recalculation of the palette indices. + * Recalculating the Palette should only be executed once right before saving the Chunk to file. + */ public void setBlockStateAt(int blockX, int blockY, int blockZ, CompoundTag state, boolean cleanup) { int sectionIndex = MCAUtil.blockToChunk(blockY); Section section = sections[sectionIndex]; @@ -211,149 +257,283 @@ public void setBlockStateAt(int blockX, int blockY, int blockZ, CompoundTag stat section.setBlockStateAt(blockX, blockY, blockZ, state, cleanup); } + /** + * @return The DataVersion of this chunk. + */ public int getDataVersion() { return dataVersion; } + /** + * Sets the DataVersion of this chunk. This does not check if the data of this chunk conforms + * to that DataVersion, that is the responsibility of the developer. + * @param dataVersion The DataVersion to be set. + */ public void setDataVersion(int dataVersion) { this.dataVersion = dataVersion; } + /** + * @return The timestamp when this region file was last updated in seconds since 1970-01-01. + */ public int getLastMCAUpdate() { return lastMCAUpdate; } + /** + * Sets the timestamp when this region file was last updated in seconds since 1970-01-01. + * @param lastMCAUpdate The time in seconds since 1970-01-01. + */ public void setLastMCAUpdate(int lastMCAUpdate) { this.lastMCAUpdate = lastMCAUpdate; } + /** + * @return The generation station of this chunk. + */ public String getStatus() { return status; } + /** + * Sets the generation status of this chunk. + * @param status The generation status of this chunk. + */ public void setStatus(String status) { this.status = status; } + /** + * Fetches the section at the given y-coordinate. + * @param sectionY The y-coordinate of the section in this chunk ranging from 0 to 15. + * @return The Section. + */ public Section getSection(int sectionY) { return sections[sectionY]; } + /** + * Sets a section at a givesn y-coordinate + * @param sectionY The y-coordinate of the section in this chunk ranging from 0 to 15. + * @param section The section to be set. + */ public void setSection(int sectionY, Section section) { sections[sectionY] = section; } + /** + * @return The timestamp when this chunk was last updated as a UNIX timestamp. + */ public long getLastUpdate() { return lastUpdate; } + /** + * Sets the time when this chunk was last updated as a UNIX timestamp. + * @param lastUpdate The UNIX timestamp. + */ public void setLastUpdate(long lastUpdate) { this.lastUpdate = lastUpdate; } + /** + * @return The cumulative amount of time players have spent in this chunk in ticks. + */ public long getInhabitedTime() { return inhabitedTime; } + /** + * Sets the cumulative amount of time players have spent in this chunk in ticks. + * @param inhabitedTime The time in ticks. + */ public void setInhabitedTime(long inhabitedTime) { this.inhabitedTime = inhabitedTime; } + /** + * @return A matrix of biome IDs for all block columns in this chunk. + */ public int[] getBiomes() { return biomes; } + /** + * Sets the biome IDs for this chunk. + * @param biomes The biome ID matrix of this chunk. Must have a length of 256. + * @throws IllegalArgumentException When the biome matrix does not have a length of 256 + * or is null + */ public void setBiomes(int[] biomes) { - if (biomes != null && biomes.length != 256) { - throw new IllegalArgumentException("biomes array must have a length of 256"); + if (biomes != null) { + if (dataVersion < 2202 && biomes.length != 256 || dataVersion >= 2202 && biomes.length != 1024) { + throw new IllegalArgumentException("biomes array must have a length of " + (dataVersion < 2202 ? "256" : "1024")); + } } this.biomes = biomes; } + /** + * @return The height maps of this chunk. + */ public CompoundTag getHeightMaps() { return heightMaps; } + /** + * Sets the height maps of this chunk. + * @param heightMaps The height maps. + */ public void setHeightMaps(CompoundTag heightMaps) { this.heightMaps = heightMaps; } + /** + * @return The carving masks of this chunk. + */ public CompoundTag getCarvingMasks() { return carvingMasks; } + /** + * Sets the carving masks of this chunk. + * @param carvingMasks The carving masks. + */ public void setCarvingMasks(CompoundTag carvingMasks) { this.carvingMasks = carvingMasks; } + /** + * @return The entities of this chunk. + */ public ListTag getEntities() { return entities; } + /** + * Sets the entities of this chunk. + * @param entities The entities. + */ public void setEntities(ListTag entities) { this.entities = entities; } + /** + * @return The tile entities of this chunk. + */ public ListTag getTileEntities() { return tileEntities; } + /** + * Sets the tile entities of this chunk. + * @param tileEntities The tile entities of this chunk. + */ public void setTileEntities(ListTag tileEntities) { this.tileEntities = tileEntities; } + /** + * @return The tile ticks of this chunk. + */ public ListTag getTileTicks() { return tileTicks; } + /** + * Sets the tile ticks of this chunk. + * @param tileTicks Thee tile ticks. + */ public void setTileTicks(ListTag tileTicks) { this.tileTicks = tileTicks; } + /** + * @return The liquid ticks of this chunk. + */ public ListTag getLiquidTicks() { return liquidTicks; } + /** + * Sets the liquid ticks of this chunk. + * @param liquidTicks The liquid ticks. + */ public void setLiquidTicks(ListTag liquidTicks) { this.liquidTicks = liquidTicks; } + /** + * @return The light sources in this chunk. + */ public ListTag> getLights() { return lights; } + /** + * Sets the light sources in this chunk. + * @param lights The light sources. + */ public void setLights(ListTag> lights) { this.lights = lights; } + /** + * @return THe liquids to be ticked in this chunk. + */ public ListTag> getLiquidsToBeTicked() { return liquidsToBeTicked; } + /** + * Sets the liquids to be ticked in this chunk. + * @param liquidsToBeTicked The liquids to be ticked. + */ public void setLiquidsToBeTicked(ListTag> liquidsToBeTicked) { this.liquidsToBeTicked = liquidsToBeTicked; } + /** + * @return Stuff to be ticked in this chunk. + */ public ListTag> getToBeTicked() { return toBeTicked; } + /** + * Sets stuff to be ticked in this chunk. + * @param toBeTicked The stuff to be ticked. + */ public void setToBeTicked(ListTag> toBeTicked) { this.toBeTicked = toBeTicked; } + /** + * @return Things that are in post processing in this chunk. + */ public ListTag> getPostProcessing() { return postProcessing; } + /** + * Sets things to be post processed in this chunk. + * @param postProcessing The things to be post processed. + */ public void setPostProcessing(ListTag> postProcessing) { this.postProcessing = postProcessing; } + /** + * @return Data about structures in this chunk. + */ public CompoundTag getStructures() { return structures; } + /** + * Sets data about structures in this chunk. + * @param structures The data about structures. + */ public void setStructures(CompoundTag structures) { this.structures = structures; } diff --git a/src/main/java/net/querz/mca/MCAFile.java b/src/main/java/net/querz/mca/MCAFile.java index 4f03cd77..f5fcf1e1 100644 --- a/src/main/java/net/querz/mca/MCAFile.java +++ b/src/main/java/net/querz/mca/MCAFile.java @@ -124,6 +124,12 @@ public int serialize(RandomAccessFile raf, boolean changeLastUpdate) throws IOEx return chunksWritten; } + /** + * Set a specific Chunk at a specific index. The index must be in range of 0 - 1023. + * @param index The index of the Chunk. + * @param chunk The Chunk to be set. + * @throws IndexOutOfBoundsException If index is not in the range. + */ public void setChunk(int index, Chunk chunk) { checkIndex(index); if (chunks == null) { @@ -132,6 +138,13 @@ public void setChunk(int index, Chunk chunk) { chunks[index] = chunk; } + /** + * Set a specific Chunk at a specific chunk location. + * The x- and z-value can be absolute chunk coordinates or they can be relative to the region origin. + * @param chunkX The x-coordinate of the Chunk. + * @param chunkZ The z-coordinate of the Chunk. + * @param chunk The chunk to be set. + */ public void setChunk(int chunkX, int chunkZ, Chunk chunk) { setChunk(getChunkIndex(chunkX, chunkZ), chunk); } @@ -212,6 +225,13 @@ public int getBiomeAt(int blockX, int blockZ) { return chunk.getBiomeAt(blockX, blockZ); } + /** + * Fetches the biome id at a specific block. + * @param blockX The x-coordinate of the block. + * @param blockY The y-coordinate of the block. + * @param blockZ The z-coordinate of the block. + * @return The biome id if the chunk exists and the chunk has biomes, otherwise -1. + */ public int getBiomeAt(int blockX, int blockY, int blockZ) { int chunkX = MCAUtil.blockToChunk(blockX), chunkZ = MCAUtil.blockToChunk(blockZ); Chunk chunk = getChunk(getChunkIndex(chunkX, chunkZ)); @@ -221,10 +241,27 @@ public int getBiomeAt(int blockX, int blockY, int blockZ) { return chunk.getBiomeAt(blockX,blockY, blockZ); } + /** + * Set a block state at a specific block location. + * The block coordinates can be absolute coordinates or they can be relative to the region. + * @param blockX The x-coordinate of the block. + * @param blockY The y-coordinate of the block. + * @param blockZ The z-coordinate of the block. + * @param state The block state to be set. + * @param cleanup Whether the Palette and the BLockStates should be recalculated after adding the block state. + */ public void setBlockStateAt(int blockX, int blockY, int blockZ, CompoundTag state, boolean cleanup) { createChunkIfMissing(blockX, blockZ).setBlockStateAt(blockX, blockY, blockZ, state, cleanup); } + /** + * Fetches a block state at a specific block location. + * The block coordinates can be absolute coordinates or they can be relative to the region. + * @param blockX The x-coordinate of the block. + * @param blockY The y-coordinate of the block. + * @param blockZ The z-coordinate of the block. + * @return The block state or null if the chunk or the section do not exist. + */ public CompoundTag getBlockStateAt(int blockX, int blockY, int blockZ) { int chunkX = MCAUtil.blockToChunk(blockX), chunkZ = MCAUtil.blockToChunk(blockZ); Chunk chunk = getChunk(chunkX, chunkZ); @@ -234,6 +271,9 @@ public CompoundTag getBlockStateAt(int blockX, int blockY, int blockZ) { return chunk.getBlockStateAt(blockX, blockY, blockZ); } + /** + * Recalculates the Palette and the BlockStates of all chunks and sections of this region. + */ public void cleanupPalettesAndBlockStates() { for (Chunk chunk : chunks) { if (chunk != null) { diff --git a/src/main/java/net/querz/mca/Section.java b/src/main/java/net/querz/mca/Section.java index 742ec617..a3ff7f30 100644 --- a/src/main/java/net/querz/mca/Section.java +++ b/src/main/java/net/querz/mca/Section.java @@ -20,7 +20,11 @@ public class Section { public Section(CompoundTag sectionRoot, int dataVersion) { data = sectionRoot; this.dataVersion = dataVersion; - palette = sectionRoot.getListTag("Palette").asCompoundTagList(); + ListTag rawPalette = sectionRoot.getListTag("Palette"); + if (rawPalette == null) { + return; + } + palette = rawPalette.asCompoundTagList(); for (int i = 0; i < palette.size(); i++) { CompoundTag data = palette.get(i); putValueIndexedPalette(data, i); @@ -74,12 +78,38 @@ private static class PaletteIndex { } } + /** + * Checks whether the data of this Section is empty. + * @return true if empty + */ + public boolean isEmpty() { + return data == null; + } + + /** + * Fetches a block state based on a block location from this section. + * The coordinates represent the location of the block inside of this Section. + * @param blockX The x-coordinate of the block in this Section + * @param blockY The y-coordinate of the block in this Section + * @param blockZ The z-coordinate of the block in this Section + * @return The block state data of this block. + */ public CompoundTag getBlockStateAt(int blockX, int blockY, int blockZ) { int index = getBlockIndex(blockX, blockY, blockZ); int paletteIndex = getPaletteIndex(index); return palette.get(paletteIndex); } + /** + * Attempts to add a block state for a specific block location in this Section. + * @param blockX The x-coordinate of the block in this Section + * @param blockY The y-coordinate of the block in this Section + * @param blockZ The z-coordinate of the block in this Section + * @param state The block state to be set + * @param cleanup When true, it will cleanup the palette of this section. + * This option should only be used moderately to avoid unnecessary recalculation of the palette indices. + * Recalculating the Palette should only be executed once right before saving the Section to file. + */ public void setBlockStateAt(int blockX, int blockY, int blockZ, CompoundTag state, boolean cleanup) { int paletteSizeBefore = palette.size(); int paletteIndex = addToPalette(state); @@ -152,7 +182,11 @@ public void setPaletteIndex(int blockIndex, int paletteIndex, long[] blockStates } } - ListTag getPalette() { + /** + * Fetches the palette of this Section. + * @return The palette of this Section. + */ + public ListTag getPalette() { return palette; } @@ -181,12 +215,17 @@ static long bitRange(long value, int from, int to) { return (value << waste) >>> (waste + from); } + /** + * This method recalculates the palette and its indices. + * This should only be used moderately to avoid unnecessary recalculation of the palette indices. + * Recalculating the Palette should only be executed once right before saving the Section to file. + */ public void cleanupPaletteAndBlockStates() { Map oldToNewMapping = cleanupPalette(); adjustBlockStateBits(oldToNewMapping, blockStates); } - Map cleanupPalette() { + private Map cleanupPalette() { //create index - palette mapping Map allIndices = new HashMap<>(); for (int i = 0; i < 4096; i++) { @@ -220,7 +259,14 @@ void adjustBlockStateBits(Map oldToNewMapping, long[] blockSta int newBits = 32 - Integer.numberOfLeadingZeros(palette.size() - 1); newBits = Math.max(newBits, 4); - long[] newBlockStates = newBits == blockStates.length / 64 ? blockStates : new long[newBits * 64]; + long[] newBlockStates; + + if (dataVersion < 2527) { + newBlockStates = newBits == blockStates.length / 64 ? blockStates : new long[newBits * 64]; + } else { + int newLength = (int) Math.ceil(4096D / (64D / newBits)); + newBlockStates = newBits == blockStates.length / 64 ? blockStates : new long[newLength]; + } if (oldToNewMapping != null) { for (int i = 0; i < 4096; i++) { setPaletteIndex(i, oldToNewMapping.get(getPaletteIndex(i)), newBlockStates); @@ -233,10 +279,18 @@ void adjustBlockStateBits(Map oldToNewMapping, long[] blockSta this.blockStates = newBlockStates; } + /** + * @return The block light array of this Section + */ public byte[] getBlockLight() { return blockLight; } + /** + * Sets the block light array for this section. + * @param blockLight The block light array + * @throws IllegalArgumentException When the length of the array is not 2048 + */ public void setBlockLight(byte[] blockLight) { if (blockLight != null && blockLight.length != 2048) { throw new IllegalArgumentException("BlockLight array must have a length of 2048"); @@ -244,10 +298,19 @@ public void setBlockLight(byte[] blockLight) { this.blockLight = blockLight; } + /** + * @return The indices of the block states of this Section. + */ public long[] getBlockStates() { return blockStates; } + /** + * Sets the block state indices to a custom value. + * @param blockStates The block state indices. + * @throws NullPointerException If blockStates is null + * @throws IllegalArgumentException When blockStates' length is < 256 or > 4096 and is not a multiple of 64 + */ public void setBlockStates(long[] blockStates) { if (blockStates == null) { throw new NullPointerException("BlockStates cannot be null"); @@ -257,10 +320,18 @@ public void setBlockStates(long[] blockStates) { this.blockStates = blockStates; } + /** + * @return The sky light values of this Section + */ public byte[] getSkyLight() { return skyLight; } + /** + * Sets the sky light values of this section. + * @param skyLight The custom sky light values + * @throws IllegalArgumentException If the length of the array is not 2048 + */ public void setSkyLight(byte[] skyLight) { if (skyLight != null && skyLight.length != 2048) { throw new IllegalArgumentException("SkyLight array must have a length of 2048"); @@ -268,6 +339,10 @@ public void setSkyLight(byte[] skyLight) { this.skyLight = skyLight; } + /** + * Creates an empty Section with base values. + * @return An empty Section + */ public static Section newSection() { Section s = new Section(); s.blockStates = new long[256]; @@ -279,6 +354,13 @@ public static Section newSection() { return s; } + /** + * Updates the raw CompoundTag that this Section is based on. + * This must be called before saving a Section to disk if the Section was manually created + * to set the Y of this Section. + * @param y The Y-value of this Section + * @return A reference to the raw CompoundTag this Section is based on + */ public CompoundTag updateHandle(int y) { data.putByte("Y", (byte) y); data.put("Palette", palette); diff --git a/src/main/java/net/querz/nbt/tag/ListTag.java b/src/main/java/net/querz/nbt/tag/ListTag.java index b1fd3ade..008e3aaa 100644 --- a/src/main/java/net/querz/nbt/tag/ListTag.java +++ b/src/main/java/net/querz/nbt/tag/ListTag.java @@ -123,10 +123,15 @@ public void add(T t) { public void add(int index, T t) { Objects.requireNonNull(t); - getValue().add(index, t); if (typeClass == null || typeClass == EndTag.class) { typeClass = t.getClass(); + } else if (typeClass != t.getClass()) { + throw new ClassCastException( + String.format("cannot add %s to ListTag<%s>", + t.getClass().getSimpleName(), + typeClass.getSimpleName())); } + getValue().add(index, t); } public void addAll(Collection t) { From 9db0d64697e46c8b16bf0160f74a0c312d18360a Mon Sep 17 00:00:00 2001 From: Querz Date: Mon, 27 Apr 2020 11:03:19 +0200 Subject: [PATCH 12/51] update version to 5.1 --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 7babd463..069e7fe9 100644 --- a/build.gradle +++ b/build.gradle @@ -9,7 +9,7 @@ apply plugin: 'jacoco' group = 'net.querz.nbt' archivesBaseName = 'nbt' -version = '5.0' +version = '5.1' sourceCompatibility = '1.8' targetCompatibility = '1.8' compileJava.options.encoding = 'UTF-8' From 09ff4224184c414e827e6bf265967cde95454d83 Mon Sep 17 00:00:00 2001 From: Querz Date: Mon, 27 Apr 2020 12:41:04 +0200 Subject: [PATCH 13/51] fix biome index calculation --- src/main/java/net/querz/mca/Chunk.java | 2 +- src/test/java/net/querz/mca/MCAFileTest.java | 16 ++++++++++++++++ src/test/resources/r.0.0.mca | Bin 0 -> 16384 bytes 3 files changed, 17 insertions(+), 1 deletion(-) create mode 100644 src/test/resources/r.0.0.mca diff --git a/src/main/java/net/querz/mca/Chunk.java b/src/main/java/net/querz/mca/Chunk.java index 1ed9ca1e..d492fbad 100644 --- a/src/main/java/net/querz/mca/Chunk.java +++ b/src/main/java/net/querz/mca/Chunk.java @@ -226,7 +226,7 @@ public void setBiomeAt(int blockX, int blockY, int blockZ, int biomeID) { } int getBiomeIndex(int biomeX, int biomeY, int biomeZ) { - return biomeY * 64 + biomeX * 4 + biomeZ; + return biomeY * 64 + biomeZ * 4 + biomeX; } public CompoundTag getBlockStateAt(int blockX, int blockY, int blockZ) { diff --git a/src/test/java/net/querz/mca/MCAFileTest.java b/src/test/java/net/querz/mca/MCAFileTest.java index 0a7c3b71..794489bf 100644 --- a/src/test/java/net/querz/mca/MCAFileTest.java +++ b/src/test/java/net/querz/mca/MCAFileTest.java @@ -342,4 +342,20 @@ public void testChunkInvalidDataTag() { } }, IOException.class); } + + public void test1_15GetBiomeAt() throws IOException { + MCAFile f = assertThrowsNoException(() -> MCAUtil.read(copyResourceToTmp("r.0.0.mca"))); + assertEquals(162, f.getBiomeAt(31, 0, 63)); + assertEquals(4, f.getBiomeAt(16, 0, 48)); + assertEquals(4, f.getBiomeAt(16, 0, 63)); + assertEquals(162, f.getBiomeAt(31, 0, 48)); + assertEquals(162, f.getBiomeAt(31, 100, 63)); + assertEquals(4, f.getBiomeAt(16, 100, 48)); + assertEquals(4, f.getBiomeAt(16, 100, 63)); + assertEquals(162, f.getBiomeAt(31, 100, 48)); + assertEquals(162, f.getBiomeAt(31, 106, 63)); + assertEquals(4, f.getBiomeAt(16, 106, 48)); + assertEquals(4, f.getBiomeAt(16, 106, 63)); + assertEquals(162, f.getBiomeAt(31, 106, 48)); + } } diff --git a/src/test/resources/r.0.0.mca b/src/test/resources/r.0.0.mca new file mode 100644 index 0000000000000000000000000000000000000000..4514672910dc70916e34ad76f4d3215c63823d7f GIT binary patch literal 16384 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 Date: Mon, 27 Apr 2020 12:49:18 +0200 Subject: [PATCH 14/51] update version to 5.2 --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 069e7fe9..50e9e143 100644 --- a/build.gradle +++ b/build.gradle @@ -9,7 +9,7 @@ apply plugin: 'jacoco' group = 'net.querz.nbt' archivesBaseName = 'nbt' -version = '5.1' +version = '5.2' sourceCompatibility = '1.8' targetCompatibility = '1.8' compileJava.options.encoding = 'UTF-8' From eaa17146d79c5529ecf4adee2963d8fc51ae91c4 Mon Sep 17 00:00:00 2001 From: Querz Date: Wed, 29 Apr 2020 14:00:29 +0200 Subject: [PATCH 15/51] fix some old bugs --- src/main/java/net/querz/mca/Chunk.java | 2 +- src/main/java/net/querz/mca/MCAFile.java | 2 +- src/main/java/net/querz/mca/Section.java | 14 +++++++++++--- 3 files changed, 13 insertions(+), 5 deletions(-) diff --git a/src/main/java/net/querz/mca/Chunk.java b/src/main/java/net/querz/mca/Chunk.java index d492fbad..ef5c47a5 100644 --- a/src/main/java/net/querz/mca/Chunk.java +++ b/src/main/java/net/querz/mca/Chunk.java @@ -106,7 +106,7 @@ public int serialize(RandomAccessFile raf, int xPos, int zPos) throws IOExceptio new NBTSerializer(false).toStream(new NamedTag(null, updateHandle(xPos, zPos)), nbtOut); } byte[] rawData = baos.toByteArray(); - raf.writeInt(rawData.length); + raf.writeInt(rawData.length + 1); // including the byte to store the compression type raf.writeByte(CompressionType.ZLIB.getID()); raf.write(rawData); return rawData.length + 5; diff --git a/src/main/java/net/querz/mca/MCAFile.java b/src/main/java/net/querz/mca/MCAFile.java index f5fcf1e1..c68c5123 100644 --- a/src/main/java/net/querz/mca/MCAFile.java +++ b/src/main/java/net/querz/mca/MCAFile.java @@ -100,7 +100,7 @@ public int serialize(RandomAccessFile raf, boolean changeLastUpdate) throws IOEx chunksWritten++; - int sectors = (lastWritten >> 12) + 1; + int sectors = (lastWritten >> 12) + (lastWritten % 4096 == 0 ? 0 : 1); raf.seek(index * 4); raf.writeByte(globalOffset >>> 16); diff --git a/src/main/java/net/querz/mca/Section.java b/src/main/java/net/querz/mca/Section.java index a3ff7f30..1e2b1f11 100644 --- a/src/main/java/net/querz/mca/Section.java +++ b/src/main/java/net/querz/mca/Section.java @@ -1,7 +1,10 @@ package net.querz.mca; +import net.querz.nbt.tag.ByteArrayTag; import net.querz.nbt.tag.CompoundTag; import net.querz.nbt.tag.ListTag; +import net.querz.nbt.tag.LongArrayTag; + import java.util.ArrayList; import java.util.HashMap; import java.util.List; @@ -29,9 +32,14 @@ public Section(CompoundTag sectionRoot, int dataVersion) { CompoundTag data = palette.get(i); putValueIndexedPalette(data, i); } - blockLight = sectionRoot.getByteArray("BlockLight"); - blockStates = sectionRoot.getLongArray("BlockStates"); - skyLight = sectionRoot.getByteArray("SkyLight"); + + ByteArrayTag blockLight = sectionRoot.getByteArrayTag("BlockLight"); + LongArrayTag blockStates = sectionRoot.getLongArrayTag("BlockStates"); + ByteArrayTag skyLight = sectionRoot.getByteArrayTag("SkyLight"); + + this.blockLight = blockLight != null ? blockLight.getValue() : null; + this.blockStates = blockStates != null ? blockStates.getValue() : null; + this.skyLight = skyLight != null ? skyLight.getValue() : null; } Section() {} From 4b6301dd5a2a69ab35313b2a8cdd2cfba87afc99 Mon Sep 17 00:00:00 2001 From: Querz Date: Wed, 29 Apr 2020 13:37:25 +0200 Subject: [PATCH 16/51] add information how to integrate NBT using gradle or maven --- README.md | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/README.md b/README.md index 5c7aad69..e769b6dc 100644 --- a/README.md +++ b/README.md @@ -25,6 +25,41 @@ According to the [specification](https://minecraft.gamepedia.com/NBT_format), th * The maximum depth of the NBT structure is 512. If the depth exceeds this restriction during serialization, deserialization or String conversion, a `MaxDepthReachedException` is thrown. This usually happens when a circular reference exists in the NBT structure. The NBT specification does not allow circular references, as there is no tag to represent this. +### Add the library as a dependency using Gradle: +Add Jitpack to your `repositories`: +``` +repositories { + ... + maven { url 'https://jitpack.io/' } +} +``` +And then add it as a dependency as usual: +``` +dependencies { + ... + compile 'com.github.Querz:NBT:5.2' +} +``` + +### Add the library as a dependency using Maven: +Add Jitpack: +``` + + + jitpack.io + https://jitpack.io + + +``` +Dependency: +``` + + com.github.Querz + NBT + Tag + +``` + --- ### Example usage: The following code snippet shows how to create a `CompoundTag`: From 38173f6f95d7b1ae7bc644d105169e801a964ea4 Mon Sep 17 00:00:00 2001 From: Querz Date: Mon, 4 May 2020 22:56:33 +0200 Subject: [PATCH 17/51] make newMCAFile public / check if palette is null --- src/main/java/net/querz/mca/MCAUtil.java | 2 +- src/main/java/net/querz/mca/Section.java | 16 ++++++++++++---- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/src/main/java/net/querz/mca/MCAUtil.java b/src/main/java/net/querz/mca/MCAUtil.java index 392773ba..b12304a2 100644 --- a/src/main/java/net/querz/mca/MCAUtil.java +++ b/src/main/java/net/querz/mca/MCAUtil.java @@ -191,7 +191,7 @@ public static int chunkToBlock(int chunk) { private static final Pattern mcaFilePattern = Pattern.compile("^.*r\\.(?-?\\d+)\\.(?-?\\d+)\\.mca$"); - private static MCAFile newMCAFile(File file) { + public static MCAFile newMCAFile(File file) { Matcher m = mcaFilePattern.matcher(file.getName()); if (m.find()) { return new MCAFile(Integer.parseInt(m.group("regionX")), Integer.parseInt(m.group("regionZ"))); diff --git a/src/main/java/net/querz/mca/Section.java b/src/main/java/net/querz/mca/Section.java index 1e2b1f11..8958b649 100644 --- a/src/main/java/net/querz/mca/Section.java +++ b/src/main/java/net/querz/mca/Section.java @@ -371,10 +371,18 @@ public static Section newSection() { */ public CompoundTag updateHandle(int y) { data.putByte("Y", (byte) y); - data.put("Palette", palette); - if (blockLight != null) data.putByteArray("BlockLight", blockLight); - data.putLongArray("BlockStates", blockStates); - if (skyLight != null) data.putByteArray("SkyLight", skyLight); + if (palette != null) { + data.put("Palette", palette); + } + if (blockLight != null) { + data.putByteArray("BlockLight", blockLight); + } + if (blockStates != null) { + data.putLongArray("BlockStates", blockStates); + } + if (skyLight != null) { + data.putByteArray("SkyLight", skyLight); + } return data; } } From ad2205e6bbade5573f0e1618ce45d15d7852d84c Mon Sep 17 00:00:00 2001 From: Querz Date: Sat, 9 May 2020 17:30:24 +0200 Subject: [PATCH 18/51] Update build.gradle --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 50e9e143..15664b54 100644 --- a/build.gradle +++ b/build.gradle @@ -9,7 +9,7 @@ apply plugin: 'jacoco' group = 'net.querz.nbt' archivesBaseName = 'nbt' -version = '5.2' +version = '5.3' sourceCompatibility = '1.8' targetCompatibility = '1.8' compileJava.options.encoding = 'UTF-8' From d0ae2077a040801169348cff258c85c1cb3eeba9 Mon Sep 17 00:00:00 2001 From: Querz Date: Mon, 25 May 2020 10:45:07 +0200 Subject: [PATCH 19/51] sync flush for GZIPOutputStream --- src/main/java/net/querz/nbt/io/NBTSerializer.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/net/querz/nbt/io/NBTSerializer.java b/src/main/java/net/querz/nbt/io/NBTSerializer.java index 1328ec77..fe505992 100644 --- a/src/main/java/net/querz/nbt/io/NBTSerializer.java +++ b/src/main/java/net/querz/nbt/io/NBTSerializer.java @@ -22,10 +22,11 @@ public NBTSerializer(boolean compressed) { public void toStream(NamedTag object, OutputStream out) throws IOException { NBTOutputStream nbtOut; if (compressed) { - nbtOut = new NBTOutputStream(new GZIPOutputStream(out)); + nbtOut = new NBTOutputStream(new GZIPOutputStream(out, true)); } else { nbtOut = new NBTOutputStream(out); } nbtOut.writeTag(object, Tag.DEFAULT_MAX_DEPTH); + nbtOut.flush(); } } From f77e2871628ed623ec3143826920cb2fc6ff047b Mon Sep 17 00:00:00 2001 From: Querz Date: Mon, 25 May 2020 10:48:32 +0200 Subject: [PATCH 20/51] update version to 5.4 --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 15664b54..3c42734b 100644 --- a/build.gradle +++ b/build.gradle @@ -9,7 +9,7 @@ apply plugin: 'jacoco' group = 'net.querz.nbt' archivesBaseName = 'nbt' -version = '5.3' +version = '5.4' sourceCompatibility = '1.8' targetCompatibility = '1.8' compileJava.options.encoding = 'UTF-8' From f693559722e9f05c1de7c8e6420b8c51e4121cc5 Mon Sep 17 00:00:00 2001 From: prydin Date: Thu, 28 May 2020 12:42:10 -0400 Subject: [PATCH 21/51] Fixed Heightmap tag typo and added maven plugin --- build.gradle | 1 + src/main/java/net/querz/mca/Chunk.java | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 3c42734b..fe225ecc 100644 --- a/build.gradle +++ b/build.gradle @@ -1,5 +1,6 @@ plugins { id 'com.github.kt3k.coveralls' version '2.4.0' + id 'maven' } apply plugin: 'java' diff --git a/src/main/java/net/querz/mca/Chunk.java b/src/main/java/net/querz/mca/Chunk.java index ef5c47a5..6a9873a5 100644 --- a/src/main/java/net/querz/mca/Chunk.java +++ b/src/main/java/net/querz/mca/Chunk.java @@ -64,7 +64,7 @@ private void initReferences() { this.inhabitedTime = level.getLong("InhabitedTime"); this.lastUpdate = level.getLong("LastUpdate"); this.biomes = level.getIntArray("Biomes"); - this.heightMaps = level.getCompoundTag("HeightMaps"); + this.heightMaps = level.getCompoundTag("Heightmaps"); this.carvingMasks = level.getCompoundTag("CarvingMasks"); this.entities = level.containsKey("Entities") ? level.getListTag("Entities").asCompoundTagList() : null; this.tileEntities = level.containsKey("TileEntities") ? level.getListTag("TileEntities").asCompoundTagList() : null; From 173115e41346c124672bb8d8af4d452968cef232 Mon Sep 17 00:00:00 2001 From: prydin Date: Tue, 2 Jun 2020 09:12:47 -0400 Subject: [PATCH 22/51] Performance improvements --- .../java/net/querz/nbt/io/NBTInputStream.java | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/src/main/java/net/querz/nbt/io/NBTInputStream.java b/src/main/java/net/querz/nbt/io/NBTInputStream.java index 05ede87e..b3ca6b8a 100644 --- a/src/main/java/net/querz/nbt/io/NBTInputStream.java +++ b/src/main/java/net/querz/nbt/io/NBTInputStream.java @@ -105,17 +105,21 @@ private static ByteArrayTag readByteArray(NBTInputStream in) throws IOException } private static IntArrayTag readIntArray(NBTInputStream in) throws IOException { - IntArrayTag iat = new IntArrayTag(new int[in.readInt()]); - for (int i = 0; i < iat.length(); i++) { - iat.getValue()[i] = in.readInt(); + int l = in.readInt(); + int[] data = new int[l]; + IntArrayTag iat = new IntArrayTag(data); + for (int i = 0; i < l; i++) { + data[i] = in.readInt(); } return iat; } private static LongArrayTag readLongArray(NBTInputStream in) throws IOException { - LongArrayTag iat = new LongArrayTag(new long[in.readInt()]); - for (int i = 0; i < iat.length(); i++) { - iat.getValue()[i] = in.readLong(); + int l = in.readInt(); + long[] data = new long[l]; + LongArrayTag iat = new LongArrayTag(data); + for (int i = 0; i < l; i++) { + data[i] = in.readLong(); } return iat; } From 6653bd395341fc62cd526bd19b671a6085fad996 Mon Sep 17 00:00:00 2001 From: prydin Date: Tue, 2 Jun 2020 15:51:24 -0400 Subject: [PATCH 23/51] Added support for partial chunk/region load --- src/main/java/net/querz/mca/Chunk.java | 67 +++++++++++++++++----- src/main/java/net/querz/mca/LoadFlags.java | 24 ++++++++ src/main/java/net/querz/mca/MCAFile.java | 13 ++++- src/main/java/net/querz/mca/Section.java | 18 +++++- 4 files changed, 103 insertions(+), 19 deletions(-) create mode 100644 src/main/java/net/querz/mca/LoadFlags.java diff --git a/src/main/java/net/querz/mca/Chunk.java b/src/main/java/net/querz/mca/Chunk.java index 6a9873a5..c0691ab9 100644 --- a/src/main/java/net/querz/mca/Chunk.java +++ b/src/main/java/net/querz/mca/Chunk.java @@ -12,6 +12,7 @@ import java.io.IOException; import java.io.RandomAccessFile; import java.util.Arrays; +import static net.querz.mca.LoadFlags.*; public class Chunk { @@ -49,10 +50,10 @@ public class Chunk { */ public Chunk(CompoundTag data) { this.data = data; - initReferences(); + initReferences(-1); } - private void initReferences() { + private void initReferences(long loadFlags) { if (data == null) { throw new NullPointerException("data cannot be null"); } @@ -63,17 +64,39 @@ private void initReferences() { this.dataVersion = data.getInt("DataVersion"); this.inhabitedTime = level.getLong("InhabitedTime"); this.lastUpdate = level.getLong("LastUpdate"); - this.biomes = level.getIntArray("Biomes"); - this.heightMaps = level.getCompoundTag("Heightmaps"); - this.carvingMasks = level.getCompoundTag("CarvingMasks"); - this.entities = level.containsKey("Entities") ? level.getListTag("Entities").asCompoundTagList() : null; - this.tileEntities = level.containsKey("TileEntities") ? level.getListTag("TileEntities").asCompoundTagList() : null; - this.tileTicks = level.containsKey("TileTicks") ? level.getListTag("TileTicks").asCompoundTagList() : null; - this.liquidTicks = level.containsKey("LiquidTicks") ? level.getListTag("LiquidTicks").asCompoundTagList() : null; - this.lights = level.containsKey("Lights") ? level.getListTag("Lights").asListTagList() : null; - this.liquidsToBeTicked = level.containsKey("LiquidsToBeTicked") ? level.getListTag("LiquidsToBeTicked").asListTagList() : null; - this.toBeTicked = level.containsKey("ToBeTicked") ? level.getListTag("ToBeTicked").asListTagList() : null; - this.postProcessing = level.containsKey("PostProcessing") ? level.getListTag("PostProcessing").asListTagList() : null; + if((loadFlags | BIOMES) != 0) { + this.biomes = level.getIntArray("Biomes"); + } + if((loadFlags | HEIGHTMAPS) != 0) { + this.heightMaps = level.getCompoundTag("Heightmaps"); + } + if((loadFlags | CARVING_MARKS) != 0) { + this.carvingMasks = level.getCompoundTag("CarvingMasks"); + } + if((loadFlags | ENTITIES) != 0) { + this.entities = level.containsKey("Entities") ? level.getListTag("Entities").asCompoundTagList() : null; + } + if((loadFlags | TILE_ENTITIES) != 0) { + this.tileEntities = level.containsKey("TileEntities") ? level.getListTag("TileEntities").asCompoundTagList() : null; + } + if((loadFlags | TILE_TICKS) != 0) { + this.tileTicks = level.containsKey("TileTicks") ? level.getListTag("TileTicks").asCompoundTagList() : null; + } + if((loadFlags | LIQUID_TILE_TICKS) != 0) { + this.liquidTicks = level.containsKey("LiquidTicks") ? level.getListTag("LiquidTicks").asCompoundTagList() : null; + } + if((loadFlags | LIGHTS) != 0) { + this.lights = level.containsKey("Lights") ? level.getListTag("Lights").asListTagList() : null; + } + if((loadFlags | LIQUIDS_TO_BE_TICKED) != 0) { + this.liquidsToBeTicked = level.containsKey("LiquidsToBeTicked") ? level.getListTag("LiquidsToBeTicked").asListTagList() : null; + } + if((loadFlags | TO_BE_TICKED) != 0) { + this.toBeTicked = level.containsKey("ToBeTicked") ? level.getListTag("ToBeTicked").asListTagList() : null; + } + if((loadFlags | POST_PROCESSING) != 0) { + this.postProcessing = level.containsKey("PostProcessing") ? level.getListTag("PostProcessing").asListTagList() : null; + } this.status = level.getString("Status"); this.structures = level.getCompoundTag("Structures"); if (level.containsKey("Sections")) { @@ -86,10 +109,14 @@ private void initReferences() { if (newSection.isEmpty()) { continue; } - this.sections[sectionIndex] = newSection; } } + + // If we haven't requested the full set of data we can drop the underlying raw data to let the GC handle it. + if(loadFlags != ALL_DATA) { + this.data = null; + } } /** @@ -118,6 +145,16 @@ public int serialize(RandomAccessFile raf, int xPos, int zPos) throws IOExceptio * @throws IOException When something went wrong during reading. */ public void deserialize(RandomAccessFile raf) throws IOException { + deserialize(raf, ALL_DATA); + } + + /** + * Reads chunk data from a RandomAccessFile. The RandomAccessFile must already be at the correct position. + * @param raf The RandomAccessFile to read the chunk data from. + * @param loadFlags A logical or of {@link LoadFlags} constants indicating what data should be loaded + * @throws IOException When something went wrong during reading. + */ + public void deserialize(RandomAccessFile raf, long loadFlags) throws IOException { byte compressionTypeByte = raf.readByte(); CompressionType compressionType = CompressionType.getFromID(compressionTypeByte); if (compressionType == null) { @@ -127,7 +164,7 @@ public void deserialize(RandomAccessFile raf) throws IOException { NamedTag tag = new NBTDeserializer(false).fromStream(dis); if (tag != null && tag.getTag() instanceof CompoundTag) { data = (CompoundTag) tag.getTag(); - initReferences(); + initReferences(loadFlags); } else { throw new IOException("invalid data tag: " + (tag == null ? "null" : tag.getClass().getName())); } diff --git a/src/main/java/net/querz/mca/LoadFlags.java b/src/main/java/net/querz/mca/LoadFlags.java new file mode 100644 index 00000000..c2e89ff0 --- /dev/null +++ b/src/main/java/net/querz/mca/LoadFlags.java @@ -0,0 +1,24 @@ +package net.querz.mca; + +public class LoadFlags { + public static long BIOMES = 0x0001; + public static long HEIGHTMAPS = 0x0002; + public static long CARVINGMARKS = 0x0004; + public static long ENTITIES = 0x0008; + public static long TILE_ENTITIES = 0x0010; + public static long CARVING_MARKS = 0x0020; + public static long TILE_TICKS = 0x0040; + public static long LIQUID_TILE_TICKS = 0x0040; + public static long TO_BE_TICKED = 0x0080; + public static long POST_PROCESSING = 0x0100; + public static long STRUCTURES = 0x0200; + public static long BLOCK_LIGHTS = 0x0400; + public static long BLOCK_STATES = 0x0800; + public static long SKY_LIGHT = 0x1000; + public static long LIGHTS = 0x2000; + public static long LIQUIDS_TO_BE_TICKED = 0x2000; + + public static long ALL_DATA = 0xffffffffffffffffL; + + +} diff --git a/src/main/java/net/querz/mca/MCAFile.java b/src/main/java/net/querz/mca/MCAFile.java index c68c5123..92037224 100644 --- a/src/main/java/net/querz/mca/MCAFile.java +++ b/src/main/java/net/querz/mca/MCAFile.java @@ -34,6 +34,17 @@ public MCAFile(int regionX, int regionZ) { * @throws IOException If something went wrong during deserialization. * */ public void deserialize(RandomAccessFile raf) throws IOException { + deserialize(raf, LoadFlags.ALL_DATA); + } + + /** + * Reads an .mca file from a {@code RandomAccessFile} into this object. + * This method does not perform any cleanups on the data. + * @param raf The {@code RandomAccessFile} to read from. + * @param loadFlags A logical or of {@link LoadFlags} constants indicating what data should be loaded + * @throws IOException If something went wrong during deserialization. + * */ + public void deserialize(RandomAccessFile raf, long loadFlags) throws IOException { chunks = new Chunk[1024]; for (int i = 0; i < 1024; i++) { raf.seek(i * 4); @@ -47,7 +58,7 @@ public void deserialize(RandomAccessFile raf) throws IOException { int timestamp = raf.readInt(); Chunk chunk = new Chunk(timestamp); raf.seek(4096 * offset + 4); //+4: skip data size - chunk.deserialize(raf); + chunk.deserialize(raf, loadFlags); chunks[i] = chunk; } } diff --git a/src/main/java/net/querz/mca/Section.java b/src/main/java/net/querz/mca/Section.java index 8958b649..d9c9e24b 100644 --- a/src/main/java/net/querz/mca/Section.java +++ b/src/main/java/net/querz/mca/Section.java @@ -1,5 +1,7 @@ package net.querz.mca; +import static net.querz.mca.LoadFlags.*; + import net.querz.nbt.tag.ByteArrayTag; import net.querz.nbt.tag.CompoundTag; import net.querz.nbt.tag.ListTag; @@ -21,6 +23,10 @@ public class Section { private int dataVersion; public Section(CompoundTag sectionRoot, int dataVersion) { + this(sectionRoot, dataVersion, -1); + } + + public Section(CompoundTag sectionRoot, int dataVersion, long loadFlags) { data = sectionRoot; this.dataVersion = dataVersion; ListTag rawPalette = sectionRoot.getListTag("Palette"); @@ -37,9 +43,15 @@ public Section(CompoundTag sectionRoot, int dataVersion) { LongArrayTag blockStates = sectionRoot.getLongArrayTag("BlockStates"); ByteArrayTag skyLight = sectionRoot.getByteArrayTag("SkyLight"); - this.blockLight = blockLight != null ? blockLight.getValue() : null; - this.blockStates = blockStates != null ? blockStates.getValue() : null; - this.skyLight = skyLight != null ? skyLight.getValue() : null; + if((loadFlags | BLOCK_LIGHTS) != 0) { + this.blockLight = blockLight != null ? blockLight.getValue() : null; + } + if((loadFlags | BLOCK_STATES) != 0) { + this.blockStates = blockStates != null ? blockStates.getValue() : null; + } + if((loadFlags | SKY_LIGHT) != 0) { + this.skyLight = skyLight != null ? skyLight.getValue() : null; + } } Section() {} From 7e1be3e7fe5d02f3767ddc29b08f7c59aca104a2 Mon Sep 17 00:00:00 2001 From: prydin Date: Tue, 2 Jun 2020 15:54:49 -0400 Subject: [PATCH 24/51] Use ALL_DATA instead of -1 --- src/main/java/net/querz/mca/Chunk.java | 2 +- src/main/java/net/querz/mca/Section.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/net/querz/mca/Chunk.java b/src/main/java/net/querz/mca/Chunk.java index c0691ab9..89a712b4 100644 --- a/src/main/java/net/querz/mca/Chunk.java +++ b/src/main/java/net/querz/mca/Chunk.java @@ -50,7 +50,7 @@ public class Chunk { */ public Chunk(CompoundTag data) { this.data = data; - initReferences(-1); + initReferences(ALL_DATA); } private void initReferences(long loadFlags) { diff --git a/src/main/java/net/querz/mca/Section.java b/src/main/java/net/querz/mca/Section.java index d9c9e24b..ea4887f4 100644 --- a/src/main/java/net/querz/mca/Section.java +++ b/src/main/java/net/querz/mca/Section.java @@ -23,7 +23,7 @@ public class Section { private int dataVersion; public Section(CompoundTag sectionRoot, int dataVersion) { - this(sectionRoot, dataVersion, -1); + this(sectionRoot, dataVersion, ALL_DATA); } public Section(CompoundTag sectionRoot, int dataVersion, long loadFlags) { From aa2561b76a24520fcc20449498ba345847745ec1 Mon Sep 17 00:00:00 2001 From: prydin Date: Sat, 6 Jun 2020 13:32:18 -0400 Subject: [PATCH 25/51] Fixed logical operation bugs. Added tests. Added check for partial chunk on save. Upgraded gradle --- NBT.iml | 13 ++++ gradle/wrapper/gradle-wrapper.jar | Bin 54208 -> 58702 bytes gradle/wrapper/gradle-wrapper.properties | 3 +- gradlew | 57 ++++++++------ gradlew.bat | 18 ++++- src/main/java/net/querz/mca/Chunk.java | 42 +++++++---- src/main/java/net/querz/mca/LoadFlags.java | 21 +++--- src/main/java/net/querz/mca/MCAUtil.java | 27 +++++++ src/main/java/net/querz/mca/Section.java | 6 +- src/test/java/net/querz/mca/MCAFileTest.java | 74 +++++++++++++++++++ 10 files changed, 206 insertions(+), 55 deletions(-) create mode 100644 NBT.iml diff --git a/NBT.iml b/NBT.iml new file mode 100644 index 00000000..065a1192 --- /dev/null +++ b/NBT.iml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index ae6575fa67dec3c9fa5902c0648132a2aa623f6d..cc4fdc293d0e50b0ad9b65c16e7ddd1db2f6025b 100644 GIT binary patch literal 58702 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

@up^y4&I8N>~wvL%f(S2W%NN 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 D613UA literal 54208 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 diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 77a041f6..622ab64a 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,5 @@ -#Wed Mar 29 16:28:10 CEST 2017 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-6.5-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-3.3-all.zip diff --git a/gradlew b/gradlew index 4453ccea..2fe81a7d 100755 --- a/gradlew +++ b/gradlew @@ -1,5 +1,21 @@ #!/usr/bin/env sh +# +# Copyright 2015 the original author or authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + ############################################################################## ## ## Gradle start up script for UN*X @@ -28,16 +44,16 @@ APP_NAME="Gradle" APP_BASE_NAME=`basename "$0"` # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -DEFAULT_JVM_OPTS="" +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD="maximum" -warn ( ) { +warn () { echo "$*" } -die ( ) { +die () { echo echo "$*" echo @@ -109,8 +125,8 @@ if $darwin; then GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" fi -# For Cygwin, switch paths to Windows format before running java -if $cygwin ; then +# For Cygwin or MSYS, switch paths to Windows format before running java +if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then APP_HOME=`cygpath --path --mixed "$APP_HOME"` CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` JAVACMD=`cygpath --unix "$JAVACMD"` @@ -138,35 +154,30 @@ if $cygwin ; then else eval `echo args$i`="\"$arg\"" fi - i=$((i+1)) + i=`expr $i + 1` done case $i in - (0) set -- ;; - (1) set -- "$args0" ;; - (2) set -- "$args0" "$args1" ;; - (3) set -- "$args0" "$args1" "$args2" ;; - (4) set -- "$args0" "$args1" "$args2" "$args3" ;; - (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; - (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; - (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; - (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; - (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + 0) set -- ;; + 1) set -- "$args0" ;; + 2) set -- "$args0" "$args1" ;; + 3) set -- "$args0" "$args1" "$args2" ;; + 4) set -- "$args0" "$args1" "$args2" "$args3" ;; + 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; esac fi # Escape application args -save ( ) { +save () { for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done echo " " } -APP_ARGS=$(save "$@") +APP_ARGS=`save "$@"` # Collect all arguments for the java command, following the shell quoting and substitution rules eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" -# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong -if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then - cd "$(dirname "$0")" -fi - exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat index e95643d6..24467a14 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -1,3 +1,19 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + @if "%DEBUG%" == "" @echo off @rem ########################################################################## @rem @@ -14,7 +30,7 @@ set APP_BASE_NAME=%~n0 set APP_HOME=%DIRNAME% @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -set DEFAULT_JVM_OPTS= +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" @rem Find java.exe if defined JAVA_HOME goto findJavaFromJavaHome diff --git a/src/main/java/net/querz/mca/Chunk.java b/src/main/java/net/querz/mca/Chunk.java index 89a712b4..59c45b07 100644 --- a/src/main/java/net/querz/mca/Chunk.java +++ b/src/main/java/net/querz/mca/Chunk.java @@ -1,5 +1,6 @@ package net.querz.mca; +import java.rmi.UnexpectedException; import net.querz.nbt.tag.CompoundTag; import net.querz.nbt.tag.ListTag; import net.querz.nbt.io.NamedTag; @@ -18,6 +19,8 @@ public class Chunk { public static final int DEFAULT_DATA_VERSION = 1628; + private boolean partial; + private int lastMCAUpdate; private CompoundTag data; @@ -64,48 +67,50 @@ private void initReferences(long loadFlags) { this.dataVersion = data.getInt("DataVersion"); this.inhabitedTime = level.getLong("InhabitedTime"); this.lastUpdate = level.getLong("LastUpdate"); - if((loadFlags | BIOMES) != 0) { + if((loadFlags & BIOMES) != 0) { this.biomes = level.getIntArray("Biomes"); } - if((loadFlags | HEIGHTMAPS) != 0) { + if((loadFlags & HEIGHTMAPS) != 0) { this.heightMaps = level.getCompoundTag("Heightmaps"); } - if((loadFlags | CARVING_MARKS) != 0) { + if((loadFlags & CARVINGMASKS) != 0) { this.carvingMasks = level.getCompoundTag("CarvingMasks"); } - if((loadFlags | ENTITIES) != 0) { + if((loadFlags & ENTITIES) != 0) { this.entities = level.containsKey("Entities") ? level.getListTag("Entities").asCompoundTagList() : null; } - if((loadFlags | TILE_ENTITIES) != 0) { + if((loadFlags & TILE_ENTITIES) != 0) { this.tileEntities = level.containsKey("TileEntities") ? level.getListTag("TileEntities").asCompoundTagList() : null; } - if((loadFlags | TILE_TICKS) != 0) { + if((loadFlags & TILE_TICKS) != 0) { this.tileTicks = level.containsKey("TileTicks") ? level.getListTag("TileTicks").asCompoundTagList() : null; } - if((loadFlags | LIQUID_TILE_TICKS) != 0) { + if((loadFlags & LIQUID_TICKS) != 0) { this.liquidTicks = level.containsKey("LiquidTicks") ? level.getListTag("LiquidTicks").asCompoundTagList() : null; } - if((loadFlags | LIGHTS) != 0) { + if((loadFlags & LIGHTS) != 0) { this.lights = level.containsKey("Lights") ? level.getListTag("Lights").asListTagList() : null; } - if((loadFlags | LIQUIDS_TO_BE_TICKED) != 0) { + if((loadFlags & LIQUIDS_TO_BE_TICKED) != 0) { this.liquidsToBeTicked = level.containsKey("LiquidsToBeTicked") ? level.getListTag("LiquidsToBeTicked").asListTagList() : null; } - if((loadFlags | TO_BE_TICKED) != 0) { + if((loadFlags & TO_BE_TICKED) != 0) { this.toBeTicked = level.containsKey("ToBeTicked") ? level.getListTag("ToBeTicked").asListTagList() : null; } - if((loadFlags | POST_PROCESSING) != 0) { + if((loadFlags & POST_PROCESSING) != 0) { this.postProcessing = level.containsKey("PostProcessing") ? level.getListTag("PostProcessing").asListTagList() : null; } this.status = level.getString("Status"); - this.structures = level.getCompoundTag("Structures"); - if (level.containsKey("Sections")) { + if((loadFlags & STRUCTURES) != 0) { + this.structures = level.getCompoundTag("Structures"); + } + if ((loadFlags & (BLOCK_LIGHTS|BLOCK_STATES|SKY_LIGHT)) != 0 && level.containsKey("Sections")) { for (CompoundTag section : level.getListTag("Sections").asCompoundTagList()) { int sectionIndex = section.getByte("Y"); if (sectionIndex > 15 || sectionIndex < 0) { continue; } - Section newSection = new Section(section, dataVersion); + Section newSection = new Section(section, dataVersion, loadFlags); if (newSection.isEmpty()) { continue; } @@ -116,6 +121,9 @@ private void initReferences(long loadFlags) { // If we haven't requested the full set of data we can drop the underlying raw data to let the GC handle it. if(loadFlags != ALL_DATA) { this.data = null; + this.partial = true; + } else { + partial = false; } } @@ -125,9 +133,13 @@ private void initReferences(long loadFlags) { * @param xPos The x-coordinate of the chunk. * @param zPos The z-coodrinate of the chunk. * @return The amount of bytes written to the RandomAccessFile. + * @throws UnsupportedOperationException When something went wrong during writing. * @throws IOException When something went wrong during writing. */ public int serialize(RandomAccessFile raf, int xPos, int zPos) throws IOException { + if(this.partial) { + throw new UnsupportedOperationException("Partially loaded chunks cannot be serialized"); + } ByteArrayOutputStream baos = new ByteArrayOutputStream(4096); try (BufferedOutputStream nbtOut = new BufferedOutputStream(CompressionType.ZLIB.compress(baos))) { new NBTSerializer(false).toStream(new NamedTag(null, updateHandle(xPos, zPos)), nbtOut); @@ -608,7 +620,7 @@ public CompoundTag updateHandle(int xPos, int zPos) { } else { if (biomes != null && biomes.length == 1024) level.putIntArray("Biomes", biomes); } - if (heightMaps != null) level.put("HeightMaps", heightMaps); + if (heightMaps != null) level.put("Heightmaps", heightMaps); if (carvingMasks != null) level.put("CarvingMasks", carvingMasks); if (entities != null) level.put("Entities", entities); if (tileEntities != null) level.put("TileEntities", tileEntities); diff --git a/src/main/java/net/querz/mca/LoadFlags.java b/src/main/java/net/querz/mca/LoadFlags.java index c2e89ff0..b9db8460 100644 --- a/src/main/java/net/querz/mca/LoadFlags.java +++ b/src/main/java/net/querz/mca/LoadFlags.java @@ -3,20 +3,19 @@ public class LoadFlags { public static long BIOMES = 0x0001; public static long HEIGHTMAPS = 0x0002; - public static long CARVINGMARKS = 0x0004; + public static long CARVINGMASKS = 0x0004; public static long ENTITIES = 0x0008; public static long TILE_ENTITIES = 0x0010; - public static long CARVING_MARKS = 0x0020; public static long TILE_TICKS = 0x0040; - public static long LIQUID_TILE_TICKS = 0x0040; - public static long TO_BE_TICKED = 0x0080; - public static long POST_PROCESSING = 0x0100; - public static long STRUCTURES = 0x0200; - public static long BLOCK_LIGHTS = 0x0400; - public static long BLOCK_STATES = 0x0800; - public static long SKY_LIGHT = 0x1000; - public static long LIGHTS = 0x2000; - public static long LIQUIDS_TO_BE_TICKED = 0x2000; + public static long LIQUID_TICKS = 0x0080; + public static long TO_BE_TICKED = 0x0100; + public static long POST_PROCESSING = 0x0200; + public static long STRUCTURES = 0x0400; + public static long BLOCK_LIGHTS = 0x0800; + public static long BLOCK_STATES = 0x1000; + public static long SKY_LIGHT = 0x2000; + public static long LIGHTS = 0x4000; + public static long LIQUIDS_TO_BE_TICKED = 0x8000; public static long ALL_DATA = 0xffffffffffffffffL; diff --git a/src/main/java/net/querz/mca/MCAUtil.java b/src/main/java/net/querz/mca/MCAUtil.java index b12304a2..b7a28ab3 100644 --- a/src/main/java/net/querz/mca/MCAUtil.java +++ b/src/main/java/net/querz/mca/MCAUtil.java @@ -40,6 +40,33 @@ public static MCAFile read(File file) throws IOException { } } + + /** + * @see MCAUtil#read(File) + * @param file The file to read the data from. + * @return An in-memory representation of the MCA file with decompressed chunk data. + * @param loadFlags A logical or of {@link LoadFlags} constants indicating what data should be loaded + * @throws IOException if something during deserialization goes wrong. + * */ + public static MCAFile read(String file, long loadFlags) throws IOException { + return read(new File(file), loadFlags); + } + + /** + * Reads an MCA file and loads all of its chunks. + * @param file The file to read the data from. + * @return An in-memory representation of the MCA file with decompressed chunk data + * @param loadFlags A logical or of {@link LoadFlags} constants indicating what data should be loaded + * @throws IOException if something during deserialization goes wrong. + * */ + public static MCAFile read(File file, long loadFlags) throws IOException { + MCAFile mcaFile = newMCAFile(file); + try (RandomAccessFile raf = new RandomAccessFile(file, "r")) { + mcaFile.deserialize(raf, loadFlags); + return mcaFile; + } + } + /** * Calls {@link MCAUtil#write(MCAFile, File, boolean)} without changing the timestamps. * @see MCAUtil#write(MCAFile, File, boolean) diff --git a/src/main/java/net/querz/mca/Section.java b/src/main/java/net/querz/mca/Section.java index ea4887f4..e6b25327 100644 --- a/src/main/java/net/querz/mca/Section.java +++ b/src/main/java/net/querz/mca/Section.java @@ -43,13 +43,13 @@ public Section(CompoundTag sectionRoot, int dataVersion, long loadFlags) { LongArrayTag blockStates = sectionRoot.getLongArrayTag("BlockStates"); ByteArrayTag skyLight = sectionRoot.getByteArrayTag("SkyLight"); - if((loadFlags | BLOCK_LIGHTS) != 0) { + if((loadFlags & BLOCK_LIGHTS) != 0) { this.blockLight = blockLight != null ? blockLight.getValue() : null; } - if((loadFlags | BLOCK_STATES) != 0) { + if((loadFlags & BLOCK_STATES) != 0) { this.blockStates = blockStates != null ? blockStates.getValue() : null; } - if((loadFlags | SKY_LIGHT) != 0) { + if((loadFlags & SKY_LIGHT) != 0) { this.skyLight = skyLight != null ? skyLight.getValue() : null; } } diff --git a/src/test/java/net/querz/mca/MCAFileTest.java b/src/test/java/net/querz/mca/MCAFileTest.java index 794489bf..2ebb4f11 100644 --- a/src/test/java/net/querz/mca/MCAFileTest.java +++ b/src/test/java/net/querz/mca/MCAFileTest.java @@ -2,6 +2,7 @@ import net.querz.nbt.tag.CompoundTag; import net.querz.nbt.tag.ListTag; +import static net.querz.mca.LoadFlags.*; import java.io.File; import java.io.IOException; import java.io.RandomAccessFile; @@ -343,6 +344,79 @@ public void testChunkInvalidDataTag() { }, IOException.class); } + private void assertLoadFLag(Object field, long flags, long wantedFlag) { + if((flags & wantedFlag) != 0) { + assertNotNull(String.format("Should not be null. Flags=%08x, Wanted flag=%08x", flags, wantedFlag), field); + } else { + assertNull(String.format("Should be null. Flags=%08x, Wanted flag=%08x", flags, wantedFlag), field); + } + } + + private void assertPartialChunk(Chunk c, long loadFlags) { + assertLoadFLag(c.getBiomes(), loadFlags, BIOMES); + assertLoadFLag(c.getHeightMaps(), loadFlags, HEIGHTMAPS); + assertLoadFLag(c.getEntities(), loadFlags, ENTITIES); + assertLoadFLag(c.getCarvingMasks(), loadFlags, CARVINGMASKS); + assertLoadFLag(c.getLights(), loadFlags, LIGHTS); + assertLoadFLag(c.getPostProcessing(), loadFlags, POST_PROCESSING); + assertLoadFLag(c.getLiquidTicks(), loadFlags, LIQUID_TICKS); + assertLoadFLag(c.getLiquidsToBeTicked(), loadFlags, LIQUIDS_TO_BE_TICKED); + assertLoadFLag(c.getTileTicks(), loadFlags, TILE_TICKS); + assertLoadFLag(c.getTileEntities(), loadFlags, TILE_ENTITIES); + assertLoadFLag(c.getToBeTicked(), loadFlags, TO_BE_TICKED); + assertLoadFLag(c.getSection(0), loadFlags, BLOCK_LIGHTS|BLOCK_STATES|SKY_LIGHT); + if((loadFlags & (BLOCK_LIGHTS|BLOCK_STATES|SKY_LIGHT)) != 0) { + Section s = c.getSection(0); + assertNotNull(String.format("Section is null. Flags=%08x", loadFlags), s); + assertLoadFLag(s.getBlockStates(), loadFlags, BLOCK_STATES); + assertLoadFLag(s.getBlockLight(), loadFlags, BLOCK_LIGHTS); + assertLoadFLag(s.getSkyLight(), loadFlags, SKY_LIGHT); + } + } + + public void testPartialLoad() { + long[] flags = new long[] { + BIOMES, + HEIGHTMAPS, + ENTITIES, + CARVINGMASKS, + LIGHTS, + POST_PROCESSING, + LIQUID_TICKS, + LIQUIDS_TO_BE_TICKED, + TILE_TICKS, + TILE_ENTITIES, + TO_BE_TICKED, + BLOCK_STATES, + BLOCK_LIGHTS, + SKY_LIGHT + }; + + MCAFile f = assertThrowsNoException(() -> MCAUtil.read(copyResourceToTmp("r.2.2.mca"))); + Chunk c = f.getChunk(0); + c.setCarvingMasks(getSomeCompoundTag()); + c.setEntities(getSomeCompoundTagList()); + c.setLights(getSomeListTagList()); + c.setTileEntities(getSomeCompoundTagList()); + c.setTileTicks(getSomeCompoundTagList()); + c.setLiquidTicks(getSomeCompoundTagList()); + c.setToBeTicked(getSomeListTagList()); + c.setLiquidsToBeTicked(getSomeListTagList()); + c.setHeightMaps(getSomeCompoundTag()); + c.setPostProcessing(getSomeListTagList()); + c.getSection(0).setBlockLight(new byte[2048]); + File tmp = this.getNewTmpFile("r.2.2.mca"); + assertThrowsNoException(() -> MCAUtil.write(f, tmp)); + + for(long flag : flags) { + MCAFile mcaFile = assertThrowsNoException(() -> MCAUtil.read(tmp, flag)); + c = mcaFile.getChunk(0, 0); + assertPartialChunk(c, flag); + assertThrowsException(() -> MCAUtil.write(mcaFile, getNewTmpFile("r.12.34.mca")), UnsupportedOperationException.class); + } + tmp.delete(); + } + public void test1_15GetBiomeAt() throws IOException { MCAFile f = assertThrowsNoException(() -> MCAUtil.read(copyResourceToTmp("r.0.0.mca"))); assertEquals(162, f.getBiomeAt(31, 0, 63)); From 96c78c5cbe5997a16106b5f9e2ce82c04b4aec32 Mon Sep 17 00:00:00 2001 From: Querz Date: Mon, 8 Jun 2020 10:28:34 +0200 Subject: [PATCH 26/51] remove intellij module file --- NBT.iml | 13 ------------- 1 file changed, 13 deletions(-) delete mode 100644 NBT.iml diff --git a/NBT.iml b/NBT.iml deleted file mode 100644 index 065a1192..00000000 --- a/NBT.iml +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - - - - - \ No newline at end of file From 6b8e7fa51b76be61c25bf7a931760ad6c79b540a Mon Sep 17 00:00:00 2001 From: Querz Date: Mon, 8 Jun 2020 10:48:41 +0200 Subject: [PATCH 27/51] update gradle-wrapper.jar to actual 6.5 --- gradle/wrapper/gradle-wrapper.jar | Bin 58702 -> 58910 bytes gradlew | 2 ++ gradlew.bat | 4 ++++ 3 files changed, 6 insertions(+) diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index cc4fdc293d0e50b0ad9b65c16e7ddd1db2f6025b..62d4c053550b91381bbd28b1afc82d634bf73a8a 100644 GIT binary patch delta 18673 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 Bgx delta 18406 zcmV)LK)Ju3%mdEK1F(Jx4JjpKHaG+T0N4ir06~+HK^Bv~2nmy_30{AWFTQj}gzAi) z`ZJcGAP#PLEsT5w8= zK`AmOiPLpBW8y4cY(N6%q~^Sd3m9s^Fh&ey4ZLI`ikBnjW}rSMau}83qJc33mkf+E zL>#YN7Cu8q-t)`pvd@1@jz};0d|^TO>AWPt#G5oI0-hT}UO9QKx6$K$2TzE}1| zrINQRfpC@U{smtw+FrG?c}Dm};RXyWNBF00*AC7yTukmk;ISZkrWnFQUWvFiyX1M6+sSX_TqKq zKcZ1-5~Z505b&T{8Jgp6S(F%BlKJa=k*8e|q_0fmGNYq1Cxe$tG5|y4!)AtP(Q_$i zkQZ(_n4`*$GMF2|WyB~U6mN|2g<8f5$<^Ix1uu(&m*te8o?ORjEmnP>ERXg~I%=fi}Yd zrFE$E>4~Zv*z;o3C=q82yk_BbT($59t{QlgA@e`FPDy`n;f>E;7e%U0?_-QNgO-#X zYTxy3p_gjCy9_64gQU=Cy!?WAyDHqG$n`^mx z(QOz9@r8eeW;ro*{WZ&>p*1AuhSo6I58@EX!}K&udX3h2Y6UFSyNpn(Zy8!jTSoY< z^6Dhn3Z3Kw^8Fn3_yUdil90Y4JkU@1qZ>!mbsJZ>Ozk9ci1sv_HN5u`p)WAlRe$?k z>`Q$F?Hib>uGa7hbdtC4AWF(mbL3+f)6)DkVsd}!CF4ERMIc#eiq&BlB-iM&|CX@6 zqwKyXq#w|SA8`slDRwF1OlX_z2JsYjN{0v{lPRxOPuP)G7-D(WxPm|t-Mzmfe$TbrAG$pE&#iWx@ ztHxwE)61%)nA*vtG?i2xw1Eo-tOm-9Zirf56O@(oj>k$`_pQ!bUK~v~-dV-IRWyIN z+4_iz_V+HWK3PMH<2i*EBIeTbc|589dFdMRL7p-+{{>J>0|W{H00;;G002P%&$S9Z z7n2r$1aeno>ZX)q=xXqr@;nEsl=1lDYK$uLNH zD~$;dO?&_!%6Ml>D`^SU{joE1?>Xn5J2&&|_xGOw9^$EmSu9zwv1DR7jTH;Gv6{wO z8tZy{$HE5gT1db(v1#HSL+XVe`syV^!Y%AEB!4SWT`*j(`k{E&IXn>ZJwFI2;#8v= z5B9k9^?4Lbs;1wj+>VTndfOe<6ru8KTt$+>eiMd5Rs!B`3&NDDk!*Mk$?Jjex{|kA zLVB;FZWu(ozJ6Yy%rM^&YKQ3ENY=-4eiSmSxrOQ{{+WBBP~K!v*~EQ@Rd;IPt+MXg zIDaZJEMEX*uy&)4tclmY?mcsoDrz4#GMFQc3p_E*HI-@=Te{y5Z6QrOuu+6Zm-shv z!exL?mP~BfG~GwK$YT>v7>fUQnGCs8V`mbJQ=4YU#>9Y!4R5#CR^pIhR?kI7gj79- z4YxW5QPK|^<-++8!?Ov%f23y5#>j+Ua?BypeOwyA`f@7g5Dn;)?10WglIV{~=Z~ccn8Ex=`Z=w}$QPUJD|ZYS`83kI zn^=fxw_^MvuEnJdYQ2D~uy8}evgtoiO9KQ7000OG0000%08gEtAHfa)023Yn03nl+ zK^Bu<)((HISqXStS9Si6W_eGer^RDi_SlItj$=pG8as|1%ZU@q>)7&0)>tyK<2X;! zlQi~dMwxkITUmfW-4=p{2A0^}lx( zjiiYP{c2nH-FNP}XFvbFPyWgeUi<=pt@3C9>+pXo0Yq`VnzJvf;aAo0YX-g&fFHlE zUVcMy{iccE3gD~wZ3Dj(fQ8=;;P>#g0N#h+SHmADo9|f=lUpMf_2EGx%O8iLx zEAY(#K7c<}%YUYZKR59g0lXi7sfNEY@UnXTbpTJ}TLJtH{#FftS50kyZ{i;U_(%Mc zfq#EC@$CRM;a_}Ee;R-q{44$~fT!^9CcYEEf8alrgzqZKcMbfPvg3Pd_-{4*j~c$O zhF8?^12xRj7J(@eK%Z2Z;xWZ*h%bQMxNeG{PcamWsbAeX%lW{VFoAx=Qb~fSeOI=7NQuau1 zIyIJdGMVmp#&yUj2$FPhYERZ3cam;=*mhGS*7rw`M0-v|&kXGEi$!{(Wa~a_pSOQI z5_WQ|Bj%>#$+2C6+P$e{#FOPNd)Q%hR*qqzH8-MC>ml^l(aWz){W($Hb| zG%2Rqm}?K8J8DlByfH@#YFTqldAUi)?db1|6tu)s9S7nGM{6+bkr8DY>tcUCE9+;( z$++=kL08@Uy)1zJ7l)lmwaCp4MV*nP>!g!*g2rXji3L)-xvegnangInoI<5C@k80s zQB^Tjr@ClQGCR@dDBEk?lxruto#dE1UZ%hqDU#$c6I?93y{W9y%v>^LGB+j2S(krNckBr+SH-z~Wob2%oLe-c-H;ALZZl+yAzKZ(od##@ zQKvs07pz+l?9B@U%80}b*+$(AdY7Gv4=+sU=8E}R?1ai(V7T$zs@=*tIBq0RPELa%*q{tW$fzVY{GV( z{!%1vuAGqhZtKV;HKdu76nY_*rZ>TP;&izRRy0+V5Ky!MCpn_vx+ecpz{O>AOC_2= zmB}M$bt+R%+>;(=Q3rowscd@KQ9Z`aE&@oql4D^CZ&bsZEM(wX$l{!Z^EfBiwFLOw z@l+b0#^DCZ5?Vac6%UeLajmc?h661>xpPD^&lU6$M} zyDhm#?zQkXT(a;XJY&foVawl8Y_%>81kOl#L$Z_0ai@Gq$=`pghJBXomjf1_#d8*B z@Dih_3~$qEv*e&0vLvF=V(k(RSaL+VEjcPtLwYRfm1CClDQn*?F{U-H&?TLnbe$1H zj$6_%Y{BIvkmxCuupm?v8NOg=!etxVm9YbcoUr6|GHA(3IVFH0_gQjU1}wQ>Mc*nf zSxe5~B}*PqgROte8&b1jHH>heILE>?t_plAX&FmKWlV7C#GbxrBziF1pJ9oH4;??= z8`gLeo)0+T)OjZzwv#k6;|%kZ3D03l*iD6tm>cG%@If87@CeH$9Usanp7wBW!XetI zZJ9WGF`O+ajJ30VmAX_q{!jtlRxRwFu53J^d)uEk&ys&}iCg$Mp10(z^75P{ESV5> z#ZY_OsW#Exr2)>8luR*9%t5{Zvz!pW2y&SWPiQ?y|uOxhHY zCwzF|s4mls#K3%~v==%>wG@x8`NZA=VyeVVS@Mu-J`I8G=L>*n72XO!SD$~Kn4=h} zOnV-G1Dwqx+ZqzrZp?IS%!HdZW_Ia+o6Dw_JS=~Y5JD<6c4S}a8g%1Qz_o=piQ9^O zSAZE}eWlw0-LHHgmqS6ajJ>>>yXO_i@$q!(LjJzCw!GtGQ%TppSeELQ6%ReZk^i^L zsJcv)SMwChb2m@_#JQ1pI-~EuC)t*6N=LWEId|Dy*0lJ>8)~Y2^HXbebT?U^)-?-c&bvqk z=~bWeV3yb9xj@}o;l5S*a$iSJ=T*z9GFQv5o%y>NRWo01@%BsKBfRaV^>Z~xWz+Hn_qoG|m2BUIblhd37>q$AqBH!ejlr<-JC}Qg6RC{4f-$bK z;c-WykHPt&RL0HEdI^h#nXSTYa&|21C}dWRaUgAvP3TMEBmBzeBqP@LnlAr^Q&?Sw-O@Rz9M*uN;=Xy|)q7XIeCHujXfa#ihR#5% zbQZ*I{2@ja6{zGCzb@{ru;^mseXp#3wKc{oo;;sA#~RpKA)u!!#JJKIjtW@U zl&gN#(^$zv^>y34bg`5Uh5W~V)82oja;3^}?pwz{no3XD?gAGoM5=-d1um?|Iz%CG zU?7CEILCPcKousi21%q!?~{Cgu$*7BCT!HkQcO=M>Jy<$5xnV!=s!v=Na*Oyg~mOv05t6~Vw&)Lcj146H$! zM(tJ9DO)_+mO8!vPR3#=Wz3Tu<|q55WFRajWgIZ7`$~F zP1hQ0R#soKchof2>St|Z?Fww3sD9!pSX>HRdg4i}(KKgAZS!Ygwvg6X^EtHm(H1g2 zE30#8eQx$$T=E3la%g`K`L4qZ`EuxJyN+g3ZhHeJ_b650UXB&GdjUp z7AKgTlT6Dgyd9_U4DQGCID;24M4b-4icx$GWB5Aa_$K@JWzv7XP1<)z`yOep;HtQi}{F5vvg+O3KU%od~lZi z)v#!#_=gM}X6a5vxn+G~s^4-Hn(_#PsJdzW47Ln5=de{c+|HkE{JDca+xfF&8h2hU zwW(ikIA7NG>+xYmbWX)b$oVXuE2#Kn0abT5w@hPawC#V(@V*f2Y@x0kc0CD8*X~`c z-PYjUGuS;ijeCOk=CG%k2;9YI4tv>*bMTXg!iQ+b!wl6U4CL#vin$FF$nGYT&B62T z5^U@)!NzWUl=)?l2w)%6Q21sI)m7g|4O6tDC(bnJ*_1^8}`=BRyG)7{lw@tVn&7r5$7xIRDT#OEST7tbf93w-| z7twc_JCCcK{quK*yi$p~(>sHK!QcsnSFgK96HWHOF($Q7`x-06sF{(0?v;09HT$BS z{jif=ag5d7$66m}wZDP&{TM4;#p^ysU^O{D!EAqd$h`rd;>=6RYJ3`(nZa?!I8S?w zS$_ea;jE9?@C%$Nh<>~j>&JCmdGjC`e->A0ql$0~s%EL)qid6$kW(cco8@upEs;pj zRBo9j0OZf4C;6t5xg|KrGwEpXB*$p$b=*XNTU)3Xo+x`7I&(PHmamt~tgibmmnx=F zAB=w=WCA}#+n-?@eVFL>Y+0pjDOtZo*D`M|wD@!SAh(DkFP18$2J#A8wY_~@oWV;4 zJHJ6CsxVInPt(Hg;QiX#Gs@cF0~|ZOL0dh1!6EhV2ZtL>onY3#H#X?y!xtN5nZySD zAK2BacpQnYN;Em>!yd8atUGQn#%8V)B4&@Ch z_~s{0e_m(CVj{kveG3u*u4&&wJO#d}F9czB(U&;$QOVb{*u=G%fxK(mgOM= ztN%PTxwekAq5da#9kD#{zq5rPI35HAuM6&z!9F{G-A~g{7{;Hobt@|!;6z2f1evl8 zq~k*qq8KBI(FF8jW(nRM%3&0`j8l$7KTT`&x79rE|w zavIiu3&$0Y>B2sD+@>$KMG){Bt;j_yT<+DZO6WUYjaKRLDhBhF7lhn(HxGn;q*71# zn=TIm5io2NE~<-L4ZmhZCae!WKRy<|6;tw^G^(R*Huk8<@Rs9=*UkE_@Za)XSGj7~ z+^urok$HSkub-LLgQjg$!mwUA-(f%1(@z|K&k3J0%ymI}G*u9|!K~?z9 zz9aS+l7-?shHNF|_R%&!j+-*L>LkBhE;B3^t_+u~#K&?Qup$BLeE_$CK{9{I@Yn(&4C{adLKeb~1f)U;5)1(e5Cc_tBrnOx zEb+}7lBlh=MR93eP}_oat%?g(QAxlk+FIAD)vC3vTNmxFwN|TEG~fTcH#3-Fxo2XZfFV&%1f%iw6%9(MxsG{dABE={g9BeK!tNi$Mj1Ac!h;86;X zb`ih)sGs~iM&`y!GtSTBIVjBpKTqW2{IrWFNt3VeWIqk(DMDYM@Khfc`sscyQn=Vp zqqxLR5AZY@DV1ir!ZZ9do=5q4CeQLyInS2YInvCPX5Iig$jAG5zQScPy}(cB@o%tngDFFZ1z9(l5{EN?zgTlex-|rYJ9v_M%F33Ug26FpX%c}8Q9>bCA?9ZulwnBK271%6+S~AH~Hyg*8H@B zH!EE4r&Ypz1vmIumzPFq<|*9dr*+({aLCVR@)l{reyWv`h@Up{C?9`ErH@H7&(AG< zmX8gQIiAfXCq%QYK5mu1O$2IJ_-sEli(Q-foB{M9pDWEb6#k~4B7BN8=LzBY3V%!C zZ~Ms*+OzmOvS6Didx4)m;tS>VLV4OQ9=cHBixj??Nv({=O)VTQZ`SL#Sg%ptC=YA# zxE^N;EUj3!cwN=nGnRi=)~sDqxn$ki%2m~8tX{l!Ia5y6nc7yZG^|CNN^49b6m43- zG_X7-{G#T1Eu7GOdn%TSop@J@5w>8*&KrB#yIY5`x?^w>kVBq^`)V>$u!?lgIc?XLmNX)2}3hOvFL)S zw$nqgQn87+qCS6fo@we)vw}tq*xm?jPRRQ@Bg{0&W*^nf(sgSpL6VzEQq!!>oHMs3 z5n&n{7S)$#Oe%aShO7DxGtEAtl_&LEg`E}Id1=T<3)Q2uYHhwD)FSV&C2*p*$g;S? z--TN+S)T~Qycq&dEa=T;s#Em1n)Nm&4&kp7o@}((XGMPy5@T)hxL%(yLS}pEN?;K{ zTpDW9v%EqR)1WY6Q zNwvgUC62B!wfZe9wH6C!3V%=G%M`ksX~b6D2sO56nr}@k23VckN9Fz+JAx$%(n*<# z3H#?|B4K~29eRNW)s0qNk4y-5{%3Y*k~nxcQ2nJ}>V^ds=piy0BkG%W&m7?}@z zS~eq#XBcy}5tE#}BoPYRoN%1du>J%xqxXuARq2pjkoLc|qB4ptjYJEKo=Gy$22yz} z648I62uBq}F(Vm-3SR+}hT^rMD9$kG>MEGJ0cyqK)tUe=yFm}@l4}hI`+!i1C#3@!IEOKDq~#IqOb}Hvhp&RRYVgJJ)M8oU6xqx23-yp=3TG}&m7t^!?$DHOYo{ctG&Sl}r6&5S{HOsn_rca;F)Z}>7rOw6w&wQ2DHeH$- z(#-6UVzx-(eBh>}aNH$bC#4w85k?D-ol#~TmEtw`$h?l2a8kVH39pi4;kBV>@$rAM z_>`kqe5}bft8G@#F7suV1;>np%!nG0WHn>@F{_K!{u(RE+hLKaz^4hBO3%`BD*cuI zrqUzys7k-5KdAILJ)zPCbfHS$p=~O4P^U_l^3~Y*n5tG;bd5?E((E`_g^mmjHo9bd2V4SYRn$S;3E`wU@rqslk&%}^mzLxg zNtUR=%t9eJB|knT*bvj>!D!43qF6PxP&BAT+d*dFaE(ban-;7{p0q(T7Hoe+9uMkm zIF!Tf!5L|~_GzdwdW2iG`AiI}ypKCn?&SR{AK-hKPCKj+$-U#)A{LB%H3?LHkh>Iq zNact5Ak&H?Yb;*V3}KqqJ?O9soF8Gp?PmJ1%0K0wsr(pB%FXMg=n2(ZnLQY5bXpp0 z)Zo{K3Hj0dXdQ6vNE6Z8gUx^1Rz0{`*P}sGi?m1y5Nr*Z&C~Lu)`x)+37XBCiAE1j zaV-0%1Y3+47Mkrr=+Q2F+2|}uqH|hu9tI6c{Syqup|&B`gUcIaNl-H&S~3L!UDkHV z<-tZH7J+?CBM}!**rnJsQ7o8Q-Ud;fl?C(T`O#D^4N7e(Q}%G$m-TdQ;`+`IjocAWtvC zXY_`Ifq!!-nv7bB(O==%KU~Sw3(%==08f2$B!(KtHE+%wm0v=5fy>J(zrw#(`BlOA z2}BdDW)y zTPpva|Df_8B~#}0Pb*0i6qd&l;RY*MP}d^tQiW}>rKhqDHpYyypro8XDg3s|@9;Co zbFuieC`vq)|IB|;_^&GejsLFlKlq=RYqhIEmEYy}RQ?ygj{<+xvHnr%J^r`KABY$K z!yl&1+pkDd`6Een{}msP>Fa@2y{fm7nHe{8N{s)p_~H|lKBiAp{*>uALuUpPa`;%$(mwW^+`7JB(5qmX49 z?o?#?7zamleOQl^6in@V4KlJ1QO3o>@B+uH zgy50v&UD7HT>E^bJCvM0Vq2d;Y4v(8RQ#YzU#D#Q%7s~Ps~xp+&K09qOMa9scKp1u z(1IfsV8pkYSy~R^1PHw@cI+Af3J!pRmIl#!XgI^+#$bQy5iWS#z+QvkYvYosm5?sc z(x91AYhl2KoVOtv3GRZaa>X;WcYd_4IR}`r{BKMiGlpIPZUJ+?A@qy}Xw9W$S3#lKojr}d_D1N#&pCPsR365eN z{db%@9y)(p!W$m!XR)+@ox*)dEM@}wTFXjQT{Wg=nBsxKQrlH&i6|-nV}*v1an2Vo zJ<*ZYXJ+!-u@Cvt>*CeVLh&IPufV~Ua%W**RoGc+@#c6eoG^8X^WK$0JIW)Z9v2SX z%xjg^FmBIH!7ztm__{^j!qGl5WCWgDZwpx{wo!kLH|rqDmg}!&_NCTPAiSTILh+@k zIvJvA4FK$MN4|l?7DJ$GxfYE@QRjwjWk(b^T)kNNJuZ$E;WKg2bI+u-Y9SK^4Ne{_ z_tWd0!@@K1l{u)}`3OV+xdhS8I}aSElYEGhB>=&B`|e zllgxw_1^omu}2rk?N_P-vp$B8Di5SC?E;6r?T{OsSd*PSS0^ifrpZUee?dwe`|Ov) zNv;sr)ujJ%l`*lv%8XU9RvZmlT<_TL8fbZ z-=hs~*5WH;hF%fYBk%!$KOm~NnaS6_&PabJBL(KNZyvpoE%x1bpa(|%F3d_Zb?tR4 zcsRUz>ccUylBX3%l};1r94a6p4+ZEvI-i)ng|{sFHs#2D1)g){^1|x3(?wQ)F+0YmAjPEz;Lz`~+^q!bqKBX42JJ{q*w+TnjP zw4=z2b|O{=X*eAR879#<%E!}Wx*RiwHcz^Ot|YRaL?*VLM5-+7N#t^2bO&995s_v; z`AI>)sHlSmuS7GXy6^!SgomMJ?tr_4a>_i?4J-3vd|<%aMZ@a~1KxnAxTvI)ayw|m zqm*}VA33K&$3k!}hO8wtnx;YW>6U+-%kACLaxS8)=^9Anq51TE`T@B3X*T^3R&rr& zA?>6eL9@}&^E$d7R1;|o-GC=A-H6q0th@ znc|x3%k>4k2XCh7L#aF9&GpUt=6;$`mL16Mpo#7qX+S_3GQES2s|_eJG^uu*k9T)J zA5c0dU(DjN%(9GzqHToOO*ELA0gVt`cqWafEi{F~FjRz&rxuvQppz(0YsjQclmOLM zI*Zz<9b@MKfR|fb{Fn>SEiivlHm#sJbSvFPK2bVt)(dDijD*m@s@v%f7-ujz-AQ*r zo*eMngB5P@2-8pKZkTlucD)BrUi6FTUfK&c-3LA;PSD|wh%2Vimg&tsq_7RQy zf`;Njq49i(CM#6_DGlu9lMd=kVyqh*ctr3*xWZFia)2f?96LN`iu-?IDyVZ6*X*aM z`>F6=%oHJ&+<>X#zEiFPRFXx&n!s{DO?whVrG1EGV*0(73}W=jFptEW7m+#`aT-KK z6v8wM5Ctb8Hdi4!Hz6*?sIzSe(*|s`@W$5De(=ZwmrZnl9)tl;g4?<<>!yb=uUcl7 zxrZ?)MjK47Llji#;HZB?Gzn(wWtK;vgKd@U;fSyR zVr()bo`wE+h;t%DSqcDMZbhIMV=DdB%1i?ghd;xUWRr@tH7bATF)QQAlSE&Z_4IQq zO7F9BB7X*knR-4>Po&+SWubFL7tKddl}VOau#*;|TUZU&@c6F5V6Px5MmrK-oKKBv3vB2%isZFTwM2 zh?sKm48);Ch5~=e+4Lkm1qC2kTG16rxBIYCoSl_+u|4uIMnvHva&;poDXF+PsL<2+ zWteE3z}=Oe0P! zv=JP?o^x6Uoi1QR*Fk4=(5Ai6CKq9K5v{ZOtEe^!#SJt9Z3nE~3Hu$uLHHmj9s&#> zrZSjj38H_Y7V)qNklO-?od?uh03%%l&s+lET!H7S=xKoV8M+n*yb0#Jm7WLn3zkP) z0M}X^F3;01tq{z|_>0)Bna0wu08SrvT0<|<%RoyRolLLLui=|ZK>I5F24mO2v#-)` zEwDe9#=v7%aHA+i9{imJ_FQDf*V16GAfK-r`}%)UKE`jkLQ52SU7-~U(h41-aTpUI zxHE}Jh}MCmc3==}-=H^>H9<4pB&J5?X!~jNeN^8;4OK-sdIvRT-9jGs9@n09eCJr= zz68-)GK@EFaq! zVzXkr&!j)6BQPISf22P_i9skx@~ZIzg6-Ax1S=xPlpkL>QgS~hKyL7(*$@IA5@ zN}h&8-hgxl4s_}L0Qq+G+j0JEPaCirU?chmX5XbD^e^P=_hFn5V7&j(3G^ZI^~ZnU z^eK3KMlqnvK;Mr3T>2dRx@j8=$8Nd|{gp^UV(c}r%OpCL{tmt#tR7GQ1a~j^x6*sq zLlWPPw6S+!NBg|nLI1Wue-ljJ#a9mWK%`9pE=`l^dc zP{7sAbze(^ic75Wt$la*y8!0d$WE<2=b=@RvYWiva47p|3=g0nt2BuRW}N)_X-nk8 z&Uaas$fy6oVsi42x5^|JAe#rv-54~<4$*7yJq$@G~PR{<3#cV;DJPR3wo z-iI*;W$_T9=b`jX3-d1IhI8l}h~432|Lo$4I7IpX08mQ<1d}e=8nbm)oB|DRZW$4_ z0RRAC0{{RxlL1>Jlf&5#65H4Rn%86N+c0z#vKz0e2(%4H+bR(O+m%yxmJE*!XguSDA;P;?6? z+8Ln`?T+AHbKb$Cond+uPwFx16w63Z=1erkVu=r|XE?}uP=?j9p3z}gSMg-R8uM+s ziqQ=USAS_do78r6Fm9NPCOmx*?A`}oJ^*&`%-ZLy8?2DH@|xd7e*jQR0|W{H00;;G z002P%9ZJ~Z76$+TTa*1-FMnxc8&?%QV@n!Y9>jK->nrHBoEX z!CP_C)*V|Dc@lY~jz){g;JbqVVi?~G>MHMe8 zI67Te)AN&N$+6AVvVUV1ECpKHvJ877ua`G1`%v{;lMR#=YZBYl_5^#yx+FmOP8k%Vcs35$mmy8)*0vXQItS7eGg#z8My_G6| zaOF!^%%XzX_Xswb*>rVWfkGd<_E??TTr-M(d_pA`f(Y}DzIG#{7yUwMw~dX$ zO=D`)F|3js_JHL^OJQ`BG-K#jf)~Sn-xJGlQj2rxLw`ab;a#-zwW8kDrtu=nK*bLf zL}+)MNplm@e|UiE7a|}z=E3PsrN+%%+H#DXQ2LW7 zjAC2pyr$q-gUl=FsqfB}Eyvt2_Wap9Q7GrLF{|Lj)>)`A(<+vom{%$zV*Af@m%?B0 zx`sQbXsC+2cLXl>cJ3L9j=D3mtJjS+XW!PmU*h+_!~4s_@xg-ydh9^s8+U82Gv2V!4=RiQ zBri}_R>AN)y7qpFNfUDlyFQ#g(32Y&TJr7?+nC4EUw9nG8g}dboxc&n-Zwzo8yW>C z|9?*_^4dh&ee%}G<;>xpAKJ^p(h$6M7Kjf@LkpFQS>2(zVEpR1FbZXTv`{?l?R3S{ z4KtHy)Yt1f3+r~_mNY=u(N;e4B%d`lXRnFL2Hfd?OR2oN+eMGZ(~WVZ=lfM)JYClF zr33c7vK<|~vcGb-N{+GN1@W?7V5*$0Lw|@Y;S|4;&hm?_8QpjQ=b+$&Ts2{k>ksW& zC;4L&q#WiRdm_h&xOzWlg>x`bh4PxKdVynvGth?s?!`waX`T{3iZRY&VB9zGFf~Ot zA_fx4J7}s~IYL_CcU6EiThQ-XI__!vmP8U-LSh1wzRmvB6L2m19e&c3RlsxgUE6ftz2mpv_+3_=ninGpuLKY^TA!+qx*ED`*dAtsD3E4 zgJa8y?qGMQiq7qtJ3R<-jMJ4tv?GBNjOql2u!&*UwM!o9nrpW)#qh9J@C+_87fn2M zlUnB(mJ_h}`kZ5ECK$>`7=j?}w144IN+D|UW%7ej7#9iEzAz=5+5Yc*e zaB?|7M!i+^bqrK+?{;PfvE}F~W~%>9*YYW*{5`Z+kKGu@akSFs4ko*w$bYAaE&M(} z|3m1-WqgeIt&Gnc=6Uus zCEpW1?z228HhUPS`!Yk4nty={eFPuc9_kA`Rl)sr^yBmx^f5^PGRiO(Ve!KW|UZ5EX++;F3SV7_?gcGl$X*(HB zx~mSk8_}V=7_MSuTe9buRW!wNa%<~-yO-k3n+J$LPS7Vv;z_1zg(5|y@44NRHR6-V z))GZ7N4ogG;TGvNrd-OD?jAshFa^jR*WQn8QQaknNQ?Rd8MExl!BHRGuGE-eI={u z>YQvARda~6hc#WZMi?eK-PQlqu-PkRdP7}{48AdAMP+CdjM>fgWz~ExFBgat&Kinb zSd>jooJ&lfaF^j?=9Q(IJ5Ftl1(eOVQGfWF~-p3(2gk*T7>noGrKCkkrM@3&nFfvQ`7T9m7Fej&nvLW z2d`}AMdB*(=*tquTCT)9>DxOxB0#dDtj)+9_NU-@!mxHORp1_L%B~d8+oV`h zVo5VZs=3D?Ef|}oqK<2#d|E1WdPJ^&f(YY65Fs>iJPl$T6C9I4OyLqo4;wr8Q$7ZOIT@6eh1K)C-pu5Tfa|(!BtNbY3niWfqq3=vRR!vNf z#mts$s)u7bPLMu*(@^cZSJ8<@;3!d@$%>*Di;;mORcUV(jnU<_>87O>64X_HqQed` zsGakw#n4ecF>=eRs$%WkEoX7e(cC%|+PkA>%{jDV`OvAhd8ezId)1=-I>X>Oxx!CU zFO9(emAd$8P|-%e{6=~luuw=0G@`uli1`NkZPICy&R*Js(by+=qOtf6ydUW7K{Krl zLdGUg!zK}6Q)nSx3&}_ymv9Atuqm2M#ACQl)*RIL@WV%YCi)Sc&+x^+pvHsmsGaTp zf$%Q)Qj8@2DBD5Z_AaC)p|^{`QY=oaVE3M|e%p3fxDpnCYz9qi7R@Y&2%ERvi8_Y% z-O1n<+Q|(;-6qOIK_?Nx9m44#t{?0-A{@0Lf!SGdI7d=}!|e>PgACM53Me*SW{U5d z;^-o9W>Zl+2ZD+rZs)k$#^}Li+DFN313kuCiF1SYn?4D51w_bCH&W<_c!i}fMDhUb z4&6MBn6sVad28Ev?jgDLU9#rIU~zWUZIcBw@7E&At}?O|2osR=-<9WJ3T8o zU}A$zCNuq`-97v1oNqs!Jvx8>`|Aq;b9f+Q2#Y7^k&zL>B1cY!ge4hSTn^$2u5x@N z7Rwwf0``Bg3>nurt_N^~W6owHmsWBlMDC8uk^28sva*DPdS| z*2=ndS1nh`63*8(wYs5NhFG_ZlAy~FSzF|ll9$?w3G;6wPUwfkItvoo_av6VZ?v>)if0YQ5X&wsH#qrMBNlt{82 z^sUE#hVL2rlh46@_#J_)G$Q?hS=W5xM}&7FmlCuL#tE-H-NisDd{X)ir%L`*@fkup z7<{>la7nz88j)Th-+AgBO3*zEPINOwHH%S}!x&pal3hlQU8Ux6#i76865D^HuVVs} zL|e?JXy!u@n}{PxK1WHO6sD7mnGS z6FBZd_5yQilutW>Ip!S&#cTl=oG*0I@o@6h=)*vUw8lfnwU2f&?}THW(W5!LU1S1~xNsW;O}F+gfew z`%!8Dffkf%X$5N%QlM3jwXKJLqv!PWPw3&O-|x)qZjxQt#2@|T&i8)r_jSL|yZhDG zfBhSPPE3c;1htHo{UL-AlMoLf0Dp}!kMWY=Wr!D@mjg_HP(m_@VF^YEx55lT#t1`? z2Vs@NhQmau5E?Mb+c61;LRf_-gLo>07CaroGkBIyo(o|&e#(SD3*zUDdA=ME;1|5S zAmK$m{v{v(svN(@^AcWS)|Vx`BH>j5*&K0n)980B-AG7yoiICRVQAZH7=N18lT>Y6 zvjy5Crj=+o3&i;-ALY)5r832zX$XF@+C1lkJQI*Xo79aWPlzGX$^fI6zSryM=m9?@-w&dU1r zgrPbqiv-urJKpT=ld6$u_kZu*#p0YH-LC7n{>?oLn0m8r=uVfwmyLzV7a4`Zo-gd^ z#W}NSKp@a##>rdDm?WAS-mh7^)%{7rR7T90njBCqo%cR6z}^z5-eH(yhG*5BXl^QO zKz>&8ZOAmbtwd^AGaP|?8*{_ewAfS$su_8ex#9$t5Mk_KpE}~-ZhtAqLbaTzHtvuq z0v(Nu*10sHIonh_dz0eErU%{25$8x!V=tAsDNL1MIzyVZ(=R=F#0_V9o0DKU(e|@K z!kZG_lJK^`|Gp1dS(tjgo4G~f9lWgPrIVKpylmv? zwoU1T*)kr}{0!LOAGapyT=6=z(ok6;+&jJd{}&$H&PvmT?)wG;$l3L5Z}8 z70I;-S(lQ_3KJE^x-C0NWRYPHT6fl^+1x3tOUbPo>#{bCL1SFn&ysZe$QrTZt!+Q^ z{&RlM^M22HpU?Yw|2XHo&v|+yWUNhTns>@7q`Pi1{f_(*_0p`g|8Wy*Ug5|UivdYI zhB0$VgZV0jtkGMQ{%eb#UtW3axil?mUal0SBYF9V$Rq#Gv<1CL6_o}4U-TSQi0mlP zij9aVxjdcidj9!n&W9GK9PG%Eo#G0sp*25Rx{IL>+f;sSAwAt}j+QR*sw~|soY21M zY-@cx_V<*QoI^unvf5kP+k}OYI1V-Ra>is=PgYp5!uFwU6}B{AMYT=mt((t0x*KGNt-F|z+;eGkP5Egd zNvG0XqWWp|S4(-u->beAO%-%1_I1v=;@9}9B7jh+?B;fG=*DmLQWn*(H%wmM#pUnI zIqc95ZOPjDPC;9C?OjPL@7qaBvo1bsppcSvGy}Wb9d01|XGq884+G-M1E!)AIr4wU zm>BIi8_8Qpn1*{AJMMq7u+ih971-b%+&NbMXrjcYNG6qUXVqhhko(t&L zrUiz##fp=STWzl%u3&DhE9E3}c98@;C9hjM?ZN_MWT4ST|tzt$hV6xV^S*r1dp{KwBcNSi)z2!p-Cz=bF7Nx3 zp`^EB-^ln%hM;lMO~nJ$-=v0l#sjj(?KLw(PeU%>< zkhg5qq+YK&7){~3S3EH;Nu{h>#d@7$t792h!=lKC`4Oilo=echyyW6_^xN{rzmW`0 z>%QG)z??BoYS(9NO-j3%9nX3)Hk(-$oYG`&$&7wpu!EMP8{dPM#lu9j`YzBPIcnZ1 zVLQDx8tB8mJF_FC-gnDTeLKm1p0!~Smqba`s(aA02wOEpqnrxU!YDtQL_H|^)h$jj zlI^-B_u&WGnyTO1Qx&8ly-(y_7uu#Wt24dks`jcsTeva!OM?wQGn8x}e5$m{A2+|% znc20J-!N1@NGM7#IsMOGEbg7C%|POP554tG0m6cE>s7f}IcnR|Hm%xMk$LV`wsY3a za*Xnf6>ECWmJr?W2TK{LJvB6y6xX{Hu%$R;cwY4SeSzk|{GVmN@b@fuDl3ITg=nHs z>fH6JYBOwHmc$fxFy2Q;yosFG8>R+Qj5t~hO5#fMPrY5~+Ky3>ZBBOG3wb_Dy#uw&X4_>-a3%uxWz z&~e5K?2qI&^3Xeq09R&+U?diXHR7TEtU0(4hsc>3Mc6ot1O4%cxj85Xr>D?R;292x zUWdxhjPX1>5I{`Ke5h-3K2qlVTxp&dCIt*TP2k2qAHEaSoPtj0v2?rKO;OOX4#Ej;et$)Dm RC3`**IJaad=rFcB?7uiSG#~%~ diff --git a/gradlew b/gradlew index 2fe81a7d..fbd7c515 100755 --- a/gradlew +++ b/gradlew @@ -82,6 +82,7 @@ esac CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + # Determine the Java command to use to start the JVM. if [ -n "$JAVA_HOME" ] ; then if [ -x "$JAVA_HOME/jre/sh/java" ] ; then @@ -129,6 +130,7 @@ fi if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then APP_HOME=`cygpath --path --mixed "$APP_HOME"` CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + JAVACMD=`cygpath --unix "$JAVACMD"` # We build the pattern for arguments to be converted via cygpath diff --git a/gradlew.bat b/gradlew.bat index 24467a14..a9f778a7 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -29,6 +29,9 @@ if "%DIRNAME%" == "" set DIRNAME=. set APP_BASE_NAME=%~n0 set APP_HOME=%DIRNAME% +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" @@ -81,6 +84,7 @@ set CMD_LINE_ARGS=%* set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + @rem Execute Gradle "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% From 1517877bdb3c36c33a44f7a9931ad37074be04fc Mon Sep 17 00:00:00 2001 From: Querz Date: Mon, 8 Jun 2020 10:51:37 +0200 Subject: [PATCH 28/51] ignore .iml files --- .gitignore | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index bf59b54c..7a2ac475 100644 --- a/.gitignore +++ b/.gitignore @@ -77,10 +77,13 @@ local.properties # IDEA folder .idea/ +# .iml files +*.iml + # ignore javadoc for now doc/ # ignore out out/ -*.patch \ No newline at end of file +*.patch From 5fd7dcdcd05f429334bf98c09efdcb1fac616446 Mon Sep 17 00:00:00 2001 From: Querz Date: Mon, 8 Jun 2020 11:36:42 +0200 Subject: [PATCH 29/51] formatting and cleanup --- src/main/java/net/querz/mca/Chunk.java | 67 ++++++++++---------- src/main/java/net/querz/mca/LoadFlags.java | 33 +++++----- src/main/java/net/querz/mca/MCAUtil.java | 9 +-- src/main/java/net/querz/mca/Section.java | 8 +-- src/test/java/net/querz/NBTTestCase.java | 1 + src/test/java/net/querz/mca/MCAFileTest.java | 9 ++- 6 files changed, 60 insertions(+), 67 deletions(-) diff --git a/src/main/java/net/querz/mca/Chunk.java b/src/main/java/net/querz/mca/Chunk.java index 59c45b07..d305df04 100644 --- a/src/main/java/net/querz/mca/Chunk.java +++ b/src/main/java/net/querz/mca/Chunk.java @@ -1,6 +1,5 @@ package net.querz.mca; -import java.rmi.UnexpectedException; import net.querz.nbt.tag.CompoundTag; import net.querz.nbt.tag.ListTag; import net.querz.nbt.io.NamedTag; @@ -64,45 +63,45 @@ private void initReferences(long loadFlags) { if ((level = data.getCompoundTag("Level")) == null) { throw new IllegalArgumentException("data does not contain \"Level\" tag"); } - this.dataVersion = data.getInt("DataVersion"); - this.inhabitedTime = level.getLong("InhabitedTime"); - this.lastUpdate = level.getLong("LastUpdate"); - if((loadFlags & BIOMES) != 0) { - this.biomes = level.getIntArray("Biomes"); + dataVersion = data.getInt("DataVersion"); + inhabitedTime = level.getLong("InhabitedTime"); + lastUpdate = level.getLong("LastUpdate"); + if ((loadFlags & BIOMES) != 0) { + biomes = level.getIntArray("Biomes"); } - if((loadFlags & HEIGHTMAPS) != 0) { - this.heightMaps = level.getCompoundTag("Heightmaps"); + if ((loadFlags & HEIGHTMAPS) != 0) { + heightMaps = level.getCompoundTag("Heightmaps"); } - if((loadFlags & CARVINGMASKS) != 0) { - this.carvingMasks = level.getCompoundTag("CarvingMasks"); + if ((loadFlags & CARVING_MASKS) != 0) { + carvingMasks = level.getCompoundTag("CarvingMasks"); } - if((loadFlags & ENTITIES) != 0) { - this.entities = level.containsKey("Entities") ? level.getListTag("Entities").asCompoundTagList() : null; + if ((loadFlags & ENTITIES) != 0) { + entities = level.containsKey("Entities") ? level.getListTag("Entities").asCompoundTagList() : null; } - if((loadFlags & TILE_ENTITIES) != 0) { - this.tileEntities = level.containsKey("TileEntities") ? level.getListTag("TileEntities").asCompoundTagList() : null; + if ((loadFlags & TILE_ENTITIES) != 0) { + tileEntities = level.containsKey("TileEntities") ? level.getListTag("TileEntities").asCompoundTagList() : null; } - if((loadFlags & TILE_TICKS) != 0) { - this.tileTicks = level.containsKey("TileTicks") ? level.getListTag("TileTicks").asCompoundTagList() : null; + if ((loadFlags & TILE_TICKS) != 0) { + tileTicks = level.containsKey("TileTicks") ? level.getListTag("TileTicks").asCompoundTagList() : null; } - if((loadFlags & LIQUID_TICKS) != 0) { - this.liquidTicks = level.containsKey("LiquidTicks") ? level.getListTag("LiquidTicks").asCompoundTagList() : null; + if ((loadFlags & LIQUID_TICKS) != 0) { + liquidTicks = level.containsKey("LiquidTicks") ? level.getListTag("LiquidTicks").asCompoundTagList() : null; } - if((loadFlags & LIGHTS) != 0) { - this.lights = level.containsKey("Lights") ? level.getListTag("Lights").asListTagList() : null; + if ((loadFlags & LIGHTS) != 0) { + lights = level.containsKey("Lights") ? level.getListTag("Lights").asListTagList() : null; } - if((loadFlags & LIQUIDS_TO_BE_TICKED) != 0) { - this.liquidsToBeTicked = level.containsKey("LiquidsToBeTicked") ? level.getListTag("LiquidsToBeTicked").asListTagList() : null; + if ((loadFlags & LIQUIDS_TO_BE_TICKED) != 0) { + liquidsToBeTicked = level.containsKey("LiquidsToBeTicked") ? level.getListTag("LiquidsToBeTicked").asListTagList() : null; } - if((loadFlags & TO_BE_TICKED) != 0) { - this.toBeTicked = level.containsKey("ToBeTicked") ? level.getListTag("ToBeTicked").asListTagList() : null; + if ((loadFlags & TO_BE_TICKED) != 0) { + toBeTicked = level.containsKey("ToBeTicked") ? level.getListTag("ToBeTicked").asListTagList() : null; } - if((loadFlags & POST_PROCESSING) != 0) { - this.postProcessing = level.containsKey("PostProcessing") ? level.getListTag("PostProcessing").asListTagList() : null; + if ((loadFlags & POST_PROCESSING) != 0) { + postProcessing = level.containsKey("PostProcessing") ? level.getListTag("PostProcessing").asListTagList() : null; } - this.status = level.getString("Status"); - if((loadFlags & STRUCTURES) != 0) { - this.structures = level.getCompoundTag("Structures"); + status = level.getString("Status"); + if ((loadFlags & STRUCTURES) != 0) { + structures = level.getCompoundTag("Structures"); } if ((loadFlags & (BLOCK_LIGHTS|BLOCK_STATES|SKY_LIGHT)) != 0 && level.containsKey("Sections")) { for (CompoundTag section : level.getListTag("Sections").asCompoundTagList()) { @@ -114,14 +113,14 @@ private void initReferences(long loadFlags) { if (newSection.isEmpty()) { continue; } - this.sections[sectionIndex] = newSection; + sections[sectionIndex] = newSection; } } // If we haven't requested the full set of data we can drop the underlying raw data to let the GC handle it. - if(loadFlags != ALL_DATA) { - this.data = null; - this.partial = true; + if (loadFlags != ALL_DATA) { + data = null; + partial = true; } else { partial = false; } @@ -137,7 +136,7 @@ private void initReferences(long loadFlags) { * @throws IOException When something went wrong during writing. */ public int serialize(RandomAccessFile raf, int xPos, int zPos) throws IOException { - if(this.partial) { + if (partial) { throw new UnsupportedOperationException("Partially loaded chunks cannot be serialized"); } ByteArrayOutputStream baos = new ByteArrayOutputStream(4096); diff --git a/src/main/java/net/querz/mca/LoadFlags.java b/src/main/java/net/querz/mca/LoadFlags.java index b9db8460..e4122898 100644 --- a/src/main/java/net/querz/mca/LoadFlags.java +++ b/src/main/java/net/querz/mca/LoadFlags.java @@ -1,23 +1,24 @@ package net.querz.mca; public class LoadFlags { - public static long BIOMES = 0x0001; - public static long HEIGHTMAPS = 0x0002; - public static long CARVINGMASKS = 0x0004; - public static long ENTITIES = 0x0008; - public static long TILE_ENTITIES = 0x0010; - public static long TILE_TICKS = 0x0040; - public static long LIQUID_TICKS = 0x0080; - public static long TO_BE_TICKED = 0x0100; - public static long POST_PROCESSING = 0x0200; - public static long STRUCTURES = 0x0400; - public static long BLOCK_LIGHTS = 0x0800; - public static long BLOCK_STATES = 0x1000; - public static long SKY_LIGHT = 0x2000; - public static long LIGHTS = 0x4000; - public static long LIQUIDS_TO_BE_TICKED = 0x8000; - public static long ALL_DATA = 0xffffffffffffffffL; + public static long BIOMES = 0x0001; + public static long HEIGHTMAPS = 0x0002; + public static long CARVING_MASKS = 0x0004; + public static long ENTITIES = 0x0008; + public static long TILE_ENTITIES = 0x0010; + public static long TILE_TICKS = 0x0040; + public static long LIQUID_TICKS = 0x0080; + public static long TO_BE_TICKED = 0x0100; + public static long POST_PROCESSING = 0x0200; + public static long STRUCTURES = 0x0400; + public static long BLOCK_LIGHTS = 0x0800; + public static long BLOCK_STATES = 0x1000; + public static long SKY_LIGHT = 0x2000; + public static long LIGHTS = 0x4000; + public static long LIQUIDS_TO_BE_TICKED = 0x8000; + + public static long ALL_DATA = 0xffffffffffffffffL; } diff --git a/src/main/java/net/querz/mca/MCAUtil.java b/src/main/java/net/querz/mca/MCAUtil.java index b7a28ab3..f5ddecc5 100644 --- a/src/main/java/net/querz/mca/MCAUtil.java +++ b/src/main/java/net/querz/mca/MCAUtil.java @@ -23,7 +23,7 @@ private MCAUtil() {} * @throws IOException if something during deserialization goes wrong. * */ public static MCAFile read(String file) throws IOException { - return read(new File(file)); + return read(new File(file), LoadFlags.ALL_DATA); } /** @@ -33,14 +33,9 @@ public static MCAFile read(String file) throws IOException { * @throws IOException if something during deserialization goes wrong. * */ public static MCAFile read(File file) throws IOException { - MCAFile mcaFile = newMCAFile(file); - try (RandomAccessFile raf = new RandomAccessFile(file, "r")) { - mcaFile.deserialize(raf); - return mcaFile; - } + return read(file, LoadFlags.ALL_DATA); } - /** * @see MCAUtil#read(File) * @param file The file to read the data from. diff --git a/src/main/java/net/querz/mca/Section.java b/src/main/java/net/querz/mca/Section.java index e6b25327..312035a3 100644 --- a/src/main/java/net/querz/mca/Section.java +++ b/src/main/java/net/querz/mca/Section.java @@ -1,12 +1,10 @@ package net.querz.mca; import static net.querz.mca.LoadFlags.*; - import net.querz.nbt.tag.ByteArrayTag; import net.querz.nbt.tag.CompoundTag; import net.querz.nbt.tag.ListTag; import net.querz.nbt.tag.LongArrayTag; - import java.util.ArrayList; import java.util.HashMap; import java.util.List; @@ -43,13 +41,13 @@ public Section(CompoundTag sectionRoot, int dataVersion, long loadFlags) { LongArrayTag blockStates = sectionRoot.getLongArrayTag("BlockStates"); ByteArrayTag skyLight = sectionRoot.getByteArrayTag("SkyLight"); - if((loadFlags & BLOCK_LIGHTS) != 0) { + if ((loadFlags & BLOCK_LIGHTS) != 0) { this.blockLight = blockLight != null ? blockLight.getValue() : null; } - if((loadFlags & BLOCK_STATES) != 0) { + if ((loadFlags & BLOCK_STATES) != 0) { this.blockStates = blockStates != null ? blockStates.getValue() : null; } - if((loadFlags & SKY_LIGHT) != 0) { + if ((loadFlags & SKY_LIGHT) != 0) { this.skyLight = skyLight != null ? skyLight.getValue() : null; } } diff --git a/src/test/java/net/querz/NBTTestCase.java b/src/test/java/net/querz/NBTTestCase.java index d1935be5..7d412a59 100644 --- a/src/test/java/net/querz/NBTTestCase.java +++ b/src/test/java/net/querz/NBTTestCase.java @@ -252,6 +252,7 @@ protected void cleanupTmpDir() { file.delete(); } } + tmpDir.delete(); } protected String calculateFileMD5(File file) { diff --git a/src/test/java/net/querz/mca/MCAFileTest.java b/src/test/java/net/querz/mca/MCAFileTest.java index 2ebb4f11..38e09f49 100644 --- a/src/test/java/net/querz/mca/MCAFileTest.java +++ b/src/test/java/net/querz/mca/MCAFileTest.java @@ -356,7 +356,7 @@ private void assertPartialChunk(Chunk c, long loadFlags) { assertLoadFLag(c.getBiomes(), loadFlags, BIOMES); assertLoadFLag(c.getHeightMaps(), loadFlags, HEIGHTMAPS); assertLoadFLag(c.getEntities(), loadFlags, ENTITIES); - assertLoadFLag(c.getCarvingMasks(), loadFlags, CARVINGMASKS); + assertLoadFLag(c.getCarvingMasks(), loadFlags, CARVING_MASKS); assertLoadFLag(c.getLights(), loadFlags, LIGHTS); assertLoadFLag(c.getPostProcessing(), loadFlags, POST_PROCESSING); assertLoadFLag(c.getLiquidTicks(), loadFlags, LIQUID_TICKS); @@ -365,7 +365,7 @@ private void assertPartialChunk(Chunk c, long loadFlags) { assertLoadFLag(c.getTileEntities(), loadFlags, TILE_ENTITIES); assertLoadFLag(c.getToBeTicked(), loadFlags, TO_BE_TICKED); assertLoadFLag(c.getSection(0), loadFlags, BLOCK_LIGHTS|BLOCK_STATES|SKY_LIGHT); - if((loadFlags & (BLOCK_LIGHTS|BLOCK_STATES|SKY_LIGHT)) != 0) { + if ((loadFlags & (BLOCK_LIGHTS|BLOCK_STATES|SKY_LIGHT)) != 0) { Section s = c.getSection(0); assertNotNull(String.format("Section is null. Flags=%08x", loadFlags), s); assertLoadFLag(s.getBlockStates(), loadFlags, BLOCK_STATES); @@ -379,7 +379,7 @@ public void testPartialLoad() { BIOMES, HEIGHTMAPS, ENTITIES, - CARVINGMASKS, + CARVING_MASKS, LIGHTS, POST_PROCESSING, LIQUID_TICKS, @@ -408,13 +408,12 @@ public void testPartialLoad() { File tmp = this.getNewTmpFile("r.2.2.mca"); assertThrowsNoException(() -> MCAUtil.write(f, tmp)); - for(long flag : flags) { + for (long flag : flags) { MCAFile mcaFile = assertThrowsNoException(() -> MCAUtil.read(tmp, flag)); c = mcaFile.getChunk(0, 0); assertPartialChunk(c, flag); assertThrowsException(() -> MCAUtil.write(mcaFile, getNewTmpFile("r.12.34.mca")), UnsupportedOperationException.class); } - tmp.delete(); } public void test1_15GetBiomeAt() throws IOException { From 62de6322e9c2e8811d9f7b5e2c868e7366b627f8 Mon Sep 17 00:00:00 2001 From: Querz Date: Wed, 17 Jun 2020 14:53:11 +0200 Subject: [PATCH 30/51] update version to 5.5 --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index fe225ecc..4d457e77 100644 --- a/build.gradle +++ b/build.gradle @@ -10,7 +10,7 @@ apply plugin: 'jacoco' group = 'net.querz.nbt' archivesBaseName = 'nbt' -version = '5.4' +version = '5.5' sourceCompatibility = '1.8' targetCompatibility = '1.8' compileJava.options.encoding = 'UTF-8' From 8cdd197983b6abd60d6813a8177c657ed724ad22 Mon Sep 17 00:00:00 2001 From: Querz Date: Wed, 15 Jul 2020 17:08:31 +0200 Subject: [PATCH 31/51] add support for little endian input and output --- .../nbt/io/LittleEndianNBTInputStream.java | 240 +++++++++++++++++ .../nbt/io/LittleEndianNBTOutputStream.java | 244 ++++++++++++++++++ .../net/querz/nbt/io/NBTDeserializer.java | 20 +- src/main/java/net/querz/nbt/io/NBTInput.java | 11 + .../java/net/querz/nbt/io/NBTInputStream.java | 2 +- src/main/java/net/querz/nbt/io/NBTOutput.java | 13 + .../net/querz/nbt/io/NBTOutputStream.java | 2 +- .../java/net/querz/nbt/io/NBTSerializer.java | 20 +- src/main/java/net/querz/nbt/io/NBTUtil.java | 54 ++++ 9 files changed, 596 insertions(+), 10 deletions(-) create mode 100644 src/main/java/net/querz/nbt/io/LittleEndianNBTInputStream.java create mode 100644 src/main/java/net/querz/nbt/io/LittleEndianNBTOutputStream.java create mode 100644 src/main/java/net/querz/nbt/io/NBTInput.java create mode 100644 src/main/java/net/querz/nbt/io/NBTOutput.java diff --git a/src/main/java/net/querz/nbt/io/LittleEndianNBTInputStream.java b/src/main/java/net/querz/nbt/io/LittleEndianNBTInputStream.java new file mode 100644 index 00000000..7c7d8ed2 --- /dev/null +++ b/src/main/java/net/querz/nbt/io/LittleEndianNBTInputStream.java @@ -0,0 +1,240 @@ +package net.querz.nbt.io; + +import net.querz.io.ExceptionBiFunction; +import net.querz.io.MaxDepthIO; +import net.querz.nbt.tag.ByteArrayTag; +import net.querz.nbt.tag.ByteTag; +import net.querz.nbt.tag.CompoundTag; +import net.querz.nbt.tag.DoubleTag; +import net.querz.nbt.tag.EndTag; +import net.querz.nbt.tag.FloatTag; +import net.querz.nbt.tag.IntArrayTag; +import net.querz.nbt.tag.IntTag; +import net.querz.nbt.tag.ListTag; +import net.querz.nbt.tag.LongArrayTag; +import net.querz.nbt.tag.LongTag; +import net.querz.nbt.tag.ShortTag; +import net.querz.nbt.tag.StringTag; +import net.querz.nbt.tag.Tag; +import java.io.Closeable; +import java.io.DataInput; +import java.io.DataInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; +import java.util.HashMap; +import java.util.Map; + +public class LittleEndianNBTInputStream implements DataInput, NBTInput, MaxDepthIO, Closeable { + + private final DataInputStream input; + + private static Map, IOException>> readers = new HashMap<>(); + private static Map> idClassMapping = new HashMap<>(); + + static { + put(EndTag.ID, (i, d) -> EndTag.INSTANCE, EndTag.class); + put(ByteTag.ID, (i, d) -> readByte(i), ByteTag.class); + put(ShortTag.ID, (i, d) -> readShort(i), ShortTag.class); + put(IntTag.ID, (i, d) -> readInt(i), IntTag.class); + put(LongTag.ID, (i, d) -> readLong(i), LongTag.class); + put(FloatTag.ID, (i, d) -> readFloat(i), FloatTag.class); + put(DoubleTag.ID, (i, d) -> readDouble(i), DoubleTag.class); + put(ByteArrayTag.ID, (i, d) -> readByteArray(i), ByteArrayTag.class); + put(StringTag.ID, (i, d) -> readString(i), StringTag.class); + put(ListTag.ID, LittleEndianNBTInputStream::readListTag, ListTag.class); + put(CompoundTag.ID, LittleEndianNBTInputStream::readCompound, CompoundTag.class); + put(IntArrayTag.ID, (i, d) -> readIntArray(i), IntArrayTag.class); + put(LongArrayTag.ID, (i, d) -> readLongArray(i), LongArrayTag.class); + } + + private static void put(byte id, ExceptionBiFunction, IOException> reader, Class clazz) { + readers.put(id, reader); + idClassMapping.put(id, clazz); + } + + public LittleEndianNBTInputStream(InputStream in) { + input = new DataInputStream(in); + } + + public LittleEndianNBTInputStream(DataInputStream in) { + input = in; + } + + public NamedTag readTag(int maxDepth) throws IOException { + byte id = readByte(); + return new NamedTag(readUTF(), readTag(id, maxDepth)); + } + + public Tag readRawTag(int maxDepth) throws IOException { + byte id = readByte(); + return readTag(id, maxDepth); + } + + private Tag readTag(byte type, int maxDepth) throws IOException { + ExceptionBiFunction, IOException> f; + if ((f = readers.get(type)) == null) { + throw new IOException("invalid tag id \"" + type + "\""); + } + return f.accept(this, maxDepth); + } + + private static ByteTag readByte(LittleEndianNBTInputStream in) throws IOException { + return new ByteTag(in.readByte()); + } + + private static ShortTag readShort(LittleEndianNBTInputStream in) throws IOException { + return new ShortTag(in.readShort()); + } + + private static IntTag readInt(LittleEndianNBTInputStream in) throws IOException { + return new IntTag(in.readInt()); + } + + private static LongTag readLong(LittleEndianNBTInputStream in) throws IOException { + return new LongTag(in.readLong()); + } + + private static FloatTag readFloat(LittleEndianNBTInputStream in) throws IOException { + return new FloatTag(in.readFloat()); + } + + private static DoubleTag readDouble(LittleEndianNBTInputStream in) throws IOException { + return new DoubleTag(in.readDouble()); + } + + private static StringTag readString(LittleEndianNBTInputStream in) throws IOException { + return new StringTag(in.readUTF()); + } + + private static ByteArrayTag readByteArray(LittleEndianNBTInputStream in) throws IOException { + ByteArrayTag bat = new ByteArrayTag(new byte[in.readInt()]); + in.readFully(bat.getValue()); + return bat; + } + + private static IntArrayTag readIntArray(LittleEndianNBTInputStream in) throws IOException { + int l = in.readInt(); + int[] data = new int[l]; + IntArrayTag iat = new IntArrayTag(data); + for (int i = 0; i < l; i++) { + data[i] = in.readInt(); + } + return iat; + } + + private static LongArrayTag readLongArray(LittleEndianNBTInputStream in) throws IOException { + int l = in.readInt(); + long[] data = new long[l]; + LongArrayTag iat = new LongArrayTag(data); + for (int i = 0; i < l; i++) { + data[i] = in.readLong(); + } + return iat; + } + + private static ListTag readListTag(LittleEndianNBTInputStream in, int maxDepth) throws IOException { + byte listType = in.readByte(); + ListTag list = ListTag.createUnchecked(idClassMapping.get(listType)); + int length = in.readInt(); + if (length < 0) { + length = 0; + } + for (int i = 0; i < length; i++) { + list.addUnchecked(in.readTag(listType, in.decrementMaxDepth(maxDepth))); + } + return list; + } + + private static CompoundTag readCompound(LittleEndianNBTInputStream in, int maxDepth) throws IOException { + CompoundTag comp = new CompoundTag(); + for (int id = in.readByte() & 0xFF; id != 0; id = in.readByte() & 0xFF) { + String key = in.readUTF(); + Tag element = in.readTag((byte) id, in.decrementMaxDepth(maxDepth)); + comp.put(key, element); + } + return comp; + } + + @Override + public void readFully(byte[] b) throws IOException { + input.readFully(b); + } + + @Override + public void readFully(byte[] b, int off, int len) throws IOException { + input.readFully(b, off, len); + } + + @Override + public int skipBytes(int n) throws IOException { + return input.skipBytes(n); + } + + @Override + public boolean readBoolean() throws IOException { + return input.readBoolean(); + } + + @Override + public byte readByte() throws IOException { + return input.readByte(); + } + + @Override + public int readUnsignedByte() throws IOException { + return input.readUnsignedByte(); + } + + @Override + public short readShort() throws IOException { + return Short.reverseBytes(input.readShort()); + } + + public int readUnsignedShort() throws IOException { + return Short.toUnsignedInt(Short.reverseBytes(input.readShort())); + } + + @Override + public char readChar() throws IOException { + return Character.reverseBytes(input.readChar()); + } + + @Override + public int readInt() throws IOException { + return Integer.reverseBytes(input.readInt()); + } + + @Override + public long readLong() throws IOException { + return Long.reverseBytes(input.readLong()); + } + + @Override + public float readFloat() throws IOException { + return Float.intBitsToFloat(Integer.reverseBytes(input.readInt())); + } + + @Override + public double readDouble() throws IOException { + return Double.longBitsToDouble(Long.reverseBytes(input.readLong())); + } + + @Override + @Deprecated + public String readLine() throws IOException { + return input.readLine(); + } + + @Override + public void close() throws IOException { + input.close(); + } + + @Override + public String readUTF() throws IOException { + byte[] bytes = new byte[readUnsignedShort()]; + readFully(bytes); + return new String(bytes, StandardCharsets.UTF_8); + } +} diff --git a/src/main/java/net/querz/nbt/io/LittleEndianNBTOutputStream.java b/src/main/java/net/querz/nbt/io/LittleEndianNBTOutputStream.java new file mode 100644 index 00000000..9cda01b2 --- /dev/null +++ b/src/main/java/net/querz/nbt/io/LittleEndianNBTOutputStream.java @@ -0,0 +1,244 @@ +package net.querz.nbt.io; + +import net.querz.io.ExceptionTriConsumer; +import net.querz.io.MaxDepthIO; +import net.querz.nbt.tag.ByteArrayTag; +import net.querz.nbt.tag.ByteTag; +import net.querz.nbt.tag.CompoundTag; +import net.querz.nbt.tag.DoubleTag; +import net.querz.nbt.tag.EndTag; +import net.querz.nbt.tag.FloatTag; +import net.querz.nbt.tag.IntArrayTag; +import net.querz.nbt.tag.IntTag; +import net.querz.nbt.tag.ListTag; +import net.querz.nbt.tag.LongArrayTag; +import net.querz.nbt.tag.LongTag; +import net.querz.nbt.tag.ShortTag; +import net.querz.nbt.tag.StringTag; +import net.querz.nbt.tag.Tag; +import java.io.Closeable; +import java.io.DataOutput; +import java.io.DataOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.nio.charset.StandardCharsets; +import java.util.HashMap; +import java.util.Map; + +public class LittleEndianNBTOutputStream implements DataOutput, NBTOutput, MaxDepthIO, Closeable { + + private final DataOutputStream output; + + private static Map, Integer, IOException>> writers = new HashMap<>(); + private static Map, Byte> classIdMapping = new HashMap<>(); + + static { + put(EndTag.ID, (o, t, d) -> {}, EndTag.class); + put(ByteTag.ID, (o, t, d) -> writeByte(o, t), ByteTag.class); + put(ShortTag.ID, (o, t, d) -> writeShort(o, t), ShortTag.class); + put(IntTag.ID, (o, t, d) -> writeInt(o, t), IntTag.class); + put(LongTag.ID, (o, t, d) -> writeLong(o, t), LongTag.class); + put(FloatTag.ID, (o, t, d) -> writeFloat(o, t), FloatTag.class); + put(DoubleTag.ID, (o, t, d) -> writeDouble(o, t), DoubleTag.class); + put(ByteArrayTag.ID, (o, t, d) -> writeByteArray(o, t), ByteArrayTag.class); + put(StringTag.ID, (o, t, d) -> writeString(o, t), StringTag.class); + put(ListTag.ID, LittleEndianNBTOutputStream::writeList, ListTag.class); + put(CompoundTag.ID, LittleEndianNBTOutputStream::writeCompound, CompoundTag.class); + put(IntArrayTag.ID, (o, t, d) -> writeIntArray(o, t), IntArrayTag.class); + put(LongArrayTag.ID, (o, t, d) -> writeLongArray(o, t), LongArrayTag.class); + } + + private static void put(byte id, ExceptionTriConsumer, Integer, IOException> f, Class clazz) { + writers.put(id, f); + classIdMapping.put(clazz, id); + } + + public LittleEndianNBTOutputStream(OutputStream out) { + output = new DataOutputStream(out); + } + + public LittleEndianNBTOutputStream(DataOutputStream out) { + output = out; + } + + public void writeTag(NamedTag tag, int maxDepth) throws IOException { + writeByte(tag.getTag().getID()); + if (tag.getTag().getID() != 0) { + writeUTF(tag.getName() == null ? "" : tag.getName()); + } + writeRawTag(tag.getTag(), maxDepth); + } + + public void writeTag(Tag tag, int maxDepth) throws IOException { + writeByte(tag.getID()); + if (tag.getID() != 0) { + writeUTF(""); + } + writeRawTag(tag, maxDepth); + } + + public void writeRawTag(Tag tag, int maxDepth) throws IOException { + ExceptionTriConsumer, Integer, IOException> f; + if ((f = writers.get(tag.getID())) == null) { + throw new IOException("invalid tag \"" + tag.getID() + "\""); + } + f.accept(this, tag, maxDepth); + } + + static byte idFromClass(Class clazz) { + Byte id = classIdMapping.get(clazz); + if (id == null) { + throw new IllegalArgumentException("unknown Tag class " + clazz.getName()); + } + return id; + } + + private static void writeByte(LittleEndianNBTOutputStream out, Tag tag) throws IOException { + out.writeByte(((ByteTag) tag).asByte()); + } + + private static void writeShort(LittleEndianNBTOutputStream out, Tag tag) throws IOException { + out.writeShort(((ShortTag) tag).asShort()); + } + + private static void writeInt(LittleEndianNBTOutputStream out, Tag tag) throws IOException { + out.writeInt(((IntTag) tag).asInt()); + } + + private static void writeLong(LittleEndianNBTOutputStream out, Tag tag) throws IOException { + out.writeLong(((LongTag) tag).asLong()); + } + + private static void writeFloat(LittleEndianNBTOutputStream out, Tag tag) throws IOException { + out.writeFloat(((FloatTag) tag).asFloat()); + } + + private static void writeDouble(LittleEndianNBTOutputStream out, Tag tag) throws IOException { + out.writeDouble(((DoubleTag) tag).asDouble()); + } + + private static void writeString(LittleEndianNBTOutputStream out, Tag tag) throws IOException { + out.writeUTF(((StringTag) tag).getValue()); + } + + private static void writeByteArray(LittleEndianNBTOutputStream out, Tag tag) throws IOException { + out.writeInt(((ByteArrayTag) tag).length()); + out.write(((ByteArrayTag) tag).getValue()); + } + + private static void writeIntArray(LittleEndianNBTOutputStream out, Tag tag) throws IOException { + out.writeInt(((IntArrayTag) tag).length()); + for (int i : ((IntArrayTag) tag).getValue()) { + out.writeInt(i); + } + } + + private static void writeLongArray(LittleEndianNBTOutputStream out, Tag tag) throws IOException { + out.writeInt(((LongArrayTag) tag).length()); + for (long l : ((LongArrayTag) tag).getValue()) { + out.writeLong(l); + } + } + + private static void writeList(LittleEndianNBTOutputStream out, Tag tag, int maxDepth) throws IOException { + out.writeByte(idFromClass(((ListTag) tag).getTypeClass())); + out.writeInt(((ListTag) tag).size()); + for (Tag t : ((ListTag) tag)) { + out.writeRawTag(t, out.decrementMaxDepth(maxDepth)); + } + } + + private static void writeCompound(LittleEndianNBTOutputStream out, Tag tag, int maxDepth) throws IOException { + for (Map.Entry> entry : (CompoundTag) tag) { + if (entry.getValue().getID() == 0) { + throw new IOException("end tag not allowed"); + } + out.writeByte(entry.getValue().getID()); + out.writeUTF(entry.getKey()); + out.writeRawTag(entry.getValue(), out.decrementMaxDepth(maxDepth)); + } + out.writeByte(0); + } + + @Override + public void close() throws IOException { + output.close(); + } + + @Override + public void flush() throws IOException { + output.flush(); + } + + @Override + public void write(int b) throws IOException { + output.write(b); + } + + @Override + public void write(byte[] b) throws IOException { + output.write(b); + } + + @Override + public void write(byte[] b, int off, int len) throws IOException { + output.write(b, off, len); + } + + @Override + public void writeBoolean(boolean v) throws IOException { + output.writeBoolean(v); + } + + @Override + public void writeByte(int v) throws IOException { + output.writeByte(v); + } + + @Override + public void writeShort(int v) throws IOException { + output.writeShort(Short.reverseBytes((short) v)); + } + + @Override + public void writeChar(int v) throws IOException { + output.writeChar(Character.reverseBytes((char) v)); + } + + @Override + public void writeInt(int v) throws IOException { + output.writeInt(Integer.reverseBytes(v)); + } + + @Override + public void writeLong(long v) throws IOException { + output.writeLong(Long.reverseBytes(v)); + } + + @Override + public void writeFloat(float v) throws IOException { + output.writeInt(Integer.reverseBytes(Float.floatToIntBits(v))); + } + + @Override + public void writeDouble(double v) throws IOException { + output.writeLong(Long.reverseBytes(Double.doubleToLongBits(v))); + } + + @Override + public void writeBytes(String s) throws IOException { + output.writeBytes(s); + } + + @Override + public void writeChars(String s) throws IOException { + output.writeChars(s); + } + + @Override + public void writeUTF(String s) throws IOException { + byte[] bytes = s.getBytes(StandardCharsets.UTF_8); + writeShort(bytes.length); + write(bytes); + } +} diff --git a/src/main/java/net/querz/nbt/io/NBTDeserializer.java b/src/main/java/net/querz/nbt/io/NBTDeserializer.java index 2f7289e3..085e37be 100644 --- a/src/main/java/net/querz/nbt/io/NBTDeserializer.java +++ b/src/main/java/net/querz/nbt/io/NBTDeserializer.java @@ -8,7 +8,7 @@ public class NBTDeserializer implements Deserializer { - private boolean compressed; + private boolean compressed, littleEndian; public NBTDeserializer() { this(true); @@ -18,13 +18,25 @@ public NBTDeserializer(boolean compressed) { this.compressed = compressed; } + public NBTDeserializer(boolean compressed, boolean littleEndian) { + this.compressed = compressed; + this.littleEndian = littleEndian; + } + @Override public NamedTag fromStream(InputStream stream) throws IOException { - NBTInputStream nbtIn; + NBTInput nbtIn; + InputStream input; if (compressed) { - nbtIn = new NBTInputStream(new GZIPInputStream(stream)); + input = new GZIPInputStream(stream); + } else { + input = stream; + } + + if (littleEndian) { + nbtIn = new LittleEndianNBTInputStream(input); } else { - nbtIn = new NBTInputStream(stream); + nbtIn = new NBTInputStream(input); } return nbtIn.readTag(Tag.DEFAULT_MAX_DEPTH); } diff --git a/src/main/java/net/querz/nbt/io/NBTInput.java b/src/main/java/net/querz/nbt/io/NBTInput.java new file mode 100644 index 00000000..b17c6123 --- /dev/null +++ b/src/main/java/net/querz/nbt/io/NBTInput.java @@ -0,0 +1,11 @@ +package net.querz.nbt.io; + +import net.querz.nbt.tag.Tag; +import java.io.IOException; + +public interface NBTInput { + + NamedTag readTag(int maxDepth) throws IOException; + + Tag readRawTag(int maxDepth) throws IOException; +} diff --git a/src/main/java/net/querz/nbt/io/NBTInputStream.java b/src/main/java/net/querz/nbt/io/NBTInputStream.java index b3ca6b8a..dd69c3c7 100644 --- a/src/main/java/net/querz/nbt/io/NBTInputStream.java +++ b/src/main/java/net/querz/nbt/io/NBTInputStream.java @@ -22,7 +22,7 @@ import java.util.HashMap; import java.util.Map; -public class NBTInputStream extends DataInputStream implements MaxDepthIO { +public class NBTInputStream extends DataInputStream implements NBTInput, MaxDepthIO { private static Map, IOException>> readers = new HashMap<>(); private static Map> idClassMapping = new HashMap<>(); diff --git a/src/main/java/net/querz/nbt/io/NBTOutput.java b/src/main/java/net/querz/nbt/io/NBTOutput.java new file mode 100644 index 00000000..39f6d688 --- /dev/null +++ b/src/main/java/net/querz/nbt/io/NBTOutput.java @@ -0,0 +1,13 @@ +package net.querz.nbt.io; + +import net.querz.nbt.tag.Tag; +import java.io.IOException; + +public interface NBTOutput { + + void writeTag(NamedTag tag, int maxDepth) throws IOException; + + void writeTag(Tag tag, int maxDepth) throws IOException; + + void flush() throws IOException; +} diff --git a/src/main/java/net/querz/nbt/io/NBTOutputStream.java b/src/main/java/net/querz/nbt/io/NBTOutputStream.java index 4879052e..ae1a16b0 100644 --- a/src/main/java/net/querz/nbt/io/NBTOutputStream.java +++ b/src/main/java/net/querz/nbt/io/NBTOutputStream.java @@ -22,7 +22,7 @@ import java.util.HashMap; import java.util.Map; -public class NBTOutputStream extends DataOutputStream implements MaxDepthIO { +public class NBTOutputStream extends DataOutputStream implements NBTOutput, MaxDepthIO { private static Map, Integer, IOException>> writers = new HashMap<>(); private static Map, Byte> classIdMapping = new HashMap<>(); diff --git a/src/main/java/net/querz/nbt/io/NBTSerializer.java b/src/main/java/net/querz/nbt/io/NBTSerializer.java index fe505992..921d8f47 100644 --- a/src/main/java/net/querz/nbt/io/NBTSerializer.java +++ b/src/main/java/net/querz/nbt/io/NBTSerializer.java @@ -8,7 +8,7 @@ public class NBTSerializer implements Serializer { - private boolean compressed; + private boolean compressed, littleEndian; public NBTSerializer() { this(true); @@ -18,13 +18,25 @@ public NBTSerializer(boolean compressed) { this.compressed = compressed; } + public NBTSerializer(boolean compressed, boolean littleEndian) { + this.compressed = compressed; + this.littleEndian = littleEndian; + } + @Override public void toStream(NamedTag object, OutputStream out) throws IOException { - NBTOutputStream nbtOut; + NBTOutput nbtOut; + OutputStream output; if (compressed) { - nbtOut = new NBTOutputStream(new GZIPOutputStream(out, true)); + output = new GZIPOutputStream(out, true); + } else { + output = out; + } + + if (littleEndian) { + nbtOut = new LittleEndianNBTOutputStream(output); } else { - nbtOut = new NBTOutputStream(out); + nbtOut = new NBTOutputStream(output); } nbtOut.writeTag(object, Tag.DEFAULT_MAX_DEPTH); nbtOut.flush(); diff --git a/src/main/java/net/querz/nbt/io/NBTUtil.java b/src/main/java/net/querz/nbt/io/NBTUtil.java index d8efc150..edd0b869 100644 --- a/src/main/java/net/querz/nbt/io/NBTUtil.java +++ b/src/main/java/net/querz/nbt/io/NBTUtil.java @@ -47,6 +47,40 @@ public static void write(Tag tag, String file) throws IOException { write(new NamedTag(null, tag), new File(file), true); } + public static void writeLE(NamedTag tag, File file, boolean compressed) throws IOException { + try (FileOutputStream fos = new FileOutputStream(file)) { + new NBTSerializer(compressed, true).toStream(tag, fos); + } + } + + public static void writeLE(NamedTag tag, String file, boolean compressed) throws IOException { + writeLE(tag, new File(file), compressed); + } + + public static void writeLE(NamedTag tag, File file) throws IOException { + writeLE(tag, file, true); + } + + public static void writeLE(NamedTag tag, String file) throws IOException { + writeLE(tag, new File(file), true); + } + + public static void writeLE(Tag tag, File file, boolean compressed) throws IOException { + writeLE(new NamedTag(null, tag), file, compressed); + } + + public static void writeLE(Tag tag, String file, boolean compressed) throws IOException { + writeLE(new NamedTag(null, tag), new File(file), compressed); + } + + public static void writeLE(Tag tag, File file) throws IOException { + writeLE(new NamedTag(null, tag), file, true); + } + + public static void writeLE(Tag tag, String file) throws IOException { + writeLE(new NamedTag(null, tag), new File(file), true); + } + public static NamedTag read(File file, boolean compressed) throws IOException { try (FileInputStream fis = new FileInputStream(file)) { return new NBTDeserializer(compressed).fromStream(fis); @@ -67,6 +101,26 @@ public static NamedTag read(String file) throws IOException { return read(new File(file)); } + public static NamedTag readLE(File file, boolean compressed) throws IOException { + try (FileInputStream fis = new FileInputStream(file)) { + return new NBTDeserializer(compressed, true).fromStream(fis); + } + } + + public static NamedTag readLE(String file, boolean compressed) throws IOException { + return readLE(new File(file), compressed); + } + + public static NamedTag readLE(File file) throws IOException { + try (FileInputStream fis = new FileInputStream(file)) { + return new NBTDeserializer(false, true).fromStream(detectDecompression(fis)); + } + } + + public static NamedTag readLE(String file) throws IOException { + return readLE(new File(file)); + } + private static InputStream detectDecompression(InputStream is) throws IOException { PushbackInputStream pbis = new PushbackInputStream(is, 2); int signature = (pbis.read() & 0xFF) + (pbis.read() << 8); From d65f60dcf76b084715720b0d0611797aa2a7aa59 Mon Sep 17 00:00:00 2001 From: Querz Date: Fri, 7 Aug 2020 17:19:13 +0200 Subject: [PATCH 32/51] add load flag to only load raw data and skip loading specifics --- src/main/java/net/querz/mca/Chunk.java | 107 +++++++++++++++++---- src/main/java/net/querz/mca/LoadFlags.java | 37 ++++--- 2 files changed, 107 insertions(+), 37 deletions(-) diff --git a/src/main/java/net/querz/mca/Chunk.java b/src/main/java/net/querz/mca/Chunk.java index d305df04..cd7d9853 100644 --- a/src/main/java/net/querz/mca/Chunk.java +++ b/src/main/java/net/querz/mca/Chunk.java @@ -16,9 +16,10 @@ public class Chunk { - public static final int DEFAULT_DATA_VERSION = 1628; + public static final int DEFAULT_DATA_VERSION = 2567; private boolean partial; + private boolean raw; private int lastMCAUpdate; @@ -59,6 +60,12 @@ private void initReferences(long loadFlags) { if (data == null) { throw new NullPointerException("data cannot be null"); } + + if ((loadFlags != ALL_DATA) && (loadFlags & RAW) != 0) { + raw = true; + return; + } + CompoundTag level; if ((level = data.getCompoundTag("Level")) == null) { throw new IllegalArgumentException("data does not contain \"Level\" tag"); @@ -121,8 +128,6 @@ private void initReferences(long loadFlags) { if (loadFlags != ALL_DATA) { data = null; partial = true; - } else { - partial = false; } } @@ -224,6 +229,7 @@ public int getBiomeAt(int blockX, int blockY, int blockZ) { @Deprecated public void setBiomeAt(int blockX, int blockZ, int biomeID) { + checkRaw(); if (dataVersion < 2202) { if (biomes == null || biomes.length != 256) { biomes = new int[256]; @@ -254,6 +260,7 @@ public void setBiomeAt(int blockX, int blockZ, int biomeID) { * When set to a negative number, Minecraft will replace it with the block column's default biome. */ public void setBiomeAt(int blockX, int blockY, int blockZ, int biomeID) { + checkRaw(); if (dataVersion < 2202) { if (biomes == null || biomes.length != 256) { biomes = new int[256]; @@ -297,6 +304,7 @@ public CompoundTag getBlockStateAt(int blockX, int blockY, int blockZ) { * Recalculating the Palette should only be executed once right before saving the Chunk to file. */ public void setBlockStateAt(int blockX, int blockY, int blockZ, CompoundTag state, boolean cleanup) { + checkRaw(); int sectionIndex = MCAUtil.blockToChunk(blockY); Section section = sections[sectionIndex]; if (section == null) { @@ -318,6 +326,7 @@ public int getDataVersion() { * @param dataVersion The DataVersion to be set. */ public void setDataVersion(int dataVersion) { + checkRaw(); this.dataVersion = dataVersion; } @@ -333,6 +342,7 @@ public int getLastMCAUpdate() { * @param lastMCAUpdate The time in seconds since 1970-01-01. */ public void setLastMCAUpdate(int lastMCAUpdate) { + checkRaw(); this.lastMCAUpdate = lastMCAUpdate; } @@ -348,6 +358,7 @@ public String getStatus() { * @param status The generation status of this chunk. */ public void setStatus(String status) { + checkRaw(); this.status = status; } @@ -366,6 +377,7 @@ public Section getSection(int sectionY) { * @param section The section to be set. */ public void setSection(int sectionY, Section section) { + checkRaw(); sections[sectionY] = section; } @@ -381,6 +393,7 @@ public long getLastUpdate() { * @param lastUpdate The UNIX timestamp. */ public void setLastUpdate(long lastUpdate) { + checkRaw(); this.lastUpdate = lastUpdate; } @@ -396,6 +409,7 @@ public long getInhabitedTime() { * @param inhabitedTime The time in ticks. */ public void setInhabitedTime(long inhabitedTime) { + checkRaw(); this.inhabitedTime = inhabitedTime; } @@ -413,6 +427,7 @@ public int[] getBiomes() { * or is null */ public void setBiomes(int[] biomes) { + checkRaw(); if (biomes != null) { if (dataVersion < 2202 && biomes.length != 256 || dataVersion >= 2202 && biomes.length != 1024) { throw new IllegalArgumentException("biomes array must have a length of " + (dataVersion < 2202 ? "256" : "1024")); @@ -433,6 +448,7 @@ public CompoundTag getHeightMaps() { * @param heightMaps The height maps. */ public void setHeightMaps(CompoundTag heightMaps) { + checkRaw(); this.heightMaps = heightMaps; } @@ -448,6 +464,7 @@ public CompoundTag getCarvingMasks() { * @param carvingMasks The carving masks. */ public void setCarvingMasks(CompoundTag carvingMasks) { + checkRaw(); this.carvingMasks = carvingMasks; } @@ -463,6 +480,7 @@ public ListTag getEntities() { * @param entities The entities. */ public void setEntities(ListTag entities) { + checkRaw(); this.entities = entities; } @@ -478,6 +496,7 @@ public ListTag getTileEntities() { * @param tileEntities The tile entities of this chunk. */ public void setTileEntities(ListTag tileEntities) { + checkRaw(); this.tileEntities = tileEntities; } @@ -493,6 +512,7 @@ public ListTag getTileTicks() { * @param tileTicks Thee tile ticks. */ public void setTileTicks(ListTag tileTicks) { + checkRaw(); this.tileTicks = tileTicks; } @@ -508,6 +528,7 @@ public ListTag getLiquidTicks() { * @param liquidTicks The liquid ticks. */ public void setLiquidTicks(ListTag liquidTicks) { + checkRaw(); this.liquidTicks = liquidTicks; } @@ -523,11 +544,12 @@ public ListTag> getLights() { * @param lights The light sources. */ public void setLights(ListTag> lights) { + checkRaw(); this.lights = lights; } /** - * @return THe liquids to be ticked in this chunk. + * @return The liquids to be ticked in this chunk. */ public ListTag> getLiquidsToBeTicked() { return liquidsToBeTicked; @@ -538,6 +560,7 @@ public ListTag> getLiquidsToBeTicked() { * @param liquidsToBeTicked The liquids to be ticked. */ public void setLiquidsToBeTicked(ListTag> liquidsToBeTicked) { + checkRaw(); this.liquidsToBeTicked = liquidsToBeTicked; } @@ -553,6 +576,7 @@ public ListTag> getToBeTicked() { * @param toBeTicked The stuff to be ticked. */ public void setToBeTicked(ListTag> toBeTicked) { + checkRaw(); this.toBeTicked = toBeTicked; } @@ -568,6 +592,7 @@ public ListTag> getPostProcessing() { * @param postProcessing The things to be post processed. */ public void setPostProcessing(ListTag> postProcessing) { + checkRaw(); this.postProcessing = postProcessing; } @@ -583,6 +608,7 @@ public CompoundTag getStructures() { * @param structures The data about structures. */ public void setStructures(CompoundTag structures) { + checkRaw(); this.structures = structures; } @@ -591,6 +617,7 @@ int getBlockIndex(int blockX, int blockZ) { } public void cleanupPalettesAndBlockStates() { + checkRaw(); for (Section section : sections) { if (section != null) { section.cleanupPaletteAndBlockStates(); @@ -598,6 +625,12 @@ public void cleanupPalettesAndBlockStates() { } } + private void checkRaw() { + if (raw) { + throw new UnsupportedOperationException("cannot update field when working with raw data"); + } + } + public static Chunk newChunk() { Chunk c = new Chunk(0); c.dataVersion = DEFAULT_DATA_VERSION; @@ -607,7 +640,19 @@ public static Chunk newChunk() { return c; } + /** + * Provides a reference to the full chunk data. + * @return The full chunk data or null if there is none, e.g. when this chunk has only been loaded partially. + */ + public CompoundTag getHandle() { + return data; + } + public CompoundTag updateHandle(int xPos, int zPos) { + if (raw) { + return data; + } + data.putInt("DataVersion", dataVersion); CompoundTag level = data.getCompoundTag("Level"); level.putInt("xPos", xPos); @@ -615,22 +660,48 @@ public CompoundTag updateHandle(int xPos, int zPos) { level.putLong("LastUpdate", lastUpdate); level.putLong("InhabitedTime", inhabitedTime); if (dataVersion < 2202) { - if (biomes != null && biomes.length == 256) level.putIntArray("Biomes", biomes); + if (biomes != null && biomes.length == 256) { + level.putIntArray("Biomes", biomes); + } } else { - if (biomes != null && biomes.length == 1024) level.putIntArray("Biomes", biomes); - } - if (heightMaps != null) level.put("Heightmaps", heightMaps); - if (carvingMasks != null) level.put("CarvingMasks", carvingMasks); - if (entities != null) level.put("Entities", entities); - if (tileEntities != null) level.put("TileEntities", tileEntities); - if (tileTicks != null) level.put("TileTicks", tileTicks); - if (liquidTicks != null) level.put("LiquidTicks", liquidTicks); - if (lights != null) level.put("Lights", lights); - if (liquidsToBeTicked != null) level.put("LiquidsToBeTicked", liquidsToBeTicked); - if (toBeTicked != null) level.put("ToBeTicked", toBeTicked); - if (postProcessing != null) level.put("PostProcessing", postProcessing); + if (biomes != null && biomes.length == 1024) { + level.putIntArray("Biomes", biomes); + } + } + if (heightMaps != null) { + level.put("Heightmaps", heightMaps); + } + if (carvingMasks != null) { + level.put("CarvingMasks", carvingMasks); + } + if (entities != null) { + level.put("Entities", entities); + } + if (tileEntities != null) { + level.put("TileEntities", tileEntities); + } + if (tileTicks != null) { + level.put("TileTicks", tileTicks); + } + if (liquidTicks != null) { + level.put("LiquidTicks", liquidTicks); + } + if (lights != null) { + level.put("Lights", lights); + } + if (liquidsToBeTicked != null) { + level.put("LiquidsToBeTicked", liquidsToBeTicked); + } + if (toBeTicked != null) { + level.put("ToBeTicked", toBeTicked); + } + if (postProcessing != null) { + level.put("PostProcessing", postProcessing); + } level.putString("Status", status); - if (structures != null) level.put("Structures", structures); + if (structures != null) { + level.put("Structures", structures); + } ListTag sections = new ListTag<>(CompoundTag.class); for (int i = 0; i < this.sections.length; i++) { if (this.sections[i] != null) { diff --git a/src/main/java/net/querz/mca/LoadFlags.java b/src/main/java/net/querz/mca/LoadFlags.java index e4122898..da37a597 100644 --- a/src/main/java/net/querz/mca/LoadFlags.java +++ b/src/main/java/net/querz/mca/LoadFlags.java @@ -1,24 +1,23 @@ package net.querz.mca; -public class LoadFlags { - - public static long BIOMES = 0x0001; - public static long HEIGHTMAPS = 0x0002; - public static long CARVING_MASKS = 0x0004; - public static long ENTITIES = 0x0008; - public static long TILE_ENTITIES = 0x0010; - public static long TILE_TICKS = 0x0040; - public static long LIQUID_TICKS = 0x0080; - public static long TO_BE_TICKED = 0x0100; - public static long POST_PROCESSING = 0x0200; - public static long STRUCTURES = 0x0400; - public static long BLOCK_LIGHTS = 0x0800; - public static long BLOCK_STATES = 0x1000; - public static long SKY_LIGHT = 0x2000; - public static long LIGHTS = 0x4000; - public static long LIQUIDS_TO_BE_TICKED = 0x8000; - - public static long ALL_DATA = 0xffffffffffffffffL; +public final class LoadFlags { + public static final long BIOMES = 0x00001; + public static final long HEIGHTMAPS = 0x00002; + public static final long CARVING_MASKS = 0x00004; + public static final long ENTITIES = 0x00008; + public static final long TILE_ENTITIES = 0x00010; + public static final long TILE_TICKS = 0x00040; + public static final long LIQUID_TICKS = 0x00080; + public static final long TO_BE_TICKED = 0x00100; + public static final long POST_PROCESSING = 0x00200; + public static final long STRUCTURES = 0x00400; + public static final long BLOCK_LIGHTS = 0x00800; + public static final long BLOCK_STATES = 0x01000; + public static final long SKY_LIGHT = 0x02000; + public static final long LIGHTS = 0x04000; + public static final long LIQUIDS_TO_BE_TICKED = 0x08000; + public static final long RAW = 0x10000; + public static final long ALL_DATA = 0xffffffffffffffffL; } From da559571b161992953296696331c4eaf6016da98 Mon Sep 17 00:00:00 2001 From: Querz Date: Wed, 2 Sep 2020 15:34:52 +0200 Subject: [PATCH 33/51] incorrectly set list type of empty list when casting / fix unit tests --- src/main/java/net/querz/mca/Chunk.java | 8 +++-- src/main/java/net/querz/nbt/tag/ListTag.java | 11 ++++--- src/test/java/net/querz/mca/MCAFileTest.java | 11 +++---- .../java/net/querz/nbt/tag/ListTagTest.java | 29 +++++-------------- 4 files changed, 25 insertions(+), 34 deletions(-) diff --git a/src/main/java/net/querz/mca/Chunk.java b/src/main/java/net/querz/mca/Chunk.java index cd7d9853..cfe06ce6 100644 --- a/src/main/java/net/querz/mca/Chunk.java +++ b/src/main/java/net/querz/mca/Chunk.java @@ -281,7 +281,7 @@ public void setBiomeAt(int blockX, int blockY, int blockZ, int biomeID) { } int getBiomeIndex(int biomeX, int biomeY, int biomeZ) { - return biomeY * 64 + biomeZ * 4 + biomeX; + return biomeY * 16 + biomeZ * 4 + biomeX; } public CompoundTag getBlockStateAt(int blockX, int blockY, int blockZ) { @@ -632,8 +632,12 @@ private void checkRaw() { } public static Chunk newChunk() { + return newChunk(DEFAULT_DATA_VERSION); + } + + public static Chunk newChunk(int dataVersion) { Chunk c = new Chunk(0); - c.dataVersion = DEFAULT_DATA_VERSION; + c.dataVersion = dataVersion; c.data = new CompoundTag(); c.data.put("Level", new CompoundTag()); c.status = "mobs_spawned"; diff --git a/src/main/java/net/querz/nbt/tag/ListTag.java b/src/main/java/net/querz/nbt/tag/ListTag.java index 008e3aaa..0dd6970a 100644 --- a/src/main/java/net/querz/nbt/tag/ListTag.java +++ b/src/main/java/net/querz/nbt/tag/ListTag.java @@ -12,8 +12,7 @@ /** * ListTag represents a typed List in the nbt structure. - * An empty {@link ListTag} created using {@link ListTag#createUnchecked(Class)} will be of unknown type - * and returns an {@link EndTag}{@code .class} in {@link ListTag#getTypeClass()}. + * An empty {@link ListTag} will be of type {@link EndTag} (unknown type). * 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. * */ @@ -123,7 +122,7 @@ public void add(T t) { public void add(int index, T t) { Objects.requireNonNull(t); - if (typeClass == null || typeClass == EndTag.class) { + if (getTypeClass() == EndTag.class) { typeClass = t.getClass(); } else if (typeClass != t.getClass()) { throw new ClassCastException( @@ -203,7 +202,7 @@ public int indexOf(T t) { @SuppressWarnings("unchecked") public > ListTag asTypedList(Class type) { checkTypeClass(type); - typeClass = type; +// typeClass = type; return (ListTag) this; } @@ -309,7 +308,7 @@ public ListTag clone() { //TODO: make private @SuppressWarnings("unchecked") public void addUnchecked(Tag tag) { - if (typeClass != null && typeClass != tag.getClass() && typeClass != EndTag.class) { + if (getTypeClass() != EndTag.class && typeClass != tag.getClass()) { throw new IllegalArgumentException(String.format( "cannot add %s to ListTag<%s>", tag.getClass().getSimpleName(), typeClass.getSimpleName())); @@ -318,7 +317,7 @@ public void addUnchecked(Tag tag) { } private void checkTypeClass(Class clazz) { - if (typeClass != null && typeClass != EndTag.class && typeClass != clazz) { + if (getTypeClass() != EndTag.class && typeClass != clazz) { throw new ClassCastException(String.format( "cannot cast ListTag<%s> to ListTag<%s>", typeClass.getSimpleName(), clazz.getSimpleName())); diff --git a/src/test/java/net/querz/mca/MCAFileTest.java b/src/test/java/net/querz/mca/MCAFileTest.java index 38e09f49..2688001a 100644 --- a/src/test/java/net/querz/mca/MCAFileTest.java +++ b/src/test/java/net/querz/mca/MCAFileTest.java @@ -1,6 +1,7 @@ package net.querz.mca; import net.querz.nbt.tag.CompoundTag; +import net.querz.nbt.tag.EndTag; import net.querz.nbt.tag.ListTag; import static net.querz.mca.LoadFlags.*; import java.io.File; @@ -90,7 +91,7 @@ public void testGetters() { assertNotNull(f.getChunk(0).getBiomes()); assertNull(f.getChunk(0).getHeightMaps()); assertNull(f.getChunk(0).getCarvingMasks()); - assertEquals(new ListTag<>(CompoundTag.class), f.getChunk(0).getEntities()); + assertEquals(ListTag.createUnchecked(null), f.getChunk(0).getEntities()); assertNull(f.getChunk(0).getTileEntities()); assertNull(f.getChunk(0).getTileTicks()); assertNull(f.getChunk(0).getLiquidTicks()); @@ -184,7 +185,7 @@ public void testGetBiomeAt() { MCAFile f = assertThrowsNoException(() -> MCAUtil.read(copyResourceToTmp("r.2.2.mca"))); assertEquals(21, f.getBiomeAt(1024, 1024)); assertEquals(-1, f.getBiomeAt(1040, 1024)); - f.setChunk(0, 1, Chunk.newChunk()); + f.setChunk(0, 1, Chunk.newChunk(2201)); assertEquals(-1, f.getBiomeAt(1024, 1040)); } @@ -197,9 +198,9 @@ public void testSetBiomeAt() { assertEquals(47, f.getChunk(64, 64).updateHandle(64, 64).getCompoundTag("Level").getIntArray("Biomes")[255]); f.setBiomeAt(1040, 1024, 20); int[] biomes = f.getChunk(65, 64).updateHandle(65, 64).getCompoundTag("Level").getIntArray("Biomes"); - assertEquals(256, biomes.length); - for (int i = 0; i < 256; i++) { - assertTrue(i == 0 ? biomes[i] == 20 : biomes[i] == -1); + assertEquals(1024, biomes.length); + for (int i = 0; i < 1024; i++) { + assertTrue(i % 16 == 0 ? biomes[i] == 20 : biomes[i] == -1); } } diff --git a/src/test/java/net/querz/nbt/tag/ListTagTest.java b/src/test/java/net/querz/nbt/tag/ListTagTest.java index bb1485e8..af2b72fb 100644 --- a/src/test/java/net/querz/nbt/tag/ListTagTest.java +++ b/src/test/java/net/querz/nbt/tag/ListTagTest.java @@ -72,11 +72,11 @@ public void testEquals() { ListTag lu2 = ListTag.createUnchecked(null); assertTrue(lu.equals(lu2)); lu2.asIntTagList(); - assertFalse(lu.equals(lu2)); + assertTrue(lu.equals(lu2)); ListTag lie = new ListTag<>(IntTag.class); assertFalse(lu.equals(lie)); lu.asIntTagList(); - assertTrue(lie.equals(lu)); + assertFalse(lie.equals(lu)); } public void testHashCode() { @@ -91,7 +91,8 @@ public void testClone() { ListTag i = new ListTag<>(IntTag.class); ListTag c = i.clone(); assertThrowsRuntimeException(() -> c.addString("wrong type in clone"), IllegalArgumentException.class); - c.addInt(123); + assertThrowsNoRuntimeException(() -> c.addInt(123)); + assertFalse(i.equals(c)); c.clear(); assertTrue(i.equals(c)); @@ -116,12 +117,10 @@ public void testSerializeDeserialize() { public void testSerializeDeserializeEmptyList() { ListTag empty = new ListTag<>(IntTag.class); byte[] data = serialize(empty); -// empty list can't have type - assertTrue(Arrays.equals(new byte[]{9, 0, 0, 0, 0, 0, 0, 0}, data)); + assertTrue(Arrays.equals(new byte[]{9, 0, 0, 3, 0, 0, 0, 0}, data)); ListTag et = (ListTag) deserialize(data); assertNotNull(et); -// doesn't make sense as there's no type -// assertThrowsRuntimeException(et::asByteTagList, ClassCastException.class); + assertThrowsRuntimeException(et::asByteTagList, ClassCastException.class); } public void testCasting() { @@ -205,20 +204,8 @@ public void testCasting() { ListTag lg = ListTag.createUnchecked(null); ListTag lb = assertThrowsNoRuntimeException(lg::asByteTagList); assertEquals(lb, lg); - //only allow casting once from untyped list to typed list - assertThrowsRuntimeException(lg::asShortTagList, ClassCastException.class); - - //adding generic Tags to an empty list - @SuppressWarnings("unchecked") - ListTag> u = (ListTag>) TagFactory.fromID(9); - assertEquals(EndTag.class, u.getTypeClass()); - assertThrowsNoRuntimeException(() -> u.add(new StringTag())); - assertEquals(StringTag.class, u.getTypeClass()); - assertThrowsRuntimeException(() -> u.add(new ByteTag()), ClassCastException.class); - assertThrowsNoRuntimeException(() -> u.add(new StringTag())); - assertEquals(2, u.size()); - - + // allow casting to a different list type if the list is empty + assertThrowsNoException(lg::asShortTagList); } public void testCompareTo() { From cc9202c06fa13b40d50e761db43487d01f59a459 Mon Sep 17 00:00:00 2001 From: Querz Date: Wed, 2 Sep 2020 15:35:16 +0200 Subject: [PATCH 34/51] remove commented out code --- src/main/java/net/querz/nbt/tag/ListTag.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/java/net/querz/nbt/tag/ListTag.java b/src/main/java/net/querz/nbt/tag/ListTag.java index 0dd6970a..f26c4b07 100644 --- a/src/main/java/net/querz/nbt/tag/ListTag.java +++ b/src/main/java/net/querz/nbt/tag/ListTag.java @@ -202,7 +202,6 @@ public int indexOf(T t) { @SuppressWarnings("unchecked") public > ListTag asTypedList(Class type) { checkTypeClass(type); -// typeClass = type; return (ListTag) this; } From 35adf839aa174b7035c76e44843f70adbb90bed0 Mon Sep 17 00:00:00 2001 From: Querz Date: Mon, 11 Jan 2021 12:33:16 +0100 Subject: [PATCH 35/51] update version to 6.0 --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 4d457e77..4e79db37 100644 --- a/build.gradle +++ b/build.gradle @@ -10,7 +10,7 @@ apply plugin: 'jacoco' group = 'net.querz.nbt' archivesBaseName = 'nbt' -version = '5.5' +version = '6.0' sourceCompatibility = '1.8' targetCompatibility = '1.8' compileJava.options.encoding = 'UTF-8' From 172f259bf1b0e7f11455b1a089d4eef2966aec4c Mon Sep 17 00:00:00 2001 From: Querz Date: Wed, 17 Feb 2021 14:20:31 +0100 Subject: [PATCH 36/51] add lenient mode for snbt parser --- src/main/java/net/querz/nbt/io/SNBTParser.java | 12 +++++++++--- src/main/java/net/querz/nbt/io/SNBTUtil.java | 4 ++++ 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/src/main/java/net/querz/nbt/io/SNBTParser.java b/src/main/java/net/querz/nbt/io/SNBTParser.java index 7a9ac5d2..30ff6999 100644 --- a/src/main/java/net/querz/nbt/io/SNBTParser.java +++ b/src/main/java/net/querz/nbt/io/SNBTParser.java @@ -39,11 +39,17 @@ private SNBTParser(String string) { } public static Tag parse(String string, int maxDepth) throws ParseException { + return parse(string, maxDepth, false); + } + + public static Tag parse(String string, int maxDepth, boolean lenient) throws ParseException { SNBTParser parser = new SNBTParser(string); Tag tag = parser.parseAnything(maxDepth); - parser.ptr.skipWhitespace(); - if (parser.ptr.hasNext()) { - throw parser.ptr.parseException("invalid characters after end of snbt"); + if (!lenient) { + parser.ptr.skipWhitespace(); + if (parser.ptr.hasNext()) { + throw parser.ptr.parseException("invalid characters after end of snbt"); + } } return tag; } diff --git a/src/main/java/net/querz/nbt/io/SNBTUtil.java b/src/main/java/net/querz/nbt/io/SNBTUtil.java index e29981fc..6f1bb897 100644 --- a/src/main/java/net/querz/nbt/io/SNBTUtil.java +++ b/src/main/java/net/querz/nbt/io/SNBTUtil.java @@ -12,4 +12,8 @@ public static String toSNBT(Tag tag) throws IOException { public static Tag fromSNBT(String string) throws IOException { return new SNBTDeserializer().fromString(string); } + + public static Tag fromSNBT(String string, boolean lenient) throws IOException { + return SNBTParser.parse(string, Tag.DEFAULT_MAX_DEPTH, lenient); + } } From a78b794dbb197f2ba92b1060531252c2bbe88095 Mon Sep 17 00:00:00 2001 From: Querz Date: Wed, 17 Feb 2021 14:36:22 +0100 Subject: [PATCH 37/51] make SNBTParser public, remove static methods and add methods to get number of read characters --- .../net/querz/nbt/io/SNBTDeserializer.java | 2 +- .../java/net/querz/nbt/io/SNBTParser.java | 29 ++++--- src/main/java/net/querz/nbt/io/SNBTUtil.java | 2 +- .../java/net/querz/nbt/io/StringPointer.java | 8 ++ .../java/net/querz/nbt/io/SNBTParserTest.java | 80 +++++++++---------- 5 files changed, 66 insertions(+), 55 deletions(-) diff --git a/src/main/java/net/querz/nbt/io/SNBTDeserializer.java b/src/main/java/net/querz/nbt/io/SNBTDeserializer.java index 05a98fef..f45c27e1 100644 --- a/src/main/java/net/querz/nbt/io/SNBTDeserializer.java +++ b/src/main/java/net/querz/nbt/io/SNBTDeserializer.java @@ -21,6 +21,6 @@ public Tag fromReader(Reader reader, int maxDepth) throws IOException { } else { bufferedReader = new BufferedReader(reader); } - return SNBTParser.parse(bufferedReader.lines().collect(Collectors.joining()), maxDepth); + return new SNBTParser(bufferedReader.lines().collect(Collectors.joining())).parse(maxDepth); } } diff --git a/src/main/java/net/querz/nbt/io/SNBTParser.java b/src/main/java/net/querz/nbt/io/SNBTParser.java index 30ff6999..7d8d2dae 100644 --- a/src/main/java/net/querz/nbt/io/SNBTParser.java +++ b/src/main/java/net/querz/nbt/io/SNBTParser.java @@ -34,28 +34,31 @@ public final class SNBTParser implements MaxDepthIO { private StringPointer ptr; - private SNBTParser(String string) { + public SNBTParser(String string) { this.ptr = new StringPointer(string); } - public static Tag parse(String string, int maxDepth) throws ParseException { - return parse(string, maxDepth, false); - } - - public static Tag parse(String string, int maxDepth, boolean lenient) throws ParseException { - SNBTParser parser = new SNBTParser(string); - Tag tag = parser.parseAnything(maxDepth); + public Tag parse(int maxDepth, boolean lenient) throws ParseException { + Tag tag = parseAnything(maxDepth); if (!lenient) { - parser.ptr.skipWhitespace(); - if (parser.ptr.hasNext()) { - throw parser.ptr.parseException("invalid characters after end of snbt"); + ptr.skipWhitespace(); + if (ptr.hasNext()) { + throw ptr.parseException("invalid characters after end of snbt"); } } return tag; } - public static Tag parse(String string) throws ParseException { - return parse(string, Tag.DEFAULT_MAX_DEPTH); + public Tag parse(int maxDepth) throws ParseException { + return parse(maxDepth, false); + } + + public Tag parse() throws ParseException { + return parse(Tag.DEFAULT_MAX_DEPTH, false); + } + + public int getReadChars() { + return ptr.getIndex() + 1; } private Tag parseAnything(int maxDepth) throws ParseException { diff --git a/src/main/java/net/querz/nbt/io/SNBTUtil.java b/src/main/java/net/querz/nbt/io/SNBTUtil.java index 6f1bb897..d6f43b26 100644 --- a/src/main/java/net/querz/nbt/io/SNBTUtil.java +++ b/src/main/java/net/querz/nbt/io/SNBTUtil.java @@ -14,6 +14,6 @@ public static Tag fromSNBT(String string) throws IOException { } public static Tag fromSNBT(String string, boolean lenient) throws IOException { - return SNBTParser.parse(string, Tag.DEFAULT_MAX_DEPTH, lenient); + return new SNBTParser(string).parse(Tag.DEFAULT_MAX_DEPTH, lenient); } } diff --git a/src/main/java/net/querz/nbt/io/StringPointer.java b/src/main/java/net/querz/nbt/io/StringPointer.java index 11bdddcc..fe37b20d 100644 --- a/src/main/java/net/querz/nbt/io/StringPointer.java +++ b/src/main/java/net/querz/nbt/io/StringPointer.java @@ -9,6 +9,14 @@ public StringPointer(String value) { this.value = value; } + public int getIndex() { + return index; + } + + public int size() { + return value.length(); + } + public String parseSimpleString() { int oldIndex = index; while (hasNext() && isSimpleChar(currentChar())) { diff --git a/src/test/java/net/querz/nbt/io/SNBTParserTest.java b/src/test/java/net/querz/nbt/io/SNBTParserTest.java index a345333c..597d2026 100644 --- a/src/test/java/net/querz/nbt/io/SNBTParserTest.java +++ b/src/test/java/net/querz/nbt/io/SNBTParserTest.java @@ -7,7 +7,7 @@ public class SNBTParserTest extends NBTTestCase { public void testParse() { - Tag t = assertThrowsNoException(() -> SNBTParser.parse("{abc: def, blah: 4b, blubb: \"string\", \"foo\": 2s}")); + Tag t = assertThrowsNoException(() -> new SNBTParser("{abc: def, blah: 4b, blubb: \"string\", \"foo\": 2s}").parse()); assertEquals(CompoundTag.class, t.getClass()); CompoundTag c = (CompoundTag) t; assertEquals(4, c.size()); @@ -19,118 +19,118 @@ public void testParse() { // ------------------------------------------------- number tags - Tag tb = assertThrowsNoException(() -> SNBTParser.parse("16b")); + Tag tb = assertThrowsNoException(() -> new SNBTParser("16b").parse()); assertEquals(ByteTag.class, tb.getClass()); assertEquals((byte) 16, ((ByteTag) tb).asByte()); - tb = assertThrowsNoException(() -> SNBTParser.parse("16B")); + tb = assertThrowsNoException(() -> new SNBTParser("16B").parse()); assertEquals(ByteTag.class, tb.getClass()); assertEquals((byte) 16, ((ByteTag) tb).asByte()); - assertThrowsException((() -> SNBTParser.parse("-129b")), ParseException.class); + assertThrowsException((() -> new SNBTParser("-129b").parse()), ParseException.class); - Tag ts = assertThrowsNoException(() -> SNBTParser.parse("17s")); + Tag ts = assertThrowsNoException(() -> new SNBTParser("17s").parse()); assertEquals(ShortTag.class, ts.getClass()); assertEquals((short) 17, ((ShortTag) ts).asShort()); - ts = assertThrowsNoException(() -> SNBTParser.parse("17S")); + ts = assertThrowsNoException(() -> new SNBTParser("17S").parse()); assertEquals(ShortTag.class, ts.getClass()); assertEquals((short) 17, ((ShortTag) ts).asShort()); - assertThrowsException((() -> SNBTParser.parse("-32769s")), ParseException.class); + assertThrowsException((() -> new SNBTParser("-32769s").parse()), ParseException.class); - Tag ti = assertThrowsNoException(() -> SNBTParser.parse("18")); + Tag ti = assertThrowsNoException(() -> new SNBTParser("18").parse()); assertEquals(IntTag.class, ti.getClass()); assertEquals(18, ((IntTag) ti).asInt()); - assertThrowsException((() -> SNBTParser.parse("-2147483649")), ParseException.class); + assertThrowsException((() -> new SNBTParser("-2147483649").parse()), ParseException.class); - Tag tl = assertThrowsNoException(() -> SNBTParser.parse("19l")); + Tag tl = assertThrowsNoException(() -> new SNBTParser("19l").parse()); assertEquals(LongTag.class, tl.getClass()); assertEquals(19L, ((LongTag) tl).asLong()); - tl = assertThrowsNoException(() -> SNBTParser.parse("19L")); + tl = assertThrowsNoException(() -> new SNBTParser("19L").parse()); assertEquals(LongTag.class, tl.getClass()); assertEquals(19L, ((LongTag) tl).asLong()); - assertThrowsException((() -> SNBTParser.parse("-9223372036854775809l")), ParseException.class); + assertThrowsException((() -> new SNBTParser("-9223372036854775809l").parse()), ParseException.class); - Tag tf = assertThrowsNoException(() -> SNBTParser.parse("20.3f")); + Tag tf = assertThrowsNoException(() -> new SNBTParser("20.3f").parse()); assertEquals(FloatTag.class, tf.getClass()); assertEquals(20.3f, ((FloatTag) tf).asFloat()); - tf = assertThrowsNoException(() -> SNBTParser.parse("20.3F")); + tf = assertThrowsNoException(() -> new SNBTParser("20.3F").parse()); assertEquals(FloatTag.class, tf.getClass()); assertEquals(20.3f, ((FloatTag) tf).asFloat()); - Tag td = assertThrowsNoException(() -> SNBTParser.parse("21.3d")); + Tag td = assertThrowsNoException(() -> new SNBTParser("21.3d").parse()); assertEquals(DoubleTag.class, td.getClass()); assertEquals(21.3d, ((DoubleTag) td).asDouble()); - td = assertThrowsNoException(() -> SNBTParser.parse("21.3D")); + td = assertThrowsNoException(() -> new SNBTParser("21.3D").parse()); assertEquals(DoubleTag.class, td.getClass()); assertEquals(21.3d, ((DoubleTag) td).asDouble()); - td = assertThrowsNoException(() -> SNBTParser.parse("21.3")); + td = assertThrowsNoException(() -> new SNBTParser("21.3").parse()); assertEquals(DoubleTag.class, td.getClass()); assertEquals(21.3d, ((DoubleTag) td).asDouble()); - Tag tbo = assertThrowsNoException(() -> SNBTParser.parse("true")); + Tag tbo = assertThrowsNoException(() -> new SNBTParser("true").parse()); assertEquals(ByteTag.class, tbo.getClass()); assertEquals((byte) 1, ((ByteTag) tbo).asByte()); - tbo = assertThrowsNoException(() -> SNBTParser.parse("false")); + tbo = assertThrowsNoException(() -> new SNBTParser("false").parse()); assertEquals(ByteTag.class, tbo.getClass()); assertEquals((byte) 0, ((ByteTag) tbo).asByte()); // ------------------------------------------------- arrays - Tag ba = assertThrowsNoException(() -> SNBTParser.parse("[B; -128,0, 127]")); + Tag ba = assertThrowsNoException(() -> new SNBTParser("[B; -128,0, 127]").parse()); assertEquals(ByteArrayTag.class, ba.getClass()); assertEquals(3, ((ByteArrayTag) ba).length()); assertTrue(Arrays.equals(new byte[]{-128, 0, 127}, ((ByteArrayTag) ba).getValue())); - Tag ia = assertThrowsNoException(() -> SNBTParser.parse("[I; -2147483648, 0,2147483647]")); + Tag ia = assertThrowsNoException(() -> new SNBTParser("[I; -2147483648, 0,2147483647]").parse()); assertEquals(IntArrayTag.class, ia.getClass()); assertEquals(3, ((IntArrayTag) ia).length()); assertTrue(Arrays.equals(new int[]{-2147483648, 0, 2147483647}, ((IntArrayTag) ia).getValue())); - Tag la = assertThrowsNoException(() -> SNBTParser.parse("[L; -9223372036854775808, 0, 9223372036854775807 ]")); + Tag la = assertThrowsNoException(() -> new SNBTParser("[L; -9223372036854775808, 0, 9223372036854775807 ]").parse()); assertEquals(LongArrayTag.class, la.getClass()); assertEquals(3, ((LongArrayTag) la).length()); assertTrue(Arrays.equals(new long[]{-9223372036854775808L, 0, 9223372036854775807L}, ((LongArrayTag) la).getValue())); // ------------------------------------------------- invalid arrays - assertThrowsException((() -> SNBTParser.parse("[B; -129]")), ParseException.class); - assertThrowsException((() -> SNBTParser.parse("[I; -2147483649]")), ParseException.class); - assertThrowsException((() -> SNBTParser.parse("[L; -9223372036854775809]")), ParseException.class); - assertThrowsException((() -> SNBTParser.parse("[B; 123b]")), ParseException.class); - assertThrowsException((() -> SNBTParser.parse("[I; 123i]")), ParseException.class); - assertThrowsException((() -> SNBTParser.parse("[L; 123l]")), ParseException.class); - assertThrowsException((() -> SNBTParser.parse("[K; -129]")), ParseException.class); + assertThrowsException((() -> new SNBTParser("[B; -129]").parse()), ParseException.class); + assertThrowsException((() -> new SNBTParser("[I; -2147483649]").parse()), ParseException.class); + assertThrowsException((() -> new SNBTParser("[L; -9223372036854775809]").parse()), ParseException.class); + assertThrowsException((() -> new SNBTParser("[B; 123b]").parse()), ParseException.class); + assertThrowsException((() -> new SNBTParser("[I; 123i]").parse()), ParseException.class); + assertThrowsException((() -> new SNBTParser("[L; 123l]").parse()), ParseException.class); + assertThrowsException((() -> new SNBTParser("[K; -129]").parse()), ParseException.class); // ------------------------------------------------- high level errors - assertThrowsException(() -> SNBTParser.parse("{20:10} {blah:blubb}"), ParseException.class); + assertThrowsException(() -> new SNBTParser("{20:10} {blah:blubb}").parse(), ParseException.class); // ------------------------------------------------- string tag - Tag st = assertThrowsNoException(() -> SNBTParser.parse("abc")); + Tag st = assertThrowsNoException(() -> new SNBTParser("abc").parse()); assertEquals(StringTag.class, st.getClass()); assertEquals("abc", ((StringTag) st).getValue()); - st = assertThrowsNoException(() -> SNBTParser.parse("\"abc\"")); + st = assertThrowsNoException(() -> new SNBTParser("\"abc\"").parse()); assertEquals(StringTag.class, st.getClass()); assertEquals("abc", ((StringTag) st).getValue()); - st = assertThrowsNoException(() -> SNBTParser.parse("123a")); + st = assertThrowsNoException(() -> new SNBTParser("123a").parse()); assertEquals(StringTag.class, st.getClass()); assertEquals("123a", ((StringTag) st).getValue()); // ------------------------------------------------- list tag - Tag lt = assertThrowsNoException(() -> SNBTParser.parse("[abc, \"def\", \"123\" ]")); + Tag lt = assertThrowsNoException(() -> new SNBTParser("[abc, \"def\", \"123\" ]").parse()); assertEquals(ListTag.class, lt.getClass()); assertEquals(StringTag.class, ((ListTag) lt).getTypeClass()); assertEquals(3, ((ListTag) lt).size()); @@ -138,12 +138,12 @@ public void testParse() { assertEquals("def", ((ListTag) lt).asStringTagList().get(1).getValue()); assertEquals("123", ((ListTag) lt).asStringTagList().get(2).getValue()); - assertThrowsException(() -> SNBTParser.parse("[123, 456"), ParseException.class); - assertThrowsException(() -> SNBTParser.parse("[123, 456d]"), ParseException.class); + assertThrowsException(() -> new SNBTParser("[123, 456").parse(), ParseException.class); + assertThrowsException(() -> new SNBTParser("[123, 456d]").parse(), ParseException.class); // ------------------------------------------------- compound tag - Tag ct = assertThrowsNoException(() -> SNBTParser.parse("{abc: def,\"key\": 123d, blah: [L;123, 456], blubb: [123, 456]}")); + Tag ct = assertThrowsNoException(() -> new SNBTParser("{abc: def,\"key\": 123d, blah: [L;123, 456], blubb: [123, 456]}").parse()); assertEquals(CompoundTag.class, ct.getClass()); assertEquals(4, ((CompoundTag) ct).size()); assertEquals("def", assertThrowsNoException(() -> ((CompoundTag) ct).getString("abc"))); @@ -152,8 +152,8 @@ public void testParse() { assertEquals(2, assertThrowsNoException(() -> ((CompoundTag) ct).getListTag("blubb")).size()); assertEquals(IntTag.class, ((CompoundTag) ct).getListTag("blubb").getTypeClass()); - assertThrowsException(() -> SNBTParser.parse("{abc: def"), ParseException.class); - assertThrowsException(() -> SNBTParser.parse("{\"\":empty}"), ParseException.class); - assertThrowsException(() -> SNBTParser.parse("{empty:}"), ParseException.class); + assertThrowsException(() -> new SNBTParser("{abc: def").parse(), ParseException.class); + assertThrowsException(() -> new SNBTParser("{\"\":empty}").parse(), ParseException.class); + assertThrowsException(() -> new SNBTParser("{empty:}").parse(), ParseException.class); } } From edb0403e0c46b199fb7b98ecc55c4ba60e973ac1 Mon Sep 17 00:00:00 2001 From: Querz Date: Fri, 26 Feb 2021 08:32:00 +0100 Subject: [PATCH 38/51] add helper method to get abstract number tag from compound tag --- src/main/java/net/querz/nbt/tag/CompoundTag.java | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/main/java/net/querz/nbt/tag/CompoundTag.java b/src/main/java/net/querz/nbt/tag/CompoundTag.java index 3909a72c..daa629cd 100644 --- a/src/main/java/net/querz/nbt/tag/CompoundTag.java +++ b/src/main/java/net/querz/nbt/tag/CompoundTag.java @@ -80,6 +80,14 @@ public Tag get(String key) { return getValue().get(key); } + public NumberTag getNumberTag(String key) { + return (NumberTag) getValue().get(key); + } + + public Number getNumber(String key) { + return getNumberTag(key).getValue(); + } + public ByteTag getByteTag(String key) { return get(key, ByteTag.class); } From de94fa511776853699909123872b57aa31da81a1 Mon Sep 17 00:00:00 2001 From: Querz Date: Tue, 6 Apr 2021 14:31:17 +0200 Subject: [PATCH 39/51] setting a chunks data version should update the sections data version too --- src/main/java/net/querz/mca/Chunk.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/main/java/net/querz/mca/Chunk.java b/src/main/java/net/querz/mca/Chunk.java index cfe06ce6..59347e29 100644 --- a/src/main/java/net/querz/mca/Chunk.java +++ b/src/main/java/net/querz/mca/Chunk.java @@ -328,6 +328,11 @@ public int getDataVersion() { public void setDataVersion(int dataVersion) { checkRaw(); this.dataVersion = dataVersion; + for (Section section : sections) { + if (section != null) { + section.dataVersion = dataVersion; + } + } } /** From d93ec5582d6d677e362ee88e13ad23a5e6706777 Mon Sep 17 00:00:00 2001 From: Querz Date: Tue, 6 Apr 2021 14:33:05 +0200 Subject: [PATCH 40/51] new BlockStates array length is not calculated correctly for DataVersion >= 2527 --- src/main/java/net/querz/mca/Section.java | 4 ++-- src/test/java/net/querz/mca/MCAFileTest.java | 19 +++++++++++++++++++ 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/src/main/java/net/querz/mca/Section.java b/src/main/java/net/querz/mca/Section.java index 312035a3..3fccc27a 100644 --- a/src/main/java/net/querz/mca/Section.java +++ b/src/main/java/net/querz/mca/Section.java @@ -18,7 +18,7 @@ public class Section { private byte[] blockLight; private long[] blockStates; private byte[] skyLight; - private int dataVersion; + int dataVersion; public Section(CompoundTag sectionRoot, int dataVersion) { this(sectionRoot, dataVersion, ALL_DATA); @@ -282,7 +282,7 @@ void adjustBlockStateBits(Map oldToNewMapping, long[] blockSta if (dataVersion < 2527) { newBlockStates = newBits == blockStates.length / 64 ? blockStates : new long[newBits * 64]; } else { - int newLength = (int) Math.ceil(4096D / (64D / newBits)); + int newLength = (int) Math.ceil(4096D / (Math.floor(64D / newBits))); newBlockStates = newBits == blockStates.length / 64 ? blockStates : new long[newLength]; } if (oldToNewMapping != null) { diff --git a/src/test/java/net/querz/mca/MCAFileTest.java b/src/test/java/net/querz/mca/MCAFileTest.java index 2688001a..dc9c130d 100644 --- a/src/test/java/net/querz/mca/MCAFileTest.java +++ b/src/test/java/net/querz/mca/MCAFileTest.java @@ -300,6 +300,25 @@ public void testSetBlockDataAt() { assertEquals(256, sss.get(0).getLongArray("BlockStates").length); } + public void testSetBlockDataAt2527() { + //test "line break" for DataVersion 2527 + MCAFile f = assertThrowsNoException(() -> MCAUtil.read(copyResourceToTmp("r.2.2.mca"))); + Chunk p = f.getChunk(0, 0); + p.setDataVersion(3000); + Section section = f.getChunk(0, 0).getSection(0); + assertEquals(10, section.getPalette().size()); + assertEquals(0b0001000100010001000100010001000100010001000100010001000100010001L, section.getBlockStates()[0]); + f.setBlockStateAt(0, 0, 0, block("minecraft:custom"), false); + assertEquals(11, section.getPalette().size()); + assertEquals(0b0001000100010001000100010001000100010001000100010001000100011010L, section.getBlockStates()[0]); + int y = 1; + for (int i = 12; i <= 17; i++) { + f.setBlockStateAt(0, y++, 0, block("minecraft:" + i), false); + } + assertEquals(17, section.getPalette().size()); + assertEquals(342, section.getBlockStates().length); + } + public void testGetBlockDataAt() { MCAFile f = assertThrowsNoException(() -> MCAUtil.read(copyResourceToTmp("r.2.2.mca"))); assertEquals(block("minecraft:bedrock"), f.getBlockStateAt(0, 0, 0)); From b5973ebffb5cdab768716c27477d9d34b5eef64a Mon Sep 17 00:00:00 2001 From: Querz Date: Tue, 6 Apr 2021 14:36:43 +0200 Subject: [PATCH 41/51] update version to 6.1 --- README.md | 2 +- build.gradle | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index e769b6dc..168c0be1 100644 --- a/README.md +++ b/README.md @@ -37,7 +37,7 @@ And then add it as a dependency as usual: ``` dependencies { ... - compile 'com.github.Querz:NBT:5.2' + compile 'com.github.Querz:NBT:6.1' } ``` diff --git a/build.gradle b/build.gradle index 4e79db37..48a12040 100644 --- a/build.gradle +++ b/build.gradle @@ -10,7 +10,7 @@ apply plugin: 'jacoco' group = 'net.querz.nbt' archivesBaseName = 'nbt' -version = '6.0' +version = '6.1' sourceCompatibility = '1.8' targetCompatibility = '1.8' compileJava.options.encoding = 'UTF-8' From 6d0980f9cbfadfd73d6abfb4d5f2457ac01aedd1 Mon Sep 17 00:00:00 2001 From: Querz Date: Wed, 7 Apr 2021 08:55:00 +0200 Subject: [PATCH 42/51] fix version for maven snippet --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 168c0be1..f7a7f6f5 100644 --- a/README.md +++ b/README.md @@ -56,7 +56,7 @@ Dependency: com.github.Querz NBT - Tag + 6.1 ``` From 10f85c27022c300e31d018c410739c801d4ff4e2 Mon Sep 17 00:00:00 2001 From: Uriel Salischiker Date: Fri, 9 Apr 2021 11:19:48 +0200 Subject: [PATCH 43/51] Add initial capacity to List and Compound tags --- .../java/net/querz/nbt/tag/CompoundTag.java | 11 +++- src/main/java/net/querz/nbt/tag/ListTag.java | 61 +++++++++++++------ 2 files changed, 51 insertions(+), 21 deletions(-) diff --git a/src/main/java/net/querz/nbt/tag/CompoundTag.java b/src/main/java/net/querz/nbt/tag/CompoundTag.java index daa629cd..9cdc3363 100644 --- a/src/main/java/net/querz/nbt/tag/CompoundTag.java +++ b/src/main/java/net/querz/nbt/tag/CompoundTag.java @@ -1,7 +1,5 @@ package net.querz.nbt.tag; -import net.querz.io.MaxDepthIO; - import java.util.Collection; import java.util.HashMap; import java.util.Iterator; @@ -10,7 +8,10 @@ import java.util.Set; import java.util.function.BiConsumer; -public class CompoundTag extends Tag>> implements Iterable>>, Comparable, MaxDepthIO { +import net.querz.io.MaxDepthIO; + +public class CompoundTag extends Tag>> + implements Iterable>>, Comparable, MaxDepthIO { public static final byte ID = 10; @@ -18,6 +19,10 @@ public CompoundTag() { super(createEmptyValue()); } + public CompoundTag(int initialCapacity) { + super(new HashMap<>(initialCapacity)); + } + @Override public byte getID() { return ID; diff --git a/src/main/java/net/querz/nbt/tag/ListTag.java b/src/main/java/net/querz/nbt/tag/ListTag.java index f26c4b07..955ad1a4 100644 --- a/src/main/java/net/querz/nbt/tag/ListTag.java +++ b/src/main/java/net/querz/nbt/tag/ListTag.java @@ -1,7 +1,5 @@ package net.querz.nbt.tag; -import net.querz.io.MaxDepthIO; - import java.util.ArrayList; import java.util.Collection; import java.util.Comparator; @@ -10,38 +8,53 @@ import java.util.Objects; import java.util.function.Consumer; +import net.querz.io.MaxDepthIO; + /** * ListTag represents a typed List in the nbt structure. * An empty {@link ListTag} will be of type {@link EndTag} (unknown type). * 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, Comparable>, MaxDepthIO { public static final byte ID = 9; private Class typeClass = null; - private ListTag() { - super(createEmptyValue(3)); + private ListTag(int initialCapacity) { + super(createEmptyValue(initialCapacity)); } @Override public byte getID() { return ID; } - + /** - *

Creates a non-type-safe ListTag. Its element type will be set after the first + *

Creates a non-type-safe ListTag. Its element type will be set after the first * element was added.

- * - *

This is an internal helper method for cases where the element type is not known + * + *

This is an internal helper method for cases where the element type is not known * at construction time. Use {@link #ListTag(Class)} when the type is known.

- * + * * @return A new non-type-safe ListTag */ public static ListTag createUnchecked(Class typeClass) { - ListTag list = new ListTag<>(); + return createUnchecked(typeClass, 3); + } + + /** + *

Creates a non-type-safe ListTag. Its element type will be set after the first + * element was added.

+ * + *

This is an internal helper method for cases where the element type is not known + * at construction time. Use {@link #ListTag(Class)} when the type is known.

+ * + * @return A new non-type-safe ListTag + */ + public static ListTag createUnchecked(Class typeClass, int initialCapacity) { + ListTag list = new ListTag<>(initialCapacity); list.typeClass = typeClass; return list; } @@ -49,10 +62,10 @@ public static ListTag createUnchecked(Class typeClass) { /** *

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

* - * @param Type of the list elements + * @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); } @@ -60,10 +73,20 @@ private static List createEmptyValue(int 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} + * @throws NullPointerException When {@code typeClass} is {@code null} */ public ListTag(Class typeClass) throws IllegalArgumentException, NullPointerException { - super(createEmptyValue(3)); + this(typeClass, 3); + } + + /** + * @param typeClass The exact class of the elements + * @param initialCapacity Initial capacity of list + * @throws IllegalArgumentException When {@code typeClass} is {@link EndTag}{@code .class} + * @throws NullPointerException When {@code typeClass} is {@code null} + */ + public ListTag(Class typeClass, int initialCapacity) throws IllegalArgumentException, NullPointerException { + super(createEmptyValue(initialCapacity)); if (typeClass == EndTag.class) { throw new IllegalArgumentException("cannot create ListTag with EndTag elements"); } @@ -114,8 +137,9 @@ public T set(int index, T t) { /** * Adds a Tag to this ListTag after the last index. + * * @param t The element to be added. - * */ + */ public void add(T t) { add(size(), t); } @@ -271,7 +295,8 @@ public boolean equals(Object other) { if (this == other) { return true; } - if (!super.equals(other) || size() != ((ListTag) other).size() || getTypeClass() != ((ListTag) other).getTypeClass()) { + if (!super.equals(other) || size() != ((ListTag) other).size() || getTypeClass() != ((ListTag) other) + .getTypeClass()) { return false; } for (int i = 0; i < size(); i++) { @@ -295,7 +320,7 @@ public int compareTo(ListTag o) { @SuppressWarnings("unchecked") @Override public ListTag clone() { - ListTag copy = new ListTag<>(); + ListTag copy = new ListTag<>(this.size()); // assure type safety for clone copy.typeClass = typeClass; for (T t : getValue()) { From 67c0f39ce968843c21d7806cc427103eadd65ec9 Mon Sep 17 00:00:00 2001 From: Uriel Salischiker Date: Fri, 16 Apr 2021 14:44:12 +0200 Subject: [PATCH 44/51] Clone with correct size to avoid resizing --- src/main/java/net/querz/nbt/tag/CompoundTag.java | 2 +- src/main/java/net/querz/nbt/tag/ListTag.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/net/querz/nbt/tag/CompoundTag.java b/src/main/java/net/querz/nbt/tag/CompoundTag.java index 9cdc3363..6c754cd7 100644 --- a/src/main/java/net/querz/nbt/tag/CompoundTag.java +++ b/src/main/java/net/querz/nbt/tag/CompoundTag.java @@ -282,7 +282,7 @@ public int compareTo(CompoundTag o) { @Override public CompoundTag clone() { - CompoundTag copy = new CompoundTag(); + CompoundTag copy = new CompoundTag(((int) Math.ceil(getValue().size() * 1.75f)) + 1); for (Map.Entry> e : getValue().entrySet()) { copy.put(e.getKey(), e.getValue().clone()); } diff --git a/src/main/java/net/querz/nbt/tag/ListTag.java b/src/main/java/net/querz/nbt/tag/ListTag.java index 955ad1a4..6f381351 100644 --- a/src/main/java/net/querz/nbt/tag/ListTag.java +++ b/src/main/java/net/querz/nbt/tag/ListTag.java @@ -320,7 +320,7 @@ public int compareTo(ListTag o) { @SuppressWarnings("unchecked") @Override public ListTag clone() { - ListTag copy = new ListTag<>(this.size()); + ListTag copy = new ListTag<>(((int) Math.ceil(this.size() * 1.75)) + 1); // assure type safety for clone copy.typeClass = typeClass; for (T t : getValue()) { From 36329fe8e1adb91a1da7d63fb4c0770b47042137 Mon Sep 17 00:00:00 2001 From: Uriel Salischiker Date: Fri, 16 Apr 2021 16:05:48 +0200 Subject: [PATCH 45/51] Clone with correct size to avoid resizing --- src/main/java/net/querz/nbt/tag/CompoundTag.java | 2 +- src/main/java/net/querz/nbt/tag/ListTag.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/net/querz/nbt/tag/CompoundTag.java b/src/main/java/net/querz/nbt/tag/CompoundTag.java index 6c754cd7..28145891 100644 --- a/src/main/java/net/querz/nbt/tag/CompoundTag.java +++ b/src/main/java/net/querz/nbt/tag/CompoundTag.java @@ -282,7 +282,7 @@ public int compareTo(CompoundTag o) { @Override public CompoundTag clone() { - CompoundTag copy = new CompoundTag(((int) Math.ceil(getValue().size() * 1.75f)) + 1); + CompoundTag copy = new CompoundTag((int) Math.ceil(getValue().size() * 1.33f)); for (Map.Entry> e : getValue().entrySet()) { copy.put(e.getKey(), e.getValue().clone()); } diff --git a/src/main/java/net/querz/nbt/tag/ListTag.java b/src/main/java/net/querz/nbt/tag/ListTag.java index 6f381351..955ad1a4 100644 --- a/src/main/java/net/querz/nbt/tag/ListTag.java +++ b/src/main/java/net/querz/nbt/tag/ListTag.java @@ -320,7 +320,7 @@ public int compareTo(ListTag o) { @SuppressWarnings("unchecked") @Override public ListTag clone() { - ListTag copy = new ListTag<>(((int) Math.ceil(this.size() * 1.75)) + 1); + ListTag copy = new ListTag<>(this.size()); // assure type safety for clone copy.typeClass = typeClass; for (T t : getValue()) { From e679810c150c0d185f242b1eae0516230ea86c08 Mon Sep 17 00:00:00 2001 From: Uriel Salischiker Date: Tue, 20 Apr 2021 01:07:04 +0200 Subject: [PATCH 46/51] Update src/main/java/net/querz/nbt/tag/CompoundTag.java Co-authored-by: Marcono1234 --- src/main/java/net/querz/nbt/tag/CompoundTag.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/net/querz/nbt/tag/CompoundTag.java b/src/main/java/net/querz/nbt/tag/CompoundTag.java index 28145891..cbc73763 100644 --- a/src/main/java/net/querz/nbt/tag/CompoundTag.java +++ b/src/main/java/net/querz/nbt/tag/CompoundTag.java @@ -282,7 +282,8 @@ public int compareTo(CompoundTag o) { @Override public CompoundTag clone() { - CompoundTag copy = new CompoundTag((int) Math.ceil(getValue().size() * 1.33f)); + // Choose initial capacity based on default load factor (0.75) so all entries fit in map without resizing + CompoundTag copy = new CompoundTag((int) Math.ceil(getValue().size() / 0.75f)); for (Map.Entry> e : getValue().entrySet()) { copy.put(e.getKey(), e.getValue().clone()); } From 53d2bff26ecbdf6b1d9541fa939c2c23d5e820f6 Mon Sep 17 00:00:00 2001 From: Uriel Salischiker Date: Fri, 9 Apr 2021 11:54:24 +0200 Subject: [PATCH 47/51] Add put if not null to CompoundTag --- .../java/net/querz/nbt/tag/CompoundTag.java | 7 +++ .../net/querz/nbt/tag/CompoundTagTest.java | 52 ++++++++++++------- 2 files changed, 40 insertions(+), 19 deletions(-) diff --git a/src/main/java/net/querz/nbt/tag/CompoundTag.java b/src/main/java/net/querz/nbt/tag/CompoundTag.java index cbc73763..f34db876 100644 --- a/src/main/java/net/querz/nbt/tag/CompoundTag.java +++ b/src/main/java/net/querz/nbt/tag/CompoundTag.java @@ -200,6 +200,13 @@ public Tag put(String key, Tag tag) { return getValue().put(Objects.requireNonNull(key), Objects.requireNonNull(tag)); } + public Tag putIfNotNull(String key, Tag tag) { + if (tag == null) { + return this; + } + return put(key, tag); + } + public Tag putBoolean(String key, boolean value) { return put(key, new ByteTag(value)); } diff --git a/src/test/java/net/querz/nbt/tag/CompoundTagTest.java b/src/test/java/net/querz/nbt/tag/CompoundTagTest.java index fcfd73ca..e08426f4 100644 --- a/src/test/java/net/querz/nbt/tag/CompoundTagTest.java +++ b/src/test/java/net/querz/nbt/tag/CompoundTagTest.java @@ -6,6 +6,8 @@ import java.util.Arrays; import java.util.LinkedHashMap; import java.util.Map; + +import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertNotEquals; public class CompoundTagTest extends NBTTestCase { @@ -37,16 +39,16 @@ public void testEquals() { ct2.putString("str", "foo"); ct2.put("list", new ListTag<>(ByteTag.class)); ct2.getListTag("list").addByte((byte) 123); - assertTrue(ct.equals(ct2)); + assertEquals(ct, ct2); ct2.getListTag("list").asByteTagList().get(0).setValue((byte) 124); - assertFalse(ct.equals(ct2)); + assertNotEquals(ct, ct2); ct2.remove("str"); - assertFalse(ct.equals(ct2)); + assertNotEquals(ct, ct2); assertThrowsNoRuntimeException(() -> ct.equals("blah")); - assertFalse(ct.equals("blah")); + assertNotEquals("blah", ct); assertEquals(ct, ct); } @@ -137,10 +139,10 @@ public void testHashCode() { public void testClone() { CompoundTag ct = createCompoundTag(); CompoundTag cl = ct.clone(); - assertTrue(ct.equals(cl)); - assertFalse(ct == cl); - assertFalse(ct.get("list") == cl.get("list")); - assertFalse(invokeGetValue(ct) == invokeGetValue(cl)); + assertEquals(ct, cl); + assertNotSame(ct, cl); + assertNotSame(ct.get("list"), cl.get("list")); + assertNotSame(invokeGetValue(ct), invokeGetValue(cl)); } public void testClear() { @@ -154,9 +156,12 @@ public void testClear() { public void testSerializeDeserialize() { CompoundTag ct = createCompoundTag(); byte[] data = serialize(ct); - assertTrue(Arrays.equals(new byte[]{10, 0, 0, 1, 0, 1, 98, 127, 8, 0, 3, 115, 116, 114, 0, 3, 102, 111, 111, 9, 0, 4, 108, 105, 115, 116, 1, 0, 0, 0, 1, 123, 0}, data)); + assertArrayEquals( + new byte[] { 10, 0, 0, 1, 0, 1, 98, 127, 8, 0, 3, 115, 116, 114, 0, 3, 102, 111, 111, 9, 0, 4, 108, 105, 115, 116, + 1, 0, 0, 0, 1, 123, 0 + }, data); CompoundTag tt = (CompoundTag) deserialize(data); - assertTrue(ct.equals(tt)); + assertEquals(ct, tt); } public void testCasting() { @@ -169,9 +174,9 @@ public void testCasting() { assertEquals(Byte.MAX_VALUE, cc.get("b", ByteTag.class).asByte()); assertThrowsRuntimeException(() -> cc.getShort("b"), ClassCastException.class); assertEquals(0, cc.getByte("bb")); - assertEquals(true, cc.getBoolean("b")); + assertTrue(cc.getBoolean("b")); cc.putByte("b2", (byte) 0); - assertEquals(false, cc.getBoolean("b2")); + assertFalse(cc.getBoolean("b2")); cc.putBoolean("b3", false); assertEquals(0, cc.getByte("b3")); cc.putBoolean("b4", true); @@ -222,23 +227,23 @@ public void testCasting() { cc.putByteArray("ba", new byte[]{Byte.MIN_VALUE, 0, Byte.MAX_VALUE}); assertEquals(new ByteArrayTag(new byte[]{Byte.MIN_VALUE, 0, Byte.MAX_VALUE}), cc.getByteArrayTag("ba")); assertNull(cc.getByteArrayTag("baba")); - assertTrue(Arrays.equals(new byte[]{Byte.MIN_VALUE, 0, Byte.MAX_VALUE}, cc.get("ba", ByteArrayTag.class).getValue())); + assertArrayEquals(new byte[] { Byte.MIN_VALUE, 0, Byte.MAX_VALUE }, cc.get("ba", ByteArrayTag.class).getValue()); assertThrowsRuntimeException(() -> cc.getIntArray("ba"), ClassCastException.class); - assertTrue(Arrays.equals(new byte[0], cc.getByteArray("baba"))); + assertArrayEquals(new byte[0], cc.getByteArray("baba")); cc.putIntArray("ia", new int[]{Integer.MIN_VALUE, 0, Integer.MAX_VALUE}); assertEquals(new IntArrayTag(new int[]{Integer.MIN_VALUE, 0, Integer.MAX_VALUE}), cc.getIntArrayTag("ia")); assertNull(cc.getIntArrayTag("iaia")); - assertTrue(Arrays.equals(new int[]{Integer.MIN_VALUE, 0, Integer.MAX_VALUE}, cc.get("ia", IntArrayTag.class).getValue())); + assertArrayEquals(new int[] { Integer.MIN_VALUE, 0, Integer.MAX_VALUE }, cc.get("ia", IntArrayTag.class).getValue()); assertThrowsRuntimeException(() -> cc.getLongArray("ia"), ClassCastException.class); - assertTrue(Arrays.equals(new int[0], cc.getIntArray("iaia"))); + assertArrayEquals(new int[0], cc.getIntArray("iaia")); cc.putLongArray("la", new long[]{Long.MIN_VALUE, 0, Long.MAX_VALUE}); assertEquals(new LongArrayTag(new long[]{Long.MIN_VALUE, 0, Long.MAX_VALUE}), cc.getLongArrayTag("la")); assertNull(cc.getLongArrayTag("lala")); - assertTrue(Arrays.equals(new long[]{Long.MIN_VALUE, 0, Long.MAX_VALUE}, cc.get("la", LongArrayTag.class).getValue())); + assertArrayEquals(new long[] { Long.MIN_VALUE, 0, Long.MAX_VALUE }, cc.get("la", LongArrayTag.class).getValue()); assertThrowsRuntimeException(() -> cc.getListTag("la"), ClassCastException.class); - assertTrue(Arrays.equals(new long[0], cc.getLongArray("lala"))); + assertArrayEquals(new long[0], cc.getLongArray("lala")); cc.put("li", new ListTag<>(IntTag.class)); assertEquals(new ListTag<>(IntTag.class), cc.getListTag("li")); @@ -302,7 +307,7 @@ public void testEntrySet() { public void testContains() { CompoundTag ct = createCompoundTag(); - assertTrue(3 == ct.size()); + assertEquals(3, ct.size()); assertTrue(ct.containsKey("b")); assertTrue(ct.containsKey("str")); assertTrue(ct.containsKey("list")); @@ -359,4 +364,13 @@ public void testIterator() { }); assertEquals(3, ct.size()); } + + public void testPutIfNotNull() { + CompoundTag ct = new CompoundTag(); + assertEquals(0, ct.size()); + ct.putIfNotNull("foo", new StringTag("bar")); + ct.putIfNotNull("bar", null); + assertEquals(1, ct.size()); + assertEquals("bar", ct.getString("foo")); + } } From e7f260dc5c5453da0475aacd11eac7f7bb56abb9 Mon Sep 17 00:00:00 2001 From: Querz Date: Wed, 7 Apr 2021 08:52:48 +0200 Subject: [PATCH 48/51] make MCAFile, Chunk and Section iterable --- src/main/java/net/querz/mca/Chunk.java | 43 +++++++------ src/main/java/net/querz/mca/MCAFile.java | 12 +++- src/main/java/net/querz/mca/Section.java | 77 +++++++++++++++++++++++- 3 files changed, 109 insertions(+), 23 deletions(-) diff --git a/src/main/java/net/querz/mca/Chunk.java b/src/main/java/net/querz/mca/Chunk.java index 59347e29..7c3c620f 100644 --- a/src/main/java/net/querz/mca/Chunk.java +++ b/src/main/java/net/querz/mca/Chunk.java @@ -12,9 +12,13 @@ import java.io.IOException; import java.io.RandomAccessFile; import java.util.Arrays; +import java.util.Iterator; +import java.util.Map; +import java.util.TreeMap; + import static net.querz.mca.LoadFlags.*; -public class Chunk { +public class Chunk implements Iterable
{ public static final int DEFAULT_DATA_VERSION = 2567; @@ -31,7 +35,7 @@ public class Chunk { private int[] biomes; private CompoundTag heightMaps; private CompoundTag carvingMasks; - private Section[] sections = new Section[16]; //always initialized with size = 16 for fast access + private Map sections = new TreeMap<>(); private ListTag entities; private ListTag tileEntities; private ListTag tileTicks; @@ -112,15 +116,9 @@ private void initReferences(long loadFlags) { } if ((loadFlags & (BLOCK_LIGHTS|BLOCK_STATES|SKY_LIGHT)) != 0 && level.containsKey("Sections")) { for (CompoundTag section : level.getListTag("Sections").asCompoundTagList()) { - int sectionIndex = section.getByte("Y"); - if (sectionIndex > 15 || sectionIndex < 0) { - continue; - } + int sectionIndex = section.getNumber("Y").byteValue(); Section newSection = new Section(section, dataVersion, loadFlags); - if (newSection.isEmpty()) { - continue; - } - sections[sectionIndex] = newSection; + sections.put(sectionIndex, newSection); } } @@ -285,7 +283,7 @@ int getBiomeIndex(int biomeX, int biomeY, int biomeZ) { } public CompoundTag getBlockStateAt(int blockX, int blockY, int blockZ) { - Section section = sections[MCAUtil.blockToChunk(blockY)]; + Section section = sections.get(MCAUtil.blockToChunk(blockY)); if (section == null) { return null; } @@ -306,9 +304,9 @@ public CompoundTag getBlockStateAt(int blockX, int blockY, int blockZ) { public void setBlockStateAt(int blockX, int blockY, int blockZ, CompoundTag state, boolean cleanup) { checkRaw(); int sectionIndex = MCAUtil.blockToChunk(blockY); - Section section = sections[sectionIndex]; + Section section = sections.get(sectionIndex); if (section == null) { - section = sections[sectionIndex] = Section.newSection(); + sections.put(sectionIndex, section = Section.newSection()); } section.setBlockStateAt(blockX, blockY, blockZ, state, cleanup); } @@ -328,7 +326,7 @@ public int getDataVersion() { public void setDataVersion(int dataVersion) { checkRaw(); this.dataVersion = dataVersion; - for (Section section : sections) { + for (Section section : sections.values()) { if (section != null) { section.dataVersion = dataVersion; } @@ -373,7 +371,7 @@ public void setStatus(String status) { * @return The Section. */ public Section getSection(int sectionY) { - return sections[sectionY]; + return sections.get(sectionY); } /** @@ -383,7 +381,7 @@ public Section getSection(int sectionY) { */ public void setSection(int sectionY, Section section) { checkRaw(); - sections[sectionY] = section; + sections.put(sectionY, section); } /** @@ -623,7 +621,7 @@ int getBlockIndex(int blockX, int blockZ) { public void cleanupPalettesAndBlockStates() { checkRaw(); - for (Section section : sections) { + for (Section section : sections.values()) { if (section != null) { section.cleanupPaletteAndBlockStates(); } @@ -712,12 +710,17 @@ public CompoundTag updateHandle(int xPos, int zPos) { level.put("Structures", structures); } ListTag sections = new ListTag<>(CompoundTag.class); - for (int i = 0; i < this.sections.length; i++) { - if (this.sections[i] != null) { - sections.add(this.sections[i].updateHandle(i)); + for (Section section : this.sections.values()) { + if (section != null) { + sections.add(section.updateHandle()); } } level.put("Sections", sections); return data; } + + @Override + public Iterator
iterator() { + return sections.values().iterator(); + } } diff --git a/src/main/java/net/querz/mca/MCAFile.java b/src/main/java/net/querz/mca/MCAFile.java index 92037224..0a6e7122 100644 --- a/src/main/java/net/querz/mca/MCAFile.java +++ b/src/main/java/net/querz/mca/MCAFile.java @@ -1,10 +1,15 @@ package net.querz.mca; import net.querz.nbt.tag.CompoundTag; +import net.querz.nbt.tag.Tag; + import java.io.IOException; import java.io.RandomAccessFile; +import java.util.Arrays; +import java.util.Iterator; +import java.util.Map; -public class MCAFile { +public class MCAFile implements Iterable { /** * The default chunk data version used when no custom version is supplied. @@ -292,4 +297,9 @@ public void cleanupPalettesAndBlockStates() { } } } + + @Override + public Iterator iterator() { + return Arrays.stream(chunks).iterator(); + } } diff --git a/src/main/java/net/querz/mca/Section.java b/src/main/java/net/querz/mca/Section.java index 3fccc27a..ddaf1818 100644 --- a/src/main/java/net/querz/mca/Section.java +++ b/src/main/java/net/querz/mca/Section.java @@ -7,10 +7,11 @@ import net.querz.nbt.tag.LongArrayTag; import java.util.ArrayList; import java.util.HashMap; +import java.util.Iterator; import java.util.List; import java.util.Map; -public class Section { +public class Section implements Comparable
{ private CompoundTag data; private Map> valueIndexedPalette = new HashMap<>(); @@ -18,6 +19,7 @@ public class Section { private byte[] blockLight; private long[] blockStates; private byte[] skyLight; + private int height; int dataVersion; public Section(CompoundTag sectionRoot, int dataVersion) { @@ -27,6 +29,8 @@ public Section(CompoundTag sectionRoot, int dataVersion) { public Section(CompoundTag sectionRoot, int dataVersion, long loadFlags) { data = sectionRoot; this.dataVersion = dataVersion; + height = sectionRoot.getNumber("Y").byteValue(); + ListTag rawPalette = sectionRoot.getListTag("Palette"); if (rawPalette == null) { return; @@ -85,6 +89,14 @@ PaletteIndex getValueIndexedPalette(CompoundTag data) { return null; } + @Override + public int compareTo(Section o) { + if (o == null) { + return -1; + } + return Integer.compare(height, o.height); + } + private static class PaletteIndex { CompoundTag data; @@ -104,6 +116,17 @@ public boolean isEmpty() { return data == null; } + /** + * @return the Y value of this section. + * */ + public int getHeight() { + return height; + } + + public void setHeight(int height) { + this.height = height; + } + /** * Fetches a block state based on a block location from this section. * The coordinates represent the location of the block inside of this Section. @@ -113,7 +136,10 @@ public boolean isEmpty() { * @return The block state data of this block. */ public CompoundTag getBlockStateAt(int blockX, int blockY, int blockZ) { - int index = getBlockIndex(blockX, blockY, blockZ); + return getBlockStateAt(getBlockIndex(blockX, blockY, blockZ)); + } + + private CompoundTag getBlockStateAt(int index) { int paletteIndex = getPaletteIndex(index); return palette.get(paletteIndex); } @@ -395,4 +421,51 @@ public CompoundTag updateHandle(int y) { } return data; } + + public CompoundTag updateHandle() { + return updateHandle(height); + } + + /** + * Creates an iterable that iterates over all blocks in this section, in order of their indices. + * An index can be calculated using the following formula: + *
+	 * {@code
+	 * index = (blockY & 0xF) * 256 + (blockZ & 0xF) * 16 + (blockX & 0xF);
+	 * }
+	 * 
+ * The CompoundTags are references to this Section's Palette and should only be modified if the intention is to + * modify ALL blocks of the same type in this Section at the same time. + * */ + public Iterable blocksStates() { + return new BlockIterator(this); + } + + private static class BlockIterator implements Iterable, Iterator { + + private Section section; + private int currentIndex; + + public BlockIterator(Section section) { + this.section = section; + currentIndex = 0; + } + + @Override + public boolean hasNext() { + return currentIndex < 4096; + } + + @Override + public CompoundTag next() { + CompoundTag blockState = section.getBlockStateAt(currentIndex); + currentIndex++; + return blockState; + } + + @Override + public Iterator iterator() { + return this; + } + } } From e08a0eb13464c6ec7af40ebdd7f7b9f8cddf9f7d Mon Sep 17 00:00:00 2001 From: Querz Date: Mon, 12 Apr 2021 10:31:35 +0200 Subject: [PATCH 49/51] add block states null check before attempting to cleanup palette --- src/main/java/net/querz/mca/Section.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/main/java/net/querz/mca/Section.java b/src/main/java/net/querz/mca/Section.java index ddaf1818..3b72b969 100644 --- a/src/main/java/net/querz/mca/Section.java +++ b/src/main/java/net/querz/mca/Section.java @@ -265,8 +265,10 @@ static long bitRange(long value, int from, int to) { * Recalculating the Palette should only be executed once right before saving the Section to file. */ public void cleanupPaletteAndBlockStates() { - Map oldToNewMapping = cleanupPalette(); - adjustBlockStateBits(oldToNewMapping, blockStates); + if (blockStates != null) { + Map oldToNewMapping = cleanupPalette(); + adjustBlockStateBits(oldToNewMapping, blockStates); + } } private Map cleanupPalette() { From 930e04e06b53627d3efcd56e5169ab98a0932c0c Mon Sep 17 00:00:00 2001 From: Querz Date: Tue, 20 Jul 2021 22:17:40 +0200 Subject: [PATCH 50/51] use implementation instead of compile for gradle dependency --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index f7a7f6f5..b629063b 100644 --- a/README.md +++ b/README.md @@ -37,7 +37,7 @@ And then add it as a dependency as usual: ``` dependencies { ... - compile 'com.github.Querz:NBT:6.1' + implementation 'com.github.Querz:NBT:6.1' } ``` From 19ca4ebbfd75114811b8a74177445e07d54a423b Mon Sep 17 00:00:00 2001 From: Artem Veselov Date: Tue, 7 Jun 2022 20:37:49 +0100 Subject: [PATCH 51/51] attempt to make compatible with 1.19 --- build.gradle | 3 +- src/main/java/net/querz/mca/Chunk.java | 38 ++++++++++++-------------- 2 files changed, 19 insertions(+), 22 deletions(-) diff --git a/build.gradle b/build.gradle index 48a12040..c3c1eaf9 100644 --- a/build.gradle +++ b/build.gradle @@ -10,12 +10,13 @@ apply plugin: 'jacoco' group = 'net.querz.nbt' archivesBaseName = 'nbt' -version = '6.1' +version = '6.1-SNAPSHOT' sourceCompatibility = '1.8' targetCompatibility = '1.8' compileJava.options.encoding = 'UTF-8' repositories { + mavenLocal() jcenter() } diff --git a/src/main/java/net/querz/mca/Chunk.java b/src/main/java/net/querz/mca/Chunk.java index 7c3c620f..dd84e1fe 100644 --- a/src/main/java/net/querz/mca/Chunk.java +++ b/src/main/java/net/querz/mca/Chunk.java @@ -70,52 +70,48 @@ private void initReferences(long loadFlags) { return; } - CompoundTag level; - if ((level = data.getCompoundTag("Level")) == null) { - throw new IllegalArgumentException("data does not contain \"Level\" tag"); - } dataVersion = data.getInt("DataVersion"); - inhabitedTime = level.getLong("InhabitedTime"); - lastUpdate = level.getLong("LastUpdate"); + inhabitedTime = data.getLong("InhabitedTime"); + lastUpdate = data.getLong("LastUpdate"); if ((loadFlags & BIOMES) != 0) { - biomes = level.getIntArray("Biomes"); + biomes = data.getIntArray("Biomes"); } if ((loadFlags & HEIGHTMAPS) != 0) { - heightMaps = level.getCompoundTag("Heightmaps"); + heightMaps = data.getCompoundTag("Heightmaps"); } if ((loadFlags & CARVING_MASKS) != 0) { - carvingMasks = level.getCompoundTag("CarvingMasks"); + carvingMasks = data.getCompoundTag("CarvingMasks"); } if ((loadFlags & ENTITIES) != 0) { - entities = level.containsKey("Entities") ? level.getListTag("Entities").asCompoundTagList() : null; + entities = data.containsKey("Entities") ? data.getListTag("Entities").asCompoundTagList() : null; } if ((loadFlags & TILE_ENTITIES) != 0) { - tileEntities = level.containsKey("TileEntities") ? level.getListTag("TileEntities").asCompoundTagList() : null; + tileEntities = data.containsKey("TileEntities") ? data.getListTag("TileEntities").asCompoundTagList() : null; } if ((loadFlags & TILE_TICKS) != 0) { - tileTicks = level.containsKey("TileTicks") ? level.getListTag("TileTicks").asCompoundTagList() : null; + tileTicks = data.containsKey("TileTicks") ? data.getListTag("TileTicks").asCompoundTagList() : null; } if ((loadFlags & LIQUID_TICKS) != 0) { - liquidTicks = level.containsKey("LiquidTicks") ? level.getListTag("LiquidTicks").asCompoundTagList() : null; + liquidTicks = data.containsKey("LiquidTicks") ? data.getListTag("LiquidTicks").asCompoundTagList() : null; } if ((loadFlags & LIGHTS) != 0) { - lights = level.containsKey("Lights") ? level.getListTag("Lights").asListTagList() : null; + lights = data.containsKey("Lights") ? data.getListTag("Lights").asListTagList() : null; } if ((loadFlags & LIQUIDS_TO_BE_TICKED) != 0) { - liquidsToBeTicked = level.containsKey("LiquidsToBeTicked") ? level.getListTag("LiquidsToBeTicked").asListTagList() : null; + liquidsToBeTicked = data.containsKey("LiquidsToBeTicked") ? data.getListTag("LiquidsToBeTicked").asListTagList() : null; } if ((loadFlags & TO_BE_TICKED) != 0) { - toBeTicked = level.containsKey("ToBeTicked") ? level.getListTag("ToBeTicked").asListTagList() : null; + toBeTicked = data.containsKey("ToBeTicked") ? data.getListTag("ToBeTicked").asListTagList() : null; } if ((loadFlags & POST_PROCESSING) != 0) { - postProcessing = level.containsKey("PostProcessing") ? level.getListTag("PostProcessing").asListTagList() : null; + postProcessing = data.containsKey("PostProcessing") ? data.getListTag("PostProcessing").asListTagList() : null; } - status = level.getString("Status"); + status = data.getString("Status"); if ((loadFlags & STRUCTURES) != 0) { - structures = level.getCompoundTag("Structures"); + structures = data.getCompoundTag("Structures"); } - if ((loadFlags & (BLOCK_LIGHTS|BLOCK_STATES|SKY_LIGHT)) != 0 && level.containsKey("Sections")) { - for (CompoundTag section : level.getListTag("Sections").asCompoundTagList()) { + if ((loadFlags & (BLOCK_LIGHTS|BLOCK_STATES|SKY_LIGHT)) != 0 && data.containsKey("Sections")) { + for (CompoundTag section : data.getListTag("Sections").asCompoundTagList()) { int sectionIndex = section.getNumber("Y").byteValue(); Section newSection = new Section(section, dataVersion, loadFlags); sections.put(sectionIndex, newSection);