diff --git a/build.gradle b/build.gradle index 1a0f1a84..cd68a75d 100644 --- a/build.gradle +++ b/build.gradle @@ -46,7 +46,13 @@ compileJava { sourceCompatibility = targetCompatibility = JavaVersion.VERSION_1_8 } +configurations { + embed + compile.extendsFrom embed +} + repositories { + mavenCentral() maven { url "http://dvs1.progwml6.com/files/maven" } // JEI maven { url "http://maven.cil.li/" } // Sedna } @@ -54,8 +60,11 @@ repositories { dependencies { minecraft "net.minecraftforge:forge:${minecraft_version}-${forge_version}" - compile 'li.cil.sedna:sedna:0.0.1+' - compile 'li.cil.sedna:sedna-buildroot:0.0.1+' + compileOnly 'org.jetbrains:annotations:16.0.2' + + embed 'li.cil.ceres:ceres:0.0.1+' + embed 'li.cil.sedna:sedna:0.0.1+' + embed 'li.cil.sedna:sedna-buildroot:0.0.1+' compileOnly fg.deobf("mezz.jei:jei-${minecraft_version}:${jei_version}:api") runtimeOnly fg.deobf("mezz.jei:jei-${minecraft_version}:${jei_version}") @@ -127,21 +136,57 @@ minecraft { } } -jar { - manifest { - attributes([ - "Specification-Title" : "oc2", - "Specification-Vendor" : "Sangar", - "Specification-Version" : "1", - "Implementation-Title" : project.name, - "Implementation-Version" : "${semver}", - "Implementation-Vendor" : "Sangar", - "Implementation-Timestamp": new Date().format("yyyy-MM-dd'T'HH:mm:ssZ") - ]) +task generateMetaFiles { + ext.embeddedFiles = [] + doLast { + file("${buildDir}/dependencyMeta/").deleteDir() + configurations.embed.resolvedConfiguration.resolvedArtifacts.each { + // Don't embed anything Minecraft provides anyway. + if (configurations.minecraft.resolvedConfiguration.resolvedArtifacts.contains(it)) { + return + } + + ext.embeddedFiles.add(it.file) + + def metaFile = file("${buildDir}/dependencyMeta/${it.file.name}.meta") + metaFile.parentFile.mkdirs() + def artifactRef = it.moduleVersion.toString() + if (it.classifier != null) { + artifactRef += ":${it.classifier}" + } + metaFile.text = "Maven-Artifact: $artifactRef" + } } } -jar.finalizedBy('reobfJar') +task embedFilesInJar { + dependsOn generateMetaFiles + doLast { + jar { + into('/') { + from generateMetaFiles.embeddedFiles + from "${buildDir}/dependencyMeta/" + } + manifest { + attributes([ + "Specification-Title" : "oc2", + "Specification-Vendor" : "Sangar", + "Specification-Version" : "1", + "Implementation-Title" : project.name, + "Implementation-Version" : "${semver}", + "Implementation-Vendor" : "Sangar", + "Implementation-Timestamp": new Date().format("yyyy-MM-dd'T'HH:mm:ssZ"), + 'ContainedDeps' : generateMetaFiles.embeddedFiles.collect { it.name }.join(' ') + ]) + } + } + } +} + +jar { + dependsOn embedFilesInJar + finalizedBy 'reobfJar' +} task apiJar(type: Jar) { from sourceSets.main.allSource diff --git a/src/main/java/li/cil/oc2/client/gui/terminal/Terminal.java b/src/main/java/li/cil/oc2/client/gui/terminal/Terminal.java index 31450fa0..9898732c 100644 --- a/src/main/java/li/cil/oc2/client/gui/terminal/Terminal.java +++ b/src/main/java/li/cil/oc2/client/gui/terminal/Terminal.java @@ -4,6 +4,7 @@ import com.mojang.blaze3d.matrix.MatrixStack; import com.mojang.blaze3d.platform.GlStateManager; import com.mojang.blaze3d.systems.RenderSystem; import it.unimi.dsi.fastutil.bytes.ByteArrayFIFOQueue; +import li.cil.ceres.api.Serialized; import li.cil.oc2.client.render.font.FontRenderer; import li.cil.oc2.client.render.font.MonospaceFontRenderer; import net.minecraft.client.Minecraft; @@ -13,7 +14,6 @@ import net.minecraft.client.renderer.Matrix4f; import net.minecraft.client.renderer.Tessellator; import net.minecraft.client.renderer.WorldVertexBufferUploader; import net.minecraft.client.renderer.vertex.DefaultVertexFormats; -import net.minecraft.nbt.CompoundNBT; import net.minecraft.state.properties.NoteBlockInstrument; import net.minecraftforge.api.distmarker.Dist; import net.minecraftforge.api.distmarker.OnlyIn; @@ -25,20 +25,21 @@ import java.util.Arrays; import java.util.concurrent.atomic.AtomicInteger; // Implements a couple of control sequences from here: https://en.wikipedia.org/wiki/ANSI_escape_code#CSI_sequences +@Serialized public final class Terminal { private static final int TAB_WIDTH = 4; private static final int WIDTH = 80, HEIGHT = 24; - private enum State { + public enum State { // Must be public for serialization. NORMAL, // Currently reading characters normally. ESCAPE, // Last character was ESC, figure out what kind next. SEQUENCE, // Know what sequence we have, now parsing it. } private final ByteArrayFIFOQueue input = new ByteArrayFIFOQueue(32); - private final byte[] buffer = new byte[WIDTH * HEIGHT]; + private byte[] buffer = new byte[WIDTH * HEIGHT]; private State state = State.NORMAL; - private final int[] args = new int[4]; + private int[] args = new int[4]; private int argCount = 0; private int x, y; private int savedX, savedY; @@ -76,53 +77,6 @@ public final class Terminal { } } - public CompoundNBT serialize(final boolean forClient) { - final CompoundNBT nbt = new CompoundNBT(); - - if (!forClient) { - // todo serialize input - } - - nbt.putByteArray("buffer", buffer); - nbt.putByte("state", (byte) state.ordinal()); - nbt.putIntArray("args", args); - nbt.putInt("argCount", argCount); - nbt.putInt("x", x); - nbt.putInt("y", y); - nbt.putInt("savedX", savedX); - nbt.putInt("savedY", savedY); - - return nbt; - } - - public void deserialize(final CompoundNBT nbt) { - if (nbt.contains("input")) { - // todo deserialize input - } - - final byte[] buffer = nbt.getByteArray("buffer"); - if (buffer.length == this.buffer.length) { - System.arraycopy(buffer, 0, this.buffer, 0, buffer.length); - } - - final byte state = nbt.getByte("state"); - final State[] states = State.values(); - if (state >= 0 && state < states.length) { - this.state = states[state]; - } - - final int[] args = nbt.getIntArray("args"); - if (args.length == this.args.length) { - System.arraycopy(args, 0, this.args, 0, args.length); - } - - argCount = nbt.getInt("argCount"); - x = nbt.getInt("x"); - y = nbt.getInt("y"); - savedX = nbt.getInt("savedX"); - savedY = nbt.getInt("savedY"); - } - public synchronized int readInput() { if (input.isEmpty()) { return -1; diff --git a/src/main/java/li/cil/oc2/common/tile/ComputerTileEntity.java b/src/main/java/li/cil/oc2/common/tile/ComputerTileEntity.java index f19343c7..c952b54a 100644 --- a/src/main/java/li/cil/oc2/common/tile/ComputerTileEntity.java +++ b/src/main/java/li/cil/oc2/common/tile/ComputerTileEntity.java @@ -3,9 +3,10 @@ package li.cil.oc2.common.tile; import it.unimi.dsi.fastutil.bytes.ByteArrayFIFOQueue; import li.cil.oc2.OpenComputers; import li.cil.oc2.client.gui.terminal.Terminal; -import li.cil.oc2.common.network.TerminalBlockOutputMessage; import li.cil.oc2.common.network.Network; +import li.cil.oc2.common.network.TerminalBlockOutputMessage; import li.cil.oc2.common.vm.VirtualMachineRunner; +import li.cil.oc2.serialization.NBTSerialization; import li.cil.sedna.api.Sizes; import li.cil.sedna.api.device.PhysicalMemory; import li.cil.sedna.buildroot.Buildroot; @@ -90,20 +91,22 @@ public final class ComputerTileEntity extends TileEntity implements ITickableTil @Override public CompoundNBT getUpdateTag() { final CompoundNBT result = super.getUpdateTag(); - result.put("terminal", terminal.serialize(true)); + + result.put("terminal", NBTSerialization.serialize(terminal)); return result; } @Override public void handleUpdateTag(final CompoundNBT tag) { super.handleUpdateTag(tag); - terminal.deserialize(tag.getCompound("terminal")); + NBTSerialization.deserialize(tag.getCompound("terminal"), terminal); } @Override public void read(final CompoundNBT compound) { super.read(compound); joinVirtualMachine(); + NBTSerialization.deserialize(compound.getCompound("terminal"), terminal); // TODO deserialize VM } @@ -111,6 +114,7 @@ public final class ComputerTileEntity extends TileEntity implements ITickableTil public CompoundNBT write(final CompoundNBT compound) { final CompoundNBT result = super.write(compound); joinVirtualMachine(); + compound.put("terminal", NBTSerialization.serialize(terminal)); // TODO serialize VM return result; } diff --git a/src/main/java/li/cil/oc2/serialization/NBTSerialization.java b/src/main/java/li/cil/oc2/serialization/NBTSerialization.java new file mode 100644 index 00000000..50d32a37 --- /dev/null +++ b/src/main/java/li/cil/oc2/serialization/NBTSerialization.java @@ -0,0 +1,308 @@ +package li.cil.oc2.serialization; + +import li.cil.ceres.Ceres; +import li.cil.ceres.api.DeserializationVisitor; +import li.cil.ceres.api.SerializationException; +import li.cil.ceres.api.SerializationVisitor; +import net.minecraft.nbt.CompoundNBT; +import net.minecraft.nbt.ListNBT; +import net.minecraftforge.common.util.Constants; +import org.jetbrains.annotations.Contract; + +import javax.annotation.Nullable; +import java.lang.reflect.Array; +import java.util.UUID; + +public final class NBTSerialization { + private static final String IS_NULL_SUFFIX = ".is_null"; + + public static CompoundNBT serialize(final Object value) throws SerializationException { + final CompoundNBT nbt = new CompoundNBT(); + Ceres.getSerializer(value.getClass()).serialize(new Serializer(nbt), value); + return nbt; + } + + @SuppressWarnings("unchecked") + public static T deserialize(final CompoundNBT nbt, final T into) throws SerializationException { + return deserialize(nbt, (Class) into.getClass(), into); + } + + public static T deserialize(final CompoundNBT nbt, final Class type, @Nullable final T into) throws SerializationException { + return Ceres.getSerializer(type).deserialize(new Deserializer(nbt), type, into); + } + + private static final class Serializer implements SerializationVisitor { + private final CompoundNBT nbt; + + private Serializer(final CompoundNBT nbt) { + this.nbt = nbt; + } + + @Override + public void putBoolean(final String name, final boolean value) { + nbt.putBoolean(name, value); + } + + @Override + public void putByte(final String name, final byte value) { + nbt.putByte(name, value); + } + + @Override + public void putChar(final String name, final char value) { + nbt.putInt(name, value); + } + + @Override + public void putShort(final String name, final short value) { + nbt.putShort(name, value); + } + + @Override + public void putInt(final String name, final int value) { + nbt.putInt(name, value); + } + + @Override + public void putLong(final String name, final long value) { + nbt.putLong(name, value); + } + + @Override + public void putFloat(final String name, final float value) { + nbt.putFloat(name, value); + } + + @Override + public void putDouble(final String name, final double value) { + nbt.putDouble(name, value); + } + + @SuppressWarnings({"unchecked", "rawtypes"}) + @Override + public void putObject(final String name, final Class type, @Nullable final Object value) throws SerializationException { + if (putIsNull(name, value)) { + return; + } + + if (type == boolean[].class) { + final boolean[] data = (boolean[]) value; + final byte[] convertedData = new byte[data.length]; + for (int i = 0; i < data.length; i++) { + convertedData[i] = data[i] ? (byte) 1 : (byte) 0; + } + nbt.putByteArray(name, convertedData); + } else if (type == byte[].class) { + nbt.putByteArray(name, (byte[]) value); + } else if (type == char[].class) { + final char[] data = (char[]) value; + final int[] convertedData = new int[data.length]; + for (int i = 0; i < data.length; i++) { + convertedData[i] = data[i]; + } + nbt.putIntArray(name, convertedData); + } else if (type == short[].class) { + final short[] data = (short[]) value; + final int[] convertedData = new int[data.length]; + for (int i = 0; i < data.length; i++) { + convertedData[i] = data[i]; + } + nbt.putIntArray(name, convertedData); + } else if (type == int[].class) { + nbt.putIntArray(name, (int[]) value); + } else if (type == long[].class) { + nbt.putLongArray(name, (long[]) value); + } else if (type == float[].class) { + final float[] data = (float[]) value; + final int[] convertedData = new int[data.length]; + for (int i = 0; i < data.length; i++) { + convertedData[i] = Float.floatToRawIntBits(data[i]); + } + nbt.putIntArray(name, convertedData); + } else if (type == double[].class) { + final double[] data = (double[]) value; + final long[] convertedData = new long[data.length]; + for (int i = 0; i < data.length; i++) { + convertedData[i] = Double.doubleToRawLongBits(data[i]); + } + nbt.putLongArray(name, convertedData); + } else if (type.isArray()) { + final Class componentType = type.getComponentType(); + final li.cil.ceres.api.Serializer serializer = Ceres.getSerializer(componentType); + final Object[] data = (Object[]) value; + final ListNBT listNBT = new ListNBT(); + for (final Object datum : data) { + final CompoundNBT itemNBT = new CompoundNBT(); + if (datum == null) { + itemNBT.putBoolean(IS_NULL_SUFFIX, true); + } else { + if (datum.getClass() != componentType) { + throw new SerializationException(String.format("Polymorphism detected in generic array [%s]. This is not supported.", name)); + } + serializer.serialize(new Serializer(itemNBT), (Class) componentType, datum); + } + listNBT.add(itemNBT); + } + nbt.put(name, listNBT); + } else if (type.isEnum()) { + nbt.putString(name, ((Enum) value).name()); + } else if (type == String.class) { + nbt.putString(name, (String) value); + } else if (type == UUID.class) { + final CompoundNBT uuidNBT = new CompoundNBT(); + uuidNBT.putUniqueId(name, (UUID) value); + nbt.put(name, uuidNBT); + } else { + final CompoundNBT valueNBT = new CompoundNBT(); + Ceres.getSerializer(type).serialize(new Serializer(valueNBT), (Class) type, value); + if (!valueNBT.isEmpty()) { + nbt.put(name, valueNBT); + } + } + } + + @Contract(value = "_, null -> true") + private boolean putIsNull(final String name, @Nullable final Object value) { + final boolean isNull = value == null; + nbt.putBoolean(name + IS_NULL_SUFFIX, isNull); + return isNull; + } + } + + private static final class Deserializer implements DeserializationVisitor { + private final CompoundNBT nbt; + + private Deserializer(final CompoundNBT nbt) { + this.nbt = nbt; + } + + @Override + public boolean getBoolean(final String name) { + return nbt.getBoolean(name); + } + + @Override + public byte getByte(final String name) { + return nbt.getByte(name); + } + + @Override + public char getChar(final String name) { + return (char) nbt.getInt(name); + } + + @Override + public short getShort(final String name) { + return nbt.getShort(name); + } + + @Override + public int getInt(final String name) { + return nbt.getInt(name); + } + + @Override + public long getLong(final String name) { + return nbt.getLong(name); + } + + @Override + public float getFloat(final String name) { + return nbt.getFloat(name); + } + + @Override + public double getDouble(final String name) { + return nbt.getDouble(name); + } + + @SuppressWarnings({"unchecked", "rawtypes"}) + @Nullable + @Override + public Object getObject(final String name, final Class type, @Nullable final Object into) throws SerializationException { + if (isNull(name)) { + return null; + } + + // Do not overwrite values which were not serialized before. + if (!nbt.contains(name)) { + return into; + } + + if (type == boolean[].class) { + final byte[] convertedData = nbt.getByteArray(name); + final boolean[] data = new boolean[convertedData.length]; + for (int i = 0; i < convertedData.length; i++) { + data[i] = convertedData[i] != 0; + } + return data; + } else if (type == byte[].class) { + return nbt.getByteArray(name); + } else if (type == char[].class) { + final int[] convertedData = nbt.getIntArray(name); + final char[] data = new char[convertedData.length]; + for (int i = 0; i < convertedData.length; i++) { + data[i] = (char) convertedData[i]; + } + return data; + } else if (type == short[].class) { + final int[] convertedData = nbt.getIntArray(name); + final short[] data = new short[convertedData.length]; + for (int i = 0; i < convertedData.length; i++) { + data[i] = (short) convertedData[i]; + } + return data; + } else if (type == int[].class) { + return nbt.getIntArray(name); + } else if (type == long[].class) { + return nbt.getLongArray(name); + } else if (type == float[].class) { + final int[] convertedData = nbt.getIntArray(name); + final float[] data = new float[convertedData.length]; + for (int i = 0; i < convertedData.length; i++) { + data[i] = Float.intBitsToFloat(convertedData[i]); + } + return data; + } else if (type == double[].class) { + final long[] convertedData = nbt.getLongArray(name); + final double[] data = new double[convertedData.length]; + for (int i = 0; i < convertedData.length; i++) { + data[i] = Double.longBitsToDouble(convertedData[i]); + } + return data; + } else if (type.isArray()) { + final Class componentType = type.getComponentType(); + final li.cil.ceres.api.Serializer serializer = Ceres.getSerializer(componentType); + final ListNBT listNBT = nbt.getList(name, Constants.NBT.TAG_COMPOUND); + final Object[] data = (Object[]) Array.newInstance(componentType, listNBT.size()); + for (int i = 0; i < listNBT.size(); i++) { + final CompoundNBT itemNBT = listNBT.getCompound(i); + if (itemNBT.contains(IS_NULL_SUFFIX)) { + continue; + } + + data[i] = serializer.deserialize(new Deserializer(itemNBT), (Class) componentType, null); + } + return data; + } else if (type.isEnum()) { + return Enum.valueOf((Class) type, nbt.getString(name)); + } else if (type == String.class) { + return nbt.getString(name); + } else if (type == UUID.class) { + return nbt.getCompound(name).getUniqueId(name); + } else { + final CompoundNBT valueNBT = nbt.getCompound(name); + return Ceres.getSerializer(type).deserialize(new Deserializer(valueNBT), (Class) type, into); + } + } + + @Override + public boolean exists(final String name) { + return nbt.contains(name) || nbt.contains(name + IS_NULL_SUFFIX); + } + + private boolean isNull(final String name) { + return nbt.getBoolean(name + IS_NULL_SUFFIX); + } + } +} diff --git a/src/main/java/li/cil/oc2/serialization/package-info.java b/src/main/java/li/cil/oc2/serialization/package-info.java new file mode 100644 index 00000000..b3a20a96 --- /dev/null +++ b/src/main/java/li/cil/oc2/serialization/package-info.java @@ -0,0 +1,7 @@ +@ParametersAreNonnullByDefault +@MethodsReturnNonnullByDefault +package li.cil.oc2.serialization; + +import mcp.MethodsReturnNonnullByDefault; + +import javax.annotation.ParametersAreNonnullByDefault; \ No newline at end of file diff --git a/src/test/java/li/cil/oc2/serialization/SerializationTests.java b/src/test/java/li/cil/oc2/serialization/SerializationTests.java new file mode 100644 index 00000000..769a58f2 --- /dev/null +++ b/src/test/java/li/cil/oc2/serialization/SerializationTests.java @@ -0,0 +1,199 @@ +package li.cil.oc2.serialization; + +import li.cil.ceres.api.Serialized; +import net.minecraft.nbt.CompoundNBT; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import java.util.UUID; + +public final class SerializationTests { + @Test + public void testSerializeFlat() { + final Flat value = new Flat(); + + final UUID uuid = UUID.randomUUID(); + value.byteValue = 123; + value.shortValue = 234; + value.intValue = 456; + value.longValue = 567; + value.floatValue = 678.9f; + value.doubleValue = 789.0; + value.byteArrayValue = new byte[]{1, 2, 3}; + value.intArrayValue = new int[]{4, 5, 6}; + value.longArrayValue = new long[]{7, 8, 9}; + value.stringValue = "test string"; + value.uuidValue = uuid; + + final CompoundNBT nbt = Assertions.assertDoesNotThrow(() -> NBTSerialization.serialize(value)); + + Assertions.assertEquals(123, nbt.getByte("byteValue")); + Assertions.assertEquals(234, nbt.getShort("shortValue")); + Assertions.assertEquals(456, nbt.getInt("intValue")); + Assertions.assertEquals(567, nbt.getLong("longValue")); + Assertions.assertEquals(678.9f, nbt.getFloat("floatValue")); + Assertions.assertEquals(789.0, nbt.getDouble("doubleValue")); + Assertions.assertArrayEquals(new byte[]{1, 2, 3}, nbt.getByteArray("byteArrayValue")); + Assertions.assertArrayEquals(new int[]{4, 5, 6}, nbt.getIntArray("intArrayValue")); + Assertions.assertArrayEquals(new long[]{7, 8, 9}, nbt.getLongArray("longArrayValue")); + Assertions.assertEquals("test string", nbt.getString("stringValue")); + Assertions.assertEquals(uuid, nbt.getCompound("uuidValue").getUniqueId("uuidValue")); + } + + @Test + public void testDeserializeFlatInto() { + final CompoundNBT nbt = new CompoundNBT(); + nbt.putByte("byteValue", (byte) 98); + nbt.putShort("shortValue", (short) 876); + nbt.putInt("intValue", 765); + nbt.putLong("longValue", 654); + nbt.putFloat("floatValue", 543.2f); + nbt.putDouble("doubleValue", 432.1); + nbt.putByteArray("byteArrayValue", new byte[]{9, 8, 7}); + nbt.putIntArray("intArrayValue", new int[]{8, 7, 6}); + nbt.putLongArray("longArrayValue", new long[]{7, 6, 5}); + nbt.putString("stringValue", "another test"); + final UUID uuid = UUID.randomUUID(); + final CompoundNBT uuidNBT = new CompoundNBT(); + uuidNBT.putUniqueId("uuidValue", uuid); + nbt.put("uuidValue", uuidNBT); + + final Flat value = Assertions.assertDoesNotThrow(() -> NBTSerialization.deserialize(nbt, Flat.class, new Flat())); + + Assertions.assertEquals(98, value.byteValue); + Assertions.assertEquals(876, value.shortValue); + Assertions.assertEquals(765, value.intValue); + Assertions.assertEquals(654, value.longValue); + Assertions.assertEquals(543.2f, value.floatValue); + Assertions.assertEquals(432, .1, value.doubleValue); + Assertions.assertArrayEquals(new byte[]{9, 8, 7}, value.byteArrayValue); + Assertions.assertArrayEquals(new int[]{8, 7, 6}, value.intArrayValue); + Assertions.assertArrayEquals(new long[]{7, 6, 5}, value.longArrayValue); + Assertions.assertEquals("another test", value.stringValue); + Assertions.assertEquals(uuid, value.uuidValue); + } + + @Test + public void testDeserializeFlatNew() { + final CompoundNBT nbt = new CompoundNBT(); + + nbt.putByte("byteValue", (byte) 98); + nbt.putShort("shortValue", (short) 876); + nbt.putInt("intValue", 765); + nbt.putLong("longValue", 654); + nbt.putFloat("floatValue", 543.2f); + nbt.putDouble("doubleValue", 432.1); + nbt.putByteArray("byteArrayValue", new byte[]{9, 8, 7}); + nbt.putIntArray("intArrayValue", new int[]{8, 7, 6}); + nbt.putLongArray("longArrayValue", new long[]{7, 6, 5}); + nbt.putString("stringValue", "another test"); + final UUID uuid = UUID.randomUUID(); + final CompoundNBT uuidNBT = new CompoundNBT(); + uuidNBT.putUniqueId("uuidValue", uuid); + nbt.put("uuidValue", uuidNBT); + + final Flat value = Assertions.assertDoesNotThrow(() -> NBTSerialization.deserialize(nbt, Flat.class, null)); + + Assertions.assertEquals(98, value.byteValue); + Assertions.assertEquals(876, value.shortValue); + Assertions.assertEquals(765, value.intValue); + Assertions.assertEquals(654, value.longValue); + Assertions.assertEquals(543.2f, value.floatValue); + Assertions.assertEquals(432, .1, value.doubleValue); + Assertions.assertArrayEquals(new byte[]{9, 8, 7}, value.byteArrayValue); + Assertions.assertArrayEquals(new int[]{8, 7, 6}, value.intArrayValue); + Assertions.assertArrayEquals(new long[]{7, 6, 5}, value.longArrayValue); + Assertions.assertEquals("another test", value.stringValue); + Assertions.assertEquals(uuid, value.uuidValue); + } + + @Test + public void testModifiers() { + final WithModifiers value = new WithModifiers(); + final CompoundNBT nbt = Assertions.assertDoesNotThrow(() -> NBTSerialization.serialize(value)); + + Assertions.assertTrue(nbt.contains("nonTransientInt")); + Assertions.assertEquals(123, nbt.getInt("nonTransientInt")); + Assertions.assertFalse(nbt.contains("transientInt")); + Assertions.assertFalse(nbt.contains("finalInt")); + + nbt.putIntArray("finalIntArray", new int[]{8, 7, 6}); + + Assertions.assertDoesNotThrow(() -> NBTSerialization.deserialize(nbt, value)); + + Assertions.assertArrayEquals(new int[]{4, 5, 6}, value.finalIntArray); + } + + @Test + public void testSerializeNested() { + final Nested root = new Nested(); + root.value = 123; + root.child = new Nested(); + root.child.value = 234; + + final CompoundNBT nbt = Assertions.assertDoesNotThrow(() -> NBTSerialization.serialize(root)); + + Assertions.assertEquals(123, nbt.getInt("value")); + Assertions.assertTrue(nbt.contains("child")); + Assertions.assertEquals(234, nbt.getCompound("child").getInt("value")); + } + + @Test + public void testDeserializeNestedInto() { + final CompoundNBT nbt = new CompoundNBT(); + nbt.putInt("value", 123); + final CompoundNBT child = new CompoundNBT(); + nbt.put("child", child); + child.putInt("value", 234); + + final Nested value = Assertions.assertDoesNotThrow(() -> NBTSerialization.deserialize(nbt, Nested.class, new Nested())); + + Assertions.assertEquals(123, value.value); + Assertions.assertEquals(234, value.child.value); + Assertions.assertNull(value.child.child); + } + + @Test + public void testDeserializeNestedNew() { + final CompoundNBT nbt = new CompoundNBT(); + nbt.putInt("value", 123); + final CompoundNBT child = new CompoundNBT(); + nbt.put("child", child); + child.putInt("value", 234); + + final Nested value = Assertions.assertDoesNotThrow(() -> NBTSerialization.deserialize(nbt, Nested.class, null)); + + Assertions.assertEquals(123, value.value); + Assertions.assertEquals(234, value.child.value); + Assertions.assertNull(value.child.child); + } + + @Serialized + private static final class Flat { + private byte byteValue; + private short shortValue; + private int intValue; + private long longValue; + private float floatValue; + private double doubleValue; + private byte[] byteArrayValue; + private int[] intArrayValue; + private long[] longArrayValue; + private String stringValue; + private UUID uuidValue; + } + + @Serialized + private static final class WithModifiers { + private int nonTransientInt = 123; + private transient int transientInt = 345; + private final int finalInt = 678; + private final int[] finalIntArray = {4, 5, 6}; + } + + @Serialized + private static final class Nested { + private int value; + private Nested child; + } +}