Classe Java pour gérer le format NBT

De Minecraft Wiki
Aller à : navigation, rechercher
Le contenu de cette page n'est pas cautionné par Mojang, le Minecraft Wiki, le Canal IRC Minecraft ni le Forum Minecraft.

Cette classe Java lit une structure NBT et retourne le plus haut tag d'un InputStream (classe abstraite permettant de lire les flux d'octets) et permet d'écrire une structure NBT à travers le plus haut tag d'un OutputStream (classe abstraite permettant d'écrire des flux d'octets). Une documentation condensée se trouve au bas de la page.

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.zip.GZIPInputStream;
import java.util.zip.GZIPOutputStream;

/**
* NBT IO class
*
* @see <a href="http://www.minecraft.net/docs/NBT.txt">Online NBT specification</a>
*/
public class Tag {
	private final Type type;
	private Type listType = null;
	private final String name;
	private Object 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
	}
	/**
	 * 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 <code>null</code> 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 <code>null</code> 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 <code>{@link Type}</code> enum.
	 * @param name name for the new tag or <code>null</code> to create an unnamed tag.
	 * @param value an object that fits the tag type or a <code>{@link Type}</code> to create an empty TAG_List with this list type.
	 */
	public Tag(Type type, String name, Object value) {
		if (type == Type.TAG_Compound)
			if (!(value instanceof Tag[]))
				throw new IllegalArgumentException();
		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();
		}
		this.type = type;
		this.name = name;
		this.value = value;
	}

	public Type getType() {
		return type;
	}

	public String getName() {
		return name;
	}

	public Object getValue() {
		return value;
	}

	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;
		insertTag(tag, subtags.length);
	}

	/**
	 * 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 <code>null</code> 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 <code>null</code> 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.
	 *
	 * @param is stream to read from, like a FileInputStream
	 * @return NBT tag or structure read from the InputStream
	 * @throws IOException if there was no valid NBT structure in the InputStream or if another IOException occurred.
	 */
	public static Tag readFrom(InputStream is) throws IOException {
		DataInputStream dis = new DataInputStream(new GZIPInputStream(is));
		byte type = dis.readByte();
		Tag tag = null;

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

	private static Object readPayload(DataInputStream dis, byte type) throws IOException {
		switch (type) {
		case 0:
			return null;
		case 1:
			return dis.readByte();
		case 2:
			return dis.readShort();
		case 3:
			return dis.readInt();
		case 4:
			return dis.readLong();
		case 5:
			return dis.readFloat();
		case 6:
			return dis.readDouble();
		case 7:
			int length = dis.readInt();
			byte[] ba = new byte[length];
			dis.readFully(ba);
			return ba;
		case 8:
			return dis.readUTF();
		case 9:
			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 10:
			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;
		}
		return null;
	}

	/**
	 * Read a tag and its nested tags from an InputStream.
	 *
	 * @param os stream to write to, like a FileOutputStream
	 * @throws IOException if this is not a valid NBT structure or if any IOException occurred.
	 */
	public void writeTo(OutputStream os) throws IOException {
		GZIPOutputStream gzos;
		DataOutputStream dos = new DataOutputStream(gzos = new GZIPOutputStream(os));
		dos.writeByte(type.ordinal());
		if (type != Type.TAG_End) {
			dos.writeUTF(name);
			writePayload(dos);
		}
		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;
		}
	}

	/**
	 * Print the NBT structure to System.out
	 */
	public void print() {
		print(this, 0);
	}

	private String getTypeString(Type type) {
		switch (type) {
		case TAG_End:
			return "TAG_End";
		case TAG_Byte:
			return "TAG_Byte";
		case TAG_Short:
			return "TAG_Short";
		case TAG_Int:
			return "TAG_Int";
		case TAG_Long:
			return "TAG_Long";
		case TAG_Float:
			return "TAG_Float";
		case TAG_Double:
			return "TAG_Double";
		case TAG_Byte_Array:
			return "TAG_Byte_Array";
		case TAG_String:
			return "TAG_String";
		case TAG_List:
			return "TAG_List";
		case TAG_Compound:
			return "TAG_Compound";
		}
		return null;
	}

	private void indent(int indent) {
		for (int i = 0; i < indent; i++) {
			System.out.print("   ");
		}
	}

	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(getTypeString(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 " + getTypeString(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());
		}
	}

	// For testing purposes.
	public static void main(String[] args) {
		try {
			String filename = "bigtest.nbt";
			if (args.length > 0)
				filename = args[0];
			Tag test = Tag.readFrom(new FileInputStream(filename));
			FileOutputStream fos = new FileOutputStream("bigtestwrite.nbt");
			test.writeTo(fos);
			fos.close();
			test = Tag.readFrom(new FileInputStream("bigtestwrite.nbt"));
			test.print();
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
}

Si vous trouvez des bugs : corrigez cette page.

Si vous n'aimez pas ce code, peut-être que le projet JNBT vous intéressera. La classe ci-dessus n'est pas liée à JNBT.

Documentation[modifier | modifier le wikicode]

Voici les fonctions utilisées par la classe ci-dessus ainsi que leur documentation, sous forme condensée. Les fonctions privées n'y sont pas incluses. Les fonctions sont divisées en 4 catégories : constructeurs, assesseurs, manipulation de tags et entrée/sortie. Le but de cette documentation est d'aider les lecteurs à comprendre comment ça fonctionne et de leur permettre d'utiliser la classe Tag.


Constructeurs :

  • Tag(Type type,String name,Tag[] value)
    • Description : Créée un nouveau TAG_List ou un nouveau tag NBT TAG_Compound.
    • Paramètre type : Soit TAG_List, soit TAG_Compound.
    • Paramètre name : Nom du nouveau tag ou null pour créer un tag sans nom.
    • Paramètre value : Liste des tags à ajouter au nouveau tag.
  • Tag(String name, Type listType)
    • Description : Créée un nouveau TAG_List avec une liste vide. Utilisez la fonction addTag(Tag tag) pour y rajouter des tags par la suite.
    • Paramètre name : Nom de ce tag ou null pour créer un tag sans nom.
    • Paramètre listType : Type des éléments dans cette liste vide.
  • Tag(Type type, String name, Object value)
    • Description : Créée un nouveau tag NBT.
    • Paramètre type : N'importe quelle valeur de l'énumération Type.
    • Paramètre name : Nom du nouveau tag ou null pour créer un tag sans nom.
    • Paramètre value : Un objet qui correspond au type de tag ou un élément de l'énumération Type pour créer un TAG_List vide avec ce type de liste.


Accesseurs :

  • getType()
  • getName()
  • getValue()
  • getListType()


Manipulation de tags :

  • addTag(Tag tag)
    • Description : Ajoute un tag à un TAG_List ou à un TAG_Compound.
  • insertTag(Tag tag, int index)
    • Description : Ajoute un tag à un TAG_List ou à un TAG_Compound à l'index spécifié.
  • removeTag(int index)
    • Description: Retire un tag d'un TAG_List ou d'un TAG_Compound à l'index spécifié.
    • valeur de retour : Le tag retiré.
  • removeSubTag(Tag tag)
    • Description : Retire un tag d'un TAG_List ou d'un TAG_Compound. Si le tag n'hérite pas de ce tag, alors une recherche est effectuée sur les tags imbriqués.
  • findTagByName(String name)
    • Description : Trouve le premier tag imbriqué ayant le nom spécifié dans un TAG_Compound.
    • Paramètre name : Le nom à rechercher. Peut valoir null pour chercher des tags sans nom.
    • Valeur de retour : Le premier tag imbriqué ayant le nom spécifié.
  • findNextTagByName(String name, Tag found)
    • Description : Trouve le premier tag imbriqué ayant le nom spécifié dans un TAG_List ou dans un TAG_Compound après un tag ayant le même nom.
    • Paramètre name : Le nom à rechercher. Peut valoir null pour chercher des tags sans nom.
    • Paramètre found : Le tag ayant le même nom précédemment trouvé.
    • Valeur de retour : Le premier tag imbriqué ayant le nom spécifié après le tag précédemment trouvé.


Entrée/sortie :

  • readFrom(InputStream is)
    • Description : Lit un tag et ses tags imbriqués à partir d'un InputStream.
    • Paramètre is : Le flux à lire, un FileInputStream par exemple.
    • Valeur de retour : Tag NBT ou structure lue à partir du flux spécifié.
    • Exception : IOException s'il n'y avait aucune structure NBT valide dans le flux ou si une autre IOException est levée.
  • writeTo(OutputStream os)
    • Description : Ecrit un tag et ses tags imbriqués dans un OutputStream.
    • Paramètre os : Le flux sur lequel écrire, un FileOutputStream par exemple.
    • Exception : IOException si ce n'est pas une structure NBT valide ou si une autre IOException est levée.
  • print()
    • Description : Envoie la structure NBT sur le flux System.out. Print the NBT structure to System.out.