Classe Java pour gérer le format NBT
| Le contenu de cette page n'est pas cautionné par Mojang, le Minecraft Wiki, le Chan 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.
[modifier] Documentation
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.