diff --git a/src/MoF/SaveLoader.java b/src/MoF/SaveLoader.java index fe7933fe..d88727b4 100644 --- a/src/MoF/SaveLoader.java +++ b/src/MoF/SaveLoader.java @@ -1,31 +1,25 @@ package MoF; +import amidst.Util; import amidst.nbt.Tag; +import amidst.nbt.TagCompound; -import java.io.File; -import java.io.FileInputStream; -import java.io.FileOutputStream; -import java.io.FileReader; -import java.io.FileWriter; +import java.io.*; import java.util.ArrayList; import javax.swing.filechooser.FileFilter; - public class SaveLoader { public static String genType = "default"; public static FileFilter getFilter() { return (new FileFilter() { public boolean accept(File f) { - - if (f.isDirectory()) { - return true; - } - String[] st = f.getName().split("\\/"); - if (st[st.length-1].toLowerCase().equals("level.dat")) - return true; - return false; + if (f.isDirectory()) { + return true; + } + String[] st = f.getName().split("\\/"); + return st[st.length - 1].equalsIgnoreCase("level.dat"); } - + @Override public String getDescription() { return "Minecraft Data File (level.dat)"; @@ -60,20 +54,19 @@ public class SaveLoader { return players; } public void movePlayer(String name, int x, int y) { - File out = null; + File out; if (multi) { - String outpath = file.getParent() + "/players/" + name +".dat"; - out = new File(outpath); + String outPath = file.getParent() + "/players/" + name +".dat"; + out = new File(outPath); backupFile(out); try { - Tag t = Tag.readFrom(new FileInputStream(out)); + TagCompound t = Tag.readFrom(new FileInputStream(out)); Tag pos = t.findTagByName("Pos"); - Tag[] pa = (Tag[]) pos.getValue(); - pa[0].setValue((double)x); - pa[1].setValue((double)120); - pa[2].setValue((double)y); + Tag[] pa = (Tag[]) pos.getValue(); + pa[0].setValue((double) x); + pa[1].setValue((double) 120); + pa[2].setValue((double) y); t.writeTo(new FileOutputStream(out)); - } catch (Exception e) { e.printStackTrace(); } @@ -82,35 +75,33 @@ public class SaveLoader { out = file; backupFile(out); try { - Tag t = Tag.readFrom(new FileInputStream(out)); + TagCompound t = Tag.readFrom(new FileInputStream(out)); Tag pos = t.findTagByName("Pos"); - Tag[] pa = (Tag[]) pos.getValue(); - pa[0].setValue((double)x); - pa[1].setValue((double)120); - pa[2].setValue((double)y); + Tag[] pa = (Tag[]) pos.getValue(); + pa[0].setValue((double) x); + pa[1].setValue((double) 120); + pa[2].setValue((double) y); t.writeTo(new FileOutputStream(out)); } catch (Exception e) { e.printStackTrace(); } } } - private void backupFile(File f) { - File inputFile = f; - File outputFile = new File(f.toString() + ".moth"); - if (!back.contains(outputFile.toString())) { - try { - FileReader in = new FileReader(inputFile); - FileWriter out = new FileWriter(outputFile); - int c; - - while ((c = in.read()) != -1) - out.write(c); - - in.close(); - out.close(); - back.add(outputFile.toString()); - } catch (Exception e) { - } + private void backupFile(File inputFile) { + File outputFile = new File(inputFile.toString() + ".moth"); + if (!back.contains(outputFile.toString())) { + try { + FileReader in = new FileReader(inputFile); + FileWriter out = new FileWriter(outputFile); + int c; + + while ((c = in.read()) != -1) + out.write(c); + + in.close(); + out.close(); + back.add(outputFile.toString()); + } catch (Exception ignored) {} } } @@ -119,35 +110,34 @@ public class SaveLoader { players = new ArrayList(); back = new ArrayList(); try { - Tag t = Tag.readFrom(new FileInputStream(f)); - Tag pTag = t.findTagByName("Player"); - seed = (Long)t.findTagByName("RandomSeed").getValue(); - genType = (String)t.findTagByName("generatorName").getValue(); + TagCompound t = Tag.readFrom(new FileInputStream(f)); + TagCompound pTag = (TagCompound) t.findTagByName("Player"); + seed = (Long) t.findTagByName("RandomSeed").getValue(); + genType = (String) t.findTagByName("generatorName").getValue(); System.out.println("Gen Type: " + genType); - if (pTag!=null) { + if (pTag != null) { multi = false; Tag pos = pTag.findTagByName("Pos"); - Tag[] pa = (Tag[]) pos.getValue(); - double x = (Double) pa[0].getValue(); - double z = (Double) pa[2].getValue(); - players.add(new Player("Player", (int)x, (int)z)); - + Tag[] pa = (Tag[]) pos.getValue(); + double x = pa[0].getValue(); + double z = pa[2].getValue(); + players.add(new Player("Player", (int) x, (int) z)); } else { multi = true; File[] listing = new File(f.getParent() + "/players").listFiles(); - Tag ps; - for (int i = 0; i < listing.length; i++) { + TagCompound ps; + for (int i = 0; i < (listing != null ? listing.length : 0); i++) { ps = Tag.readFrom(new FileInputStream(listing[i])); Tag pos = ps.findTagByName("Pos"); - Tag[] pa = (Tag[]) pos.getValue(); - double x = (Double) pa[0].getValue(); - double z = (Double) pa[2].getValue(); - players.add(new Player(listing[i].getName().split("\\.")[0], (int)x, (int)z)); + Tag[] pa = (Tag[]) pos.getValue(); + double x = pa[0].getValue(); + double z = pa[2].getValue(); + players.add(new Player(listing[i].getName().split("\\.")[0], (int) x, (int) z)); } } } catch (Exception e) { - // FIXME Dialog when file read fails. + Util.showError(e); } } } diff --git a/src/amidst/Util.java b/src/amidst/Util.java new file mode 100644 index 00000000..742e4af5 --- /dev/null +++ b/src/amidst/Util.java @@ -0,0 +1,33 @@ +package amidst; + +import javax.swing.*; +import java.io.ByteArrayOutputStream; +import java.io.PrintStream; + +public class Util { + /** Shows an error message for an exception + * @param e the exception for which the stachtrace is to be shown + */ + public static void showError(Exception e) { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + PrintStream ps = new PrintStream(baos); + e.printStackTrace(ps); + String trace = baos.toString(); + + e.printStackTrace(); + + JOptionPane.showMessageDialog( + null, + trace, + e.toString(), + JOptionPane.ERROR_MESSAGE); + } + +// public static void main(String[] args) { +// try { +// int infinity = 1 / 0; +// } catch (Exception e) { +// showError(e); +// } +// } +} diff --git a/src/amidst/nbt/SequenceTagBase.java b/src/amidst/nbt/SequenceTagBase.java new file mode 100644 index 00000000..06518ea4 --- /dev/null +++ b/src/amidst/nbt/SequenceTagBase.java @@ -0,0 +1,92 @@ +package amidst.nbt; + +import java.io.PrintStream; + +public abstract class SequenceTagBase extends Tag[]> { + SequenceTagBase(Type type, String name, Tag[] value) { + super(type, name, value); + } + + public abstract void addTag(Tag tag); + + /** Add a tag to a TAG_List or a TAG_Compound at the specified index. + */ + public void insertTag(Tag tag, int index) { + if (value.length > 0 && (this instanceof TagList && tag.type != ((TagList) this).listType)) + throw new IllegalArgumentException(); + if (index > value.length) + throw new IndexOutOfBoundsException(); + + Tag[] newValue = new Tag[value.length + 1]; + System.arraycopy(value, 0, newValue, 0, index); + newValue[index] = tag; + System.arraycopy(value, index, newValue, index + 1, value.length - index); + + value = newValue; + } + + /** Remove a tag from a TAG_List or a TAG_Compound at the specified index. + * + * @return the removed tag + */ + public Tag removeTag(int index) { + if (type != Type.TAG_List && type != Type.TAG_Compound) + throw new RuntimeException(); + + Tag victim = value[index]; + Tag[] newValue = new Tag[value.length - 1]; + System.arraycopy(value, 0, newValue, 0, index); + index++; + System.arraycopy(value, index, newValue, index - 1, value.length - index); + + value = newValue; + return victim; + } + + /** Remove a tag from a TAG_List or a TAG_Compound. If the tag is not a child of this tag then nested tags are searched. + * + * @param tag tag to look for + */ + public void removeSubTag(Tag tag) { + if (tag == null) return; + + for (int i = 0; i < value.length; i++) { + if (value[i] == tag) { + removeTag(i); + return; + } else { + if (value[i] instanceof SequenceTagBase) { + ((SequenceTagBase) value[i]).removeSubTag(tag); + } + } + } + } + + /** + * Find the first nested tag with specified name in a TAG_List or TAG_Compound after a tag with the same name. + * + * @param name the name to look for. May be null to look for unnamed tags. + * @param found the previously found tag with the same name. + * @return the first nested tag that has the specified name after the previously found tag. + */ + public Tag findNextTagByName(String name, Tag found) { + for (Tag subTag : value) { + if ((subTag.name == null && name == null) //End tag + || (subTag.name != null && subTag.name.equals(name))) { + return subTag; + } else if (subTag instanceof TagCompound) { + Tag newFound = ((TagCompound) subTag).findTagByName(name); + if (newFound != null && newFound != found) + return newFound; + } + } + return null; + } + + void serializeEntries(PrintStream ps, int indent) { + serializeIndented(ps, indent, "{"); + for (Tag st : value) + st.serialize(ps, indent + 1); + serializeIndented(ps, indent, "}"); + } +} diff --git a/src/amidst/nbt/Tag.java b/src/amidst/nbt/Tag.java index 07661af9..148a995d 100644 --- a/src/amidst/nbt/Tag.java +++ b/src/amidst/nbt/Tag.java @@ -1,10 +1,10 @@ package amidst.nbt; -import java.io.DataInputStream; -import java.io.DataOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; +import sun.reflect.generics.reflectiveObjects.NotImplementedException; + +import java.io.*; +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; import java.util.zip.GZIPInputStream; import java.util.zip.GZIPOutputStream; @@ -13,308 +13,68 @@ import java.util.zip.GZIPOutputStream; * * @see Online NBT specification */ -public class Tag { - private final Type type; - private Type listType = null; - private final String name; - private Object value; +public abstract class Tag { + public final Type type; + public final String name; + T value; /** * Enum for the tag types. */ public enum Type { - TAG_End, - TAG_Byte, - TAG_Short, - TAG_Int, - TAG_Long, - TAG_Float, - TAG_Double, - TAG_Byte_Array, - TAG_String, - TAG_List, - TAG_Compound, - TAG_Int_Array - } - - /** - * Create a new TAG_List or TAG_Compound NBT tag. - * - * @param type either TAG_List or TAG_Compound - * @param name name for the new tag or null to create an unnamed tag. - * @param value list of tags to add to the new tag. - */ - public Tag(Type type, String name, Tag[] value) { - this(type, name, (Object) value); - } - - /** - * Create a new TAG_List with an empty list. Use {@link Tag#addTag(Tag)} to add tags later. - * - * @param name name for this tag or null to create an unnamed tag. - * @param listType type of the elements in this empty list. - */ - public Tag(String name, Type listType) { - this(Type.TAG_List, name, listType); - } - - /** - * Create a new NBT tag. - * - * @param type any value from the {@link Type} enum. - * @param name name for the new tag or null to create an unnamed tag. - * @param value an object that fits the tag type or a {@link Type} to create an empty TAG_List with this list type. - */ - public Tag(Type type, String name, Object value) { - switch (type) { - case TAG_End: - if (value != null) - throw new IllegalArgumentException(); - break; - case TAG_Byte: - if (!(value instanceof Byte)) - throw new IllegalArgumentException(); - break; - case TAG_Short: - if (!(value instanceof Short)) - throw new IllegalArgumentException(); - break; - case TAG_Int: - if (!(value instanceof Integer)) - throw new IllegalArgumentException(); - break; - case TAG_Long: - if (!(value instanceof Long)) - throw new IllegalArgumentException(); - break; - case TAG_Float: - if (!(value instanceof Float)) - throw new IllegalArgumentException(); - break; - case TAG_Double: - if (!(value instanceof Double)) - throw new IllegalArgumentException(); - break; - case TAG_Byte_Array: - if (!(value instanceof byte[])) - throw new IllegalArgumentException(); - break; - case TAG_String: - if (!(value instanceof String)) - throw new IllegalArgumentException(); - break; - case TAG_List: - if (value instanceof Type) { - this.listType = (Type) value; - value = new Tag[0]; - } else { - if (!(value instanceof Tag[])) - throw new IllegalArgumentException(); - this.listType = (((Tag[]) value)[0]).getType(); - } - break; - case TAG_Compound: - if (!(value instanceof Tag[])) - throw new IllegalArgumentException(); - break; - default: - throw new IllegalArgumentException(); - //TODO: Int_Array + TAG_End(TagEnd.class), + TAG_Byte(TagByte.class), + TAG_Short(TagShort.class), + TAG_Int(TagInt.class), + TAG_Long(TagLong.class), + TAG_Float(TagFloat.class), + TAG_Double(TagDouble.class), + TAG_Byte_Array(TagByteArray.class), + TAG_String(TagString.class), + TAG_List(TagList.class), + TAG_Compound(TagCompound.class), + TAG_Int_Array(TagIntArray.class); + + final Class clazz; + + private Type(Class clazz) { + this.clazz = clazz; } + + public Tag readFrom(String name, DataInputStream dis) { + try { + Constructor c = clazz.getDeclaredConstructor(String.class, DataInputStream.class); + return c.newInstance(name, dis); + } catch (NoSuchMethodException e) { //TODO: maybe java 7 for multicatch? + throw new RuntimeException(name, e); + } catch (InvocationTargetException e) { + throw new RuntimeException(name, e); + } catch (InstantiationException e) { + throw new RuntimeException(name, e); + } catch (IllegalAccessException e) { + throw new RuntimeException(name, e); + } + } + + public static Type fromByte(Byte type) { + return Type.values()[type]; + } + } + + Tag(Type type, String name, T value) { this.type = type; this.name = name; this.value = value; } - public Type getType() { - return type; - } - - public String getName() { - return name; - } - - public Object getValue() { + public T getValue() { return value; } - public void setValue(Object newValue) { - switch (type) { - case TAG_End: - if (value != null) - throw new IllegalArgumentException(); - break; - case TAG_Byte: - if (!(value instanceof Byte)) - throw new IllegalArgumentException(); - break; - case TAG_Short: - if (!(value instanceof Short)) - throw new IllegalArgumentException(); - break; - case TAG_Int: - if (!(value instanceof Integer)) - throw new IllegalArgumentException(); - break; - case TAG_Long: - if (!(value instanceof Long)) - throw new IllegalArgumentException(); - break; - case TAG_Float: - if (!(value instanceof Float)) - throw new IllegalArgumentException(); - break; - case TAG_Double: - if (!(value instanceof Double)) - throw new IllegalArgumentException(); - break; - case TAG_Byte_Array: - if (!(value instanceof byte[])) - throw new IllegalArgumentException(); - break; - case TAG_String: - if (!(value instanceof String)) - throw new IllegalArgumentException(); - break; - case TAG_List: - if (value instanceof Type) { - this.listType = (Type) value; - value = new Tag[0]; - } else { - if (!(value instanceof Tag[])) - throw new IllegalArgumentException(); - this.listType = (((Tag[]) value)[0]).getType(); - } - break; - case TAG_Compound: - if (!(value instanceof Tag[])) - throw new IllegalArgumentException(); - break; - default: - throw new IllegalArgumentException(); - //TODO: Int_Array - } - + public void setValue(T newValue) { value = newValue; } - public Type getListType() { - return listType; - } - - /** - * Add a tag to a TAG_List or a TAG_Compound. - */ - public void addTag(Tag tag) { - if (type != Type.TAG_List && type != Type.TAG_Compound) - throw new RuntimeException(); - Tag[] subtags = (Tag[]) value; - - int index = subtags.length; - - //For TAG_Compund entries, we need to add the tag BEFORE the end, - //or the new tag gets placed after the TAG_End, messing up the data. - //TAG_End MUST be kept at the very end of the TAG_Compound. - if(type == Type.TAG_Compound) index--; - insertTag(tag, index); - } - - /** - * Add a tag to a TAG_List or a TAG_Compound at the specified index. - */ - public void insertTag(Tag tag, int index) { - if (type != Type.TAG_List && type != Type.TAG_Compound) - throw new RuntimeException(); - Tag[] subtags = (Tag[]) value; - if (subtags.length > 0) - if (type == Type.TAG_List && tag.getType() != getListType()) - throw new IllegalArgumentException(); - if (index > subtags.length) - throw new IndexOutOfBoundsException(); - Tag[] newValue = new Tag[subtags.length + 1]; - System.arraycopy(subtags, 0, newValue, 0, index); - newValue[index] = tag; - System.arraycopy(subtags, index, newValue, index + 1, subtags.length - index); - value = newValue; - } - - /** - * Remove a tag from a TAG_List or a TAG_Compound at the specified index. - * - * @return the removed tag - */ - public Tag removeTag(int index) { - if (type != Type.TAG_List && type != Type.TAG_Compound) - throw new RuntimeException(); - Tag[] subtags = (Tag[]) value; - Tag victim = subtags[index]; - Tag[] newValue = new Tag[subtags.length - 1]; - System.arraycopy(subtags, 0, newValue, 0, index); - index++; - System.arraycopy(subtags, index, newValue, index - 1, subtags.length - index); - value = newValue; - return victim; - } - - /** - * Remove a tag from a TAG_List or a TAG_Compound. If the tag is not a child of this tag then nested tags are searched. - * - * @param tag tag to look for - */ - public void removeSubTag(Tag tag) { - if (type != Type.TAG_List && type != Type.TAG_Compound) - throw new RuntimeException(); - if (tag == null) - return; - Tag[] subtags = (Tag[]) value; - for (int i = 0; i < subtags.length; i++) { - if (subtags[i] == tag) { - removeTag(i); - return; - } else { - if (subtags[i].type == Type.TAG_List || subtags[i].type == Type.TAG_Compound) { - subtags[i].removeSubTag(tag); - } - } - } - } - - /** - * Find the first nested tag with specified name in a TAG_Compound. - * - * @param name the name to look for. May be null to look for unnamed tags. - * @return the first nested tag that has the specified name. - */ - public Tag findTagByName(String name) { - return findNextTagByName(name, null); - } - - /** - * Find the first nested tag with specified name in a TAG_List or TAG_Compound after a tag with the same name. - * - * @param name the name to look for. May be null to look for unnamed tags. - * @param found the previously found tag with the same name. - * @return the first nested tag that has the specified name after the previously found tag. - */ - public Tag findNextTagByName(String name, Tag found) { - if (type != Type.TAG_List && type != Type.TAG_Compound) - return null; - Tag[] subtags = (Tag[]) value; - for (Tag subtag : subtags) { - if ((subtag.name == null && name == null) || (subtag.name != null && subtag.name.equals(name))) { - return subtag; - } else { - Tag newFound = subtag.findTagByName(name); - if (newFound != null) - if (newFound == found) - continue; - else - return newFound; - } - } - return null; - } - /** * Read a tag and its nested tags from an InputStream. * @@ -322,76 +82,17 @@ public class Tag { * @return NBT tag or structure read from the InputStream * @throws java.io.IOException if there was no valid NBT structure in the InputStream or if another IOException occurred. */ - public static Tag readFrom(InputStream is) throws IOException { + public static TagCompound readFrom(InputStream is) throws IOException { DataInputStream dis = new DataInputStream(new GZIPInputStream(is)); - byte type = dis.readByte(); - Tag tag; + Type type = Type.fromByte(dis.readByte()); + if (type != Type.TAG_Compound) + throw new IOException("Root tags have to be Compound tags"); - if (type == 0) { - tag = new Tag(Type.TAG_End, null, null); - } else { - tag = new Tag(Type.values()[type], dis.readUTF(), readPayload(dis, type)); - } - - dis.close(); - - return tag; + TagCompound root = (TagCompound) Type.TAG_Compound.readFrom(dis.readUTF(), dis); + is.close(); + return root; } - private static Object readPayload(DataInputStream dis, byte type) throws IOException { - switch (Type.values()[type]) { - case TAG_End: - return null; - case TAG_Byte: - return dis.readByte(); - case TAG_Short: - return dis.readShort(); - case TAG_Int: - return dis.readInt(); - case TAG_Long: - return dis.readLong(); - case TAG_Float: - return dis.readFloat(); - case TAG_Double: - return dis.readDouble(); - case TAG_Byte_Array: - int length = dis.readInt(); - byte[] ba = new byte[length]; - dis.readFully(ba); - return ba; - case TAG_String: - return dis.readUTF(); - case TAG_List: - byte lt = dis.readByte(); - int ll = dis.readInt(); - Tag[] lo = new Tag[ll]; - for (int i = 0; i < ll; i++) { - lo[i] = new Tag(Type.values()[lt], null, readPayload(dis, lt)); - } - if (lo.length == 0) - return Type.values()[lt]; - else - return lo; - case TAG_Compound: - byte stt; - Tag[] tags = new Tag[0]; - do { - stt = dis.readByte(); - String name = null; - if (stt != 0) { - name = dis.readUTF(); - } - Tag[] newTags = new Tag[tags.length + 1]; - System.arraycopy(tags, 0, newTags, 0, tags.length); - newTags[tags.length] = new Tag(Type.values()[stt], name, readPayload(dis, stt)); - tags = newTags; - } while (stt != 0); - return tags; - //TODO: Int_Array - } - return null; - } - /** * Read a tag and its nested tags from an InputStream. * @@ -409,106 +110,50 @@ public class Tag { gzos.flush(); gzos.close(); } - - private void writePayload(DataOutputStream dos) throws IOException { - switch (type) { - case TAG_End: - break; - case TAG_Byte: - dos.writeByte((Byte) value); - break; - case TAG_Short: - dos.writeShort((Short) value); - break; - case TAG_Int: - dos.writeInt((Integer) value); - break; - case TAG_Long: - dos.writeLong((Long) value); - break; - case TAG_Float: - dos.writeFloat((Float) value); - break; - case TAG_Double: - dos.writeDouble((Double) value); - break; - case TAG_Byte_Array: - byte[] ba = (byte[]) value; - dos.writeInt(ba.length); - dos.write(ba); - break; - case TAG_String: - dos.writeUTF((String) value); - break; - case TAG_List: - Tag[] list = (Tag[]) value; - dos.writeByte(getListType().ordinal()); - dos.writeInt(list.length); - for (Tag tt : list) { - tt.writePayload(dos); - } - break; - case TAG_Compound: - Tag[] subtags = (Tag[]) value; - for (Tag st : subtags) { - Tag subtag = st; - Type type = subtag.getType(); - dos.writeByte(type.ordinal()); - if (type != Type.TAG_End) { - dos.writeUTF(subtag.getName()); - subtag.writePayload(dos); - } - } - break; - //TODO: Int_Array - } - } - /** - * Print the NBT structure to System.out + abstract void writePayload(DataOutputStream dos) throws IOException; + + /** Print the NBT structure to System.out */ - public void print() { - print(this, 0); + public void serialize() { + serialize(System.out, 0); + } + + /** Prints an indented sequence of objects to a PrintStream, terminated by a newline + * @param ps PrintStream to serialize to + * @param indent Indentation level (2 spaces per level are printed) + * @param line Things to serialize after the indentation + */ + static void serializeIndented(PrintStream ps, int indent, Object... line) { + for (int i = 0; i < indent; i++) + ps.print(" "); + for (Object thing : line) + if (thing != null) + if (thing instanceof Object[]) + for (Object underThing : (Object[]) thing) + ps.print(underThing); + else + ps.print(thing); + ps.println(); } - private void indent(int indent) { - for (int i = 0; i < indent; i++) { - System.out.print(" "); - } + /** Prints this Tag’s information and the sequence of Objects to a Prinstream, terminated by a newline + * @param ps PrintStream to serialize to + * @param indent Indentation level (2 spaces per level are printed) + * @param line Things to serialize after the indentation and prefix + */ + void serializePrefix(PrintStream ps, int indent, Object... line) { + if (type == Type.TAG_End) return; + String suffix = (name == null) ? null : "(\"" + name + "\")"; + serializeIndented(ps, indent, type, suffix, line); } - private void print(Tag t, int indent) { - Type type = t.getType(); - if (type == Type.TAG_End) - return; - String name = t.getName(); - indent(indent); - System.out.print(t.getType()); - if (name != null) - System.out.print("(\"" + t.getName() + "\")"); - if (type == Type.TAG_Byte_Array) { - byte[] b = (byte[]) t.getValue(); - System.out.println(": [" + b.length + " bytes]"); - } else if (type == Type.TAG_List) { - Tag[] subtags = (Tag[]) t.getValue(); - System.out.println(": " + subtags.length + " entries of type " + t.getListType()); - for (Tag st : subtags) { - print(st, indent + 1); - } - indent(indent); - System.out.println("}"); - } else if (type == Type.TAG_Compound) { - Tag[] subtags = (Tag[]) t.getValue(); - System.out.println(": " + (subtags.length - 1) + " entries"); - indent(indent); - System.out.println("{"); - for (Tag st : subtags) { - print(st, indent + 1); - } - indent(indent); - System.out.println("}"); - } else { - System.out.println(": " + t.getValue()); - } + /** Serializes this Tag to a PrintStream + * Default implementation for value-like Tags. Overridden in sequence Tags + * @param ps PrintStream to serialize to + * @param indent Indentation level (2 spaces per level are printed) + */ + void serialize(PrintStream ps, int indent) { + serializePrefix(ps, indent, ": ", value); } } \ No newline at end of file diff --git a/src/amidst/nbt/TagByte.java b/src/amidst/nbt/TagByte.java new file mode 100644 index 00000000..70feaadc --- /dev/null +++ b/src/amidst/nbt/TagByte.java @@ -0,0 +1,19 @@ +package amidst.nbt; + +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; + +public class TagByte extends Tag { + TagByte(String name, byte value) { + super(Type.TAG_Byte, name, value); + } + + TagByte(String name, DataInputStream dis) throws IOException { + this(name, dis.readByte()); + } + + void writePayload(DataOutputStream dos) throws IOException { + dos.writeByte(value); + } +} diff --git a/src/amidst/nbt/TagByteArray.java b/src/amidst/nbt/TagByteArray.java new file mode 100644 index 00000000..69c22bd3 --- /dev/null +++ b/src/amidst/nbt/TagByteArray.java @@ -0,0 +1,33 @@ +package amidst.nbt; + +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; +import java.io.PrintStream; + +public class TagByteArray extends Tag { + TagByteArray(String name, byte[] value) { + super(Type.TAG_Byte_Array, name, value); + } + + TagByteArray(String name, DataInputStream dis) throws IOException { + this(name, readPayload(dis)); + } + + private static byte[] readPayload(DataInputStream dis) throws IOException { + int length = dis.readInt(); + byte[] ba = new byte[length]; + dis.readFully(ba); + return ba; + } + + void writePayload(DataOutputStream dos) throws IOException { + dos.writeInt(value.length); + dos.write(value); + } + + @Override + void serialize(PrintStream ps, int indent) { + serializePrefix(ps, indent, ": [" + value.length + " bytes]"); + } +} diff --git a/src/amidst/nbt/TagCompound.java b/src/amidst/nbt/TagCompound.java new file mode 100644 index 00000000..0f524c2b --- /dev/null +++ b/src/amidst/nbt/TagCompound.java @@ -0,0 +1,74 @@ +package amidst.nbt; + +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; +import java.io.PrintStream; +import java.util.ArrayList; +import java.util.List; + +public class TagCompound extends SequenceTagBase { + /** + * Create a new TAG_Compound NBT tag. + * + * @param name name for the new tag or null to create an unnamed tag. + * @param value list of tags to add to the new tag. + */ + public TagCompound(String name, Tag[] value) { + super(Type.TAG_Compound, name, value); + } + + TagCompound(String name, DataInputStream dis) throws IOException { + this(name, readPayload(dis)); + } + + private static Tag[] readPayload(DataInputStream dis) throws IOException { + Type stt; + List tags = new ArrayList(); + do { + stt = Type.fromByte(dis.readByte()); + String name = null; + if (stt != Type.TAG_End) { + name = dis.readUTF(); + } + tags.add(stt.readFrom(name, dis)); + } while (stt != Type.TAG_End); + + return tags.toArray(new Tag[tags.size()]); + } + + void writePayload(DataOutputStream dos) throws IOException { + for (Tag subTag : value) { + dos.writeByte(type.ordinal()); + if (subTag.type != Type.TAG_End) { + dos.writeUTF(subTag.name); + subTag.writePayload(dos); + } + } + } + + /** Add a tag to a TAG_Compound. + * + * We need to add the tag BEFORE the end, or the new tag gets placed after the TAG_End, messing up the data. + * TAG_End MUST be kept at the very end of the TAG_Compound. + */ + public void addTag(Tag tag) { + insertTag(tag, value.length - 1); + } + + /** + * Find the first nested tag with specified name in a TAG_Compound. + * + * @param name the name to look for. May be null to look for unnamed tags. + * @return the first nested tag that has the specified name. + */ + public Tag findTagByName(String name) { + return findNextTagByName(name, null); + } + + @Override + void serialize(PrintStream ps, int indent) { + serializePrefix(ps, indent, ": ", value.length - 1, " entries"); + serializeEntries(ps, indent); + } +} diff --git a/src/amidst/nbt/TagDouble.java b/src/amidst/nbt/TagDouble.java new file mode 100644 index 00000000..efe08e07 --- /dev/null +++ b/src/amidst/nbt/TagDouble.java @@ -0,0 +1,19 @@ +package amidst.nbt; + +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; + +public class TagDouble extends Tag { + TagDouble(String name, double value) { + super(Type.TAG_Double, name, value); + } + + TagDouble(String name, DataInputStream dis) throws IOException { + this(name, dis.readDouble()); + } + + void writePayload(DataOutputStream dos) throws IOException { + dos.writeDouble(value); + } +} diff --git a/src/amidst/nbt/TagEnd.java b/src/amidst/nbt/TagEnd.java new file mode 100644 index 00000000..7d73eb4f --- /dev/null +++ b/src/amidst/nbt/TagEnd.java @@ -0,0 +1,24 @@ +package amidst.nbt; + +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; + +public class TagEnd extends Tag { + public TagEnd(String name) { + super(Type.TAG_End, name, null); + } + + TagEnd(String name, DataInputStream dis) throws IOException { + this(name); + } + + void writePayload(DataOutputStream dos) throws IOException {} + + @Override + public void setValue(Object newValue) { + if (value != null) + throw new IllegalArgumentException(); + value = newValue; + } +} diff --git a/src/amidst/nbt/TagFloat.java b/src/amidst/nbt/TagFloat.java new file mode 100644 index 00000000..d2e8bc68 --- /dev/null +++ b/src/amidst/nbt/TagFloat.java @@ -0,0 +1,19 @@ +package amidst.nbt; + +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; + +public class TagFloat extends Tag { + TagFloat(String name, float value) { + super(Type.TAG_Float, name, value); + } + + TagFloat(String name, DataInputStream dis) throws IOException { + this(name, dis.readFloat()); + } + + void writePayload(DataOutputStream dos) throws IOException { + dos.writeFloat(value); + } +} diff --git a/src/amidst/nbt/TagInt.java b/src/amidst/nbt/TagInt.java new file mode 100644 index 00000000..a1569e6f --- /dev/null +++ b/src/amidst/nbt/TagInt.java @@ -0,0 +1,19 @@ +package amidst.nbt; + +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; + +public class TagInt extends Tag { + TagInt(String name, int value) { + super(Type.TAG_Int, name, value); + } + + TagInt(String name, DataInputStream dis) throws IOException { + this(name, dis.readInt()); + } + + void writePayload(DataOutputStream dos) throws IOException { + dos.writeInt(value); + } +} diff --git a/src/amidst/nbt/TagIntArray.java b/src/amidst/nbt/TagIntArray.java new file mode 100644 index 00000000..61636245 --- /dev/null +++ b/src/amidst/nbt/TagIntArray.java @@ -0,0 +1,35 @@ +package amidst.nbt; + +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; +import java.io.PrintStream; + +public class TagIntArray extends Tag { + TagIntArray(String name, int[] value) { + super(Type.TAG_Int_Array, name, value); + } + + TagIntArray(String name, DataInputStream dis) throws IOException { + this(name, readPayload(dis)); + } + + private static int[] readPayload(DataInputStream dis) throws IOException { + int length = dis.readInt(); + int[] ia = new int[length]; + for (int i=0; i extends SequenceTagBase { + Type listType; + + /** Create a new TAG_List NBT tag. + * + * @param name name for the new tag or null to create an unnamed tag. + * @param value list of tags to add to the new tag. + */ + public TagList(String name, Tag[] value) { + super(Type.TAG_List, name, value); + this.listType = value[0].type; + } + + /** Create a new TAG_List with an empty list. Use {@link TagList#addTag(Tag)} to add tags later. + * + * @param name name for this tag or null to create an unnamed tag. + * @param listType type of the elements in this empty list. + */ + public TagList(String name, Type listType) { + super(Type.TAG_List, name, new Tag[0]); + this.listType = listType; + } + + TagList(String name, DataInputStream dis) throws IOException { + super(Type.TAG_List, name, new Tag[0]); + Object payload = readPayload(dis); + if (payload instanceof Type) { + listType = (Type) payload; + } else { + value = (Tag[]) payload; + listType = value[0].type; + } + } + + private static Object readPayload(DataInputStream dis) throws IOException { + Type type = Type.fromByte(dis.readByte()); + Tag[] lo = new Tag[dis.readInt()]; + for (int i = 0; i < lo.length; i++) + lo[i] = type.readFrom(null, dis); + if (lo.length == 0) + return type; + else + return lo; + } + + void writePayload(DataOutputStream dos) throws IOException { + dos.writeByte(this.listType.ordinal()); + dos.writeInt(value.length); + for (Tag tag : value) + tag.writePayload(dos); + } + + /** Add a tag to a TAG_List + */ + public void addTag(Tag tag) { + insertTag(tag, value.length); + } + + /** Additional setValue method for empty lists + * @param listType + */ + public void setValue(Type listType) { + value = new Tag[0]; + this.listType = listType; + } + + @Override + void serialize(PrintStream ps, int indent) { + serializePrefix(ps, indent, ": ", value.length, " entries of type ", listType); + serializeEntries(ps, indent); + } +} diff --git a/src/amidst/nbt/TagLong.java b/src/amidst/nbt/TagLong.java new file mode 100644 index 00000000..642ac0d9 --- /dev/null +++ b/src/amidst/nbt/TagLong.java @@ -0,0 +1,19 @@ +package amidst.nbt; + +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; + +public class TagLong extends Tag { + TagLong(String name, long value) { + super(Type.TAG_Long, name, value); + } + + TagLong(String name, DataInputStream dis) throws IOException { + this(name, dis.readLong()); + } + + void writePayload(DataOutputStream dos) throws IOException { + dos.writeLong(value); + } +} diff --git a/src/amidst/nbt/TagShort.java b/src/amidst/nbt/TagShort.java new file mode 100644 index 00000000..9bf62274 --- /dev/null +++ b/src/amidst/nbt/TagShort.java @@ -0,0 +1,19 @@ +package amidst.nbt; + +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; + +public class TagShort extends Tag { + TagShort(String name, short value) { + super(Type.TAG_Short, name, value); + } + + TagShort(String name, DataInputStream dis) throws IOException { + this(name, dis.readShort()); + } + + void writePayload(DataOutputStream dos) throws IOException { + dos.writeShort(value); + } +} diff --git a/src/amidst/nbt/TagString.java b/src/amidst/nbt/TagString.java new file mode 100644 index 00000000..34f67f43 --- /dev/null +++ b/src/amidst/nbt/TagString.java @@ -0,0 +1,19 @@ +package amidst.nbt; + +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; + +public class TagString extends Tag { + TagString(String name, String value) { + super(Type.TAG_String, name, value); + } + + TagString(String name, DataInputStream dis) throws IOException { + this(name, dis.readUTF()); + } + + void writePayload(DataOutputStream dos) throws IOException { + dos.writeUTF(value); + } +} diff --git a/src/test/amidst/nbt/TagTest.java b/src/test/amidst/nbt/TagTest.java index 655bd6de..1432e462 100644 --- a/src/test/amidst/nbt/TagTest.java +++ b/src/test/amidst/nbt/TagTest.java @@ -4,6 +4,12 @@ import org.junit.Assert; import org.junit.Before; import org.junit.Test; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.PrintStream; +import java.util.Scanner; + import static amidst.nbt.Tag.Type.*; /** Tests NBT Tag class. @@ -33,4 +39,40 @@ public class TagTest extends Assert { assertEquals(TAG_Compound, types[10]); assertEquals(TAG_Int_Array, types[11]); } + + //testRead covers this implicitly + /* Every Tag subclass has to have a constructor with the interface + * (String, DataInputStream) throws IOException + @Test + public void testConstructors() { + + }*/ + + /** Reads bigtest.nbt from wiki.vg + */ + @Test + public void testRead() throws IOException { + testReadImpl(); + } + + private TagCompound testReadImpl() throws IOException { + return Tag.readFrom(TagTest.class.getResourceAsStream("bigtest.nbt")); + } + + /** Tests correctness of serialization + */ + @Test + public void testSerialization() throws IOException { + TagCompound bigTest = testReadImpl(); + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + PrintStream ps = new PrintStream(baos); + bigTest.serialize(ps, 0); + String serialized = baos.toString(); + + InputStream reference = TagTest.class.getResourceAsStream("bigtest.nbt.txt"); + Scanner s = new Scanner(reference).useDelimiter("\\A"); + String expected = s.next(); + + assertEquals(expected, serialized); + } } diff --git a/src/test/amidst/nbt/bigtest.nbt b/src/test/amidst/nbt/bigtest.nbt new file mode 100644 index 00000000..dc3769bc Binary files /dev/null and b/src/test/amidst/nbt/bigtest.nbt differ diff --git a/src/test/amidst/nbt/bigtest.nbt.txt b/src/test/amidst/nbt/bigtest.nbt.txt new file mode 100644 index 00000000..4efe7303 --- /dev/null +++ b/src/test/amidst/nbt/bigtest.nbt.txt @@ -0,0 +1,45 @@ +TAG_Compound("Level"): 11 entries +{ + TAG_Long("longTest"): 9223372036854775807 + TAG_Short("shortTest"): 32767 + TAG_String("stringTest"): HELLO WORLD THIS IS A TEST STRING ÅÄÖ! + TAG_Float("floatTest"): 0.49823147 + TAG_Int("intTest"): 2147483647 + TAG_Compound("nested compound test"): 2 entries + { + TAG_Compound("ham"): 2 entries + { + TAG_String("name"): Hampus + TAG_Float("value"): 0.75 + } + TAG_Compound("egg"): 2 entries + { + TAG_String("name"): Eggbert + TAG_Float("value"): 0.5 + } + } + TAG_List("listTest (long)"): 5 entries of type TAG_Long + { + TAG_Long: 11 + TAG_Long: 12 + TAG_Long: 13 + TAG_Long: 14 + TAG_Long: 15 + } + TAG_List("listTest (compound)"): 2 entries of type TAG_Compound + { + TAG_Compound: 2 entries + { + TAG_String("name"): Compound tag #0 + TAG_Long("created-on"): 1264099775885 + } + TAG_Compound: 2 entries + { + TAG_String("name"): Compound tag #1 + TAG_Long("created-on"): 1264099775885 + } + } + TAG_Byte("byteTest"): 127 + TAG_Byte_Array("byteArrayTest (the first 1000 values of (n*n*255+n*7)%100, starting with n=0 (0, 62, 34, 16, 8, ...))"): [1000 bytes] + TAG_Double("doubleTest"): 0.4931287132182315 +}