Add support for reading and writing NBT tags in packets.

This wraps the internal NBT read/write system in Minecraft.
This commit is contained in:
Kristian S. Stangeland 2013-01-07 10:14:25 +01:00
parent 73e71ff954
commit 671654aaaf
17 changed files with 1965 additions and 2 deletions

View File

@ -56,6 +56,7 @@ import com.comphenix.protocol.wrappers.BukkitConverters;
import com.comphenix.protocol.wrappers.ChunkPosition;
import com.comphenix.protocol.wrappers.WrappedDataWatcher;
import com.comphenix.protocol.wrappers.WrappedWatchableObject;
import com.comphenix.protocol.wrappers.nbt.NbtWrapper;
import com.google.common.base.Function;
import com.google.common.collect.Maps;
@ -364,6 +365,17 @@ public class PacketContainer implements Serializable {
ChunkPosition.getConverter());
}
/**
* Retrieves a read/write structure for NBT classes.
* @return A modifier for NBT classes.
*/
public StructureModifier<NbtWrapper<?>> getNbtModifier() {
// Allow access to the NBT class in packet 130
return structureModifier.withType(
MinecraftReflection.getNBTBaseClass(),
BukkitConverters.getNbtConverter());
}
/**
* Retrieves a read/write structure for collections of chunk positions.
* <p>

View File

@ -24,8 +24,6 @@ import java.util.concurrent.Executors;
import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.annotation.Nullable;

View File

@ -376,6 +376,14 @@ public class MinecraftReflection {
return getMinecraftClass("WatchableObject");
}
/**
* Retrieve the NBT base class.
* @return The NBT base class.
*/
public static Class<?> getNBTBaseClass() {
return getMinecraftClass("NBTBase");
}
/**
* Retrieve the ItemStack[] class.
* @return The ItemStack[] class.

View File

@ -34,6 +34,8 @@ import com.comphenix.protocol.reflect.EquivalentConverter;
import com.comphenix.protocol.reflect.FieldAccessException;
import com.comphenix.protocol.reflect.instances.DefaultInstances;
import com.comphenix.protocol.utility.MinecraftReflection;
import com.comphenix.protocol.wrappers.nbt.NbtFactory;
import com.comphenix.protocol.wrappers.nbt.NbtWrapper;
/**
* Contains several useful equivalent converters for normal Bukkit types.
@ -208,6 +210,32 @@ public class BukkitConverters {
});
}
/**
* Retrieve an equivalent converter for net.minecraft.server NBT classes and their wrappers.
* @return An equivalent converter for NBT.
*/
public static EquivalentConverter<NbtWrapper<?>> getNbtConverter() {
return getIgnoreNull(new EquivalentConverter<NbtWrapper<?>>() {
@Override
public Object getGeneric(Class<?> genericType, NbtWrapper<?> specific) {
return specific.getHandle();
}
@Override
public NbtWrapper<?> getSpecific(Object generic) {
return NbtFactory.fromNMS(generic);
}
@Override
@SuppressWarnings("unchecked")
public Class<NbtWrapper<?>> getSpecificType() {
// Damn you Java AGAIN
Class<?> dummy = NbtWrapper.class;
return (Class<NbtWrapper<?>>) dummy;
}
});
}
/**
* Retrieve a converter for NMS entities and Bukkit entities.
* @param world - the current world.

View File

@ -0,0 +1,34 @@
package com.comphenix.protocol.wrappers.nbt;
import javax.annotation.Nullable;
import com.google.common.base.Function;
abstract class AbstractConverted<VInner, VOuter> {
/**
* Convert a value from the inner map to the outer visible map.
* @param inner - the inner value.
* @return The outer value.
*/
protected abstract VOuter toOuter(VInner inner);
/**
* Convert a value from the outer map to the internal inner map.
* @param outer - the outer value.
* @return The inner value.
*/
protected abstract VInner toInner(VOuter outer);
/**
* Retrieve a function delegate that converts inner objects to outer objects.
* @return A function delegate.
*/
protected Function<VInner, VOuter> getOuterConverter() {
return new Function<VInner, VOuter>() {
@Override
public VOuter apply(@Nullable VInner param) {
return toOuter(param);
}
};
}
}

View File

@ -0,0 +1,119 @@
package com.comphenix.protocol.wrappers.nbt;
import java.lang.reflect.Array;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import com.google.common.collect.Iterators;
import com.google.common.collect.Lists;
abstract class ConvertedCollection<VInner, VOuter> extends AbstractConverted<VInner, VOuter> implements Collection<VOuter> {
// Inner collection
private Collection<VInner> inner;
public ConvertedCollection(Collection<VInner> inner) {
this.inner = inner;
}
@Override
public boolean add(VOuter e) {
return inner.add(toInner(e));
}
@Override
public boolean addAll(Collection<? extends VOuter> c) {
boolean modified = false;
for (VOuter outer : c)
modified |= add(outer);
return modified;
}
@Override
public void clear() {
inner.clear();
}
@Override
@SuppressWarnings("unchecked")
public boolean contains(Object o) {
return inner.contains(toInner((VOuter) o));
}
@Override
public boolean containsAll(Collection<?> c) {
for (Object outer : c) {
if (!contains(outer))
return false;
}
return true;
}
@Override
public boolean isEmpty() {
return inner.isEmpty();
}
@Override
public Iterator<VOuter> iterator() {
return Iterators.transform(inner.iterator(), getOuterConverter());
}
@Override
@SuppressWarnings("unchecked")
public boolean remove(Object o) {
return inner.remove(toInner((VOuter) o));
}
@Override
public boolean removeAll(Collection<?> c) {
boolean modified = false;
for (Object outer : c)
modified |= remove(outer);
return modified;
}
@Override
@SuppressWarnings("unchecked")
public boolean retainAll(Collection<?> c) {
List<VInner> innerCopy = Lists.newArrayList();
// Convert all the elements
for (Object outer : c)
innerCopy.add(toInner((VOuter) outer));
return inner.retainAll(innerCopy);
}
@Override
public int size() {
return inner.size();
}
@Override
@SuppressWarnings("unchecked")
public Object[] toArray() {
Object[] array = inner.toArray();
for (int i = 0; i < array.length; i++)
array[i] = toOuter((VInner) array[i]);
return array;
}
@Override
@SuppressWarnings("unchecked")
public <T> T[] toArray(T[] a) {
T[] array = a;
int index = 0;
if (array.length < size()) {
array = (T[]) Array.newInstance(a.getClass().getComponentType(), size());
}
// Build the output array
for (VInner innerValue : inner)
array[index++] = (T) toOuter(innerValue);
return array;
}
}

View File

@ -0,0 +1,138 @@
package com.comphenix.protocol.wrappers.nbt;
import java.util.Collection;
import java.util.List;
import java.util.ListIterator;
abstract class ConvertedList<VInner, VOuter> extends ConvertedCollection<VInner, VOuter> implements List<VOuter> {
private List<VInner> inner;
public ConvertedList(List<VInner> inner) {
super(inner);
this.inner = inner;
}
@Override
public void add(int index, VOuter element) {
inner.add(index, toInner(element));
}
@Override
public boolean addAll(int index, Collection<? extends VOuter> c) {
return inner.addAll(index, getInnerCollection(c));
}
@Override
public VOuter get(int index) {
return toOuter(inner.get(index));
}
@Override
@SuppressWarnings("unchecked")
public int indexOf(Object o) {
return inner.indexOf(toInner((VOuter) o));
}
@Override
@SuppressWarnings("unchecked")
public int lastIndexOf(Object o) {
return inner.lastIndexOf(toInner((VOuter) o));
}
@Override
public ListIterator<VOuter> listIterator() {
return listIterator(0);
}
@Override
public ListIterator<VOuter> listIterator(int index) {
final ListIterator<VInner> innerIterator = inner.listIterator(index);
return new ListIterator<VOuter>() {
@Override
public void add(VOuter e) {
innerIterator.add(toInner(e));
}
@Override
public boolean hasNext() {
return innerIterator.hasNext();
}
@Override
public boolean hasPrevious() {
return innerIterator.hasPrevious();
}
@Override
public VOuter next() {
return toOuter(innerIterator.next());
}
@Override
public int nextIndex() {
return innerIterator.nextIndex();
}
@Override
public VOuter previous() {
return toOuter(innerIterator.previous());
}
@Override
public int previousIndex() {
return innerIterator.previousIndex();
}
@Override
public void remove() {
innerIterator.remove();
}
@Override
public void set(VOuter e) {
innerIterator.set(toInner(e));
}
};
}
@Override
public VOuter remove(int index) {
return toOuter(inner.remove(index));
}
@Override
public VOuter set(int index, VOuter element) {
return toOuter(inner.set(index, toInner(element)));
}
@Override
public List<VOuter> subList(int fromIndex, int toIndex) {
return new ConvertedList<VInner, VOuter>(inner.subList(fromIndex, toIndex)) {
@Override
protected VInner toInner(VOuter outer) {
return ConvertedList.this.toInner(outer);
}
@Override
protected VOuter toOuter(VInner inner) {
return ConvertedList.this.toOuter(inner);
}
};
}
@SuppressWarnings({"rawtypes", "unchecked"})
private ConvertedCollection<VOuter, VInner> getInnerCollection(Collection c) {
return new ConvertedCollection<VOuter, VInner>(c) {
@Override
protected VOuter toInner(VInner outer) {
return ConvertedList.this.toOuter(outer);
}
@Override
protected VInner toOuter(VOuter inner) {
return ConvertedList.this.toInner(inner);
}
};
}
}

View File

@ -0,0 +1,139 @@
package com.comphenix.protocol.wrappers.nbt;
import java.util.Collection;
import java.util.Map;
import java.util.Set;
abstract class ConvertedMap<Key, VInner, VOuter> extends AbstractConverted<VInner, VOuter> implements Map<Key, VOuter> {
// Inner map
private Map<Key, VInner> inner;
public ConvertedMap(Map<Key, VInner> inner) {
if (inner == null)
throw new IllegalArgumentException("Inner map cannot be NULL.");
this.inner = inner;
}
@Override
public void clear() {
inner.clear();
}
@Override
public boolean containsKey(Object key) {
return inner.containsKey(key);
}
@Override
@SuppressWarnings("unchecked")
public boolean containsValue(Object value) {
return inner.containsValue(toInner((VOuter) value));
}
@Override
public Set<Entry<Key, VOuter>> entrySet() {
return new ConvertedSet<Entry<Key,VInner>, Entry<Key,VOuter>>(inner.entrySet()) {
@Override
protected Entry<Key, VInner> toInner(final Entry<Key, VOuter> outer) {
return new Entry<Key, VInner>() {
@Override
public Key getKey() {
return outer.getKey();
}
@Override
public VInner getValue() {
return ConvertedMap.this.toInner(outer.getValue());
}
@Override
public VInner setValue(VInner value) {
return ConvertedMap.this.toInner(outer.setValue(ConvertedMap.this.toOuter(value)));
}
@Override
public String toString() {
return String.format("\"%s\": %s", getKey(), getValue());
}
};
}
@Override
protected Entry<Key, VOuter> toOuter(final Entry<Key, VInner> inner) {
return new Entry<Key, VOuter>() {
@Override
public Key getKey() {
return inner.getKey();
}
@Override
public VOuter getValue() {
return ConvertedMap.this.toOuter(inner.getValue());
}
@Override
public VOuter setValue(VOuter value) {
return ConvertedMap.this.toOuter(inner.setValue(ConvertedMap.this.toInner(value)));
}
@Override
public String toString() {
return String.format("\"%s\": %s", getKey(), getValue());
}
};
}
};
}
@Override
public VOuter get(Object key) {
return toOuter(inner.get(key));
}
@Override
public boolean isEmpty() {
return inner.isEmpty();
}
@Override
public Set<Key> keySet() {
return inner.keySet();
}
@Override
public VOuter put(Key key, VOuter value) {
return toOuter(inner.put(key, toInner(value)));
}
@Override
public void putAll(Map<? extends Key, ? extends VOuter> m) {
for (Entry<? extends Key, ? extends VOuter> entry : m.entrySet()) {
put(entry.getKey(), entry.getValue());
}
}
@Override
public VOuter remove(Object key) {
return toOuter(inner.remove(key));
}
@Override
public int size() {
return inner.size();
}
@Override
public Collection<VOuter> values() {
return new ConvertedCollection<VInner, VOuter>(inner.values()) {
@Override
protected VOuter toOuter(VInner inner) {
return ConvertedMap.this.toOuter(inner);
}
@Override
protected VInner toInner(VOuter outer) {
return ConvertedMap.this.toInner(outer);
}
};
}
}

View File

@ -0,0 +1,10 @@
package com.comphenix.protocol.wrappers.nbt;
import java.util.Collection;
import java.util.Set;
abstract class ConvertedSet<VInner, VOuter> extends ConvertedCollection<VInner, VOuter> implements Set<VOuter> {
public ConvertedSet(Collection<VInner> inner) {
super(inner);
}
}

View File

@ -0,0 +1,52 @@
package com.comphenix.protocol.wrappers.nbt;
import com.comphenix.protocol.wrappers.nbt.NbtBase;
import com.comphenix.protocol.wrappers.nbt.NbtType;
/**
* Represents a generic container for an NBT element.
* @author Kristian
*
* @param <TType> - type of the value that is stored.
*/
public interface NbtBase<TType> {
/**
* Retrieve the type of this NBT element.
* @return The type of this NBT element.
*/
public abstract NbtType getType();
/**
* Retrieve the name of this NBT tag.
* <p>
* This will be an empty string if the NBT tag is stored in a list.
* @return Name of the tag.
*/
public abstract String getName();
/**
* Set the name of this NBT tag.
* <p>
* This will be ignored if the NBT tag is stored in a list.
* @param name - name of the tag.
*/
public abstract void setName(String name);
/**
* Retrieve the value of this NBT tag.
* @return Value of this tag.
*/
public abstract TType getValue();
/**
* Set the value of this NBT tag.
* @param newValue - the new value of this tag.
*/
public abstract void setValue(TType newValue);
/**
* Clone the current NBT tag.
* @return The cloned tag.
*/
public abstract NbtBase<TType> clone();
}

View File

@ -0,0 +1,440 @@
package com.comphenix.protocol.wrappers.nbt;
import java.io.DataOutput;
import java.util.Collection;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
/**
* Represents a mapping of arbitrary NBT elements and their unique names.
* <p>
* Use {@link NbtFactory} to load or create an instance.
*
* @author Kristian
*/
public class NbtCompound implements NbtWrapper<Map<String, NbtBase<?>>>, Iterable<NbtBase<?>> {
// A list container
private NbtElement<Map<String, Object>> container;
// Saved wrapper map
private ConvertedMap<String, Object, NbtBase<?>> savedMap;
/**
* Construct a new NBT compound wrapper.
* @param name - the name of the wrapper.
* @return The wrapped NBT compound.
*/
public static NbtCompound fromName(String name) {
// Simplify things for the caller
return (NbtCompound) NbtFactory.<Map<String, NbtBase<?>>>ofType(NbtType.TAG_COMPOUND, name);
}
/**
* Construct a new NBT compound wrapper initialized with a given list of NBT values.
* @param name - the name of the compound wrapper.
* @param list - the list of elements to add.
* @return The new wrapped NBT compound.
*/
public static <T> NbtCompound fromList(String name, Collection<? extends NbtBase<T>> list) {
NbtCompound copy = new NbtCompound(name);
for (NbtBase<T> base : list)
copy.getValue().put(base.getName(), base);
return copy;
}
/**
* Construct a wrapped compound from a given NMS handle.
* @param handle - the NMS handle.
*/
NbtCompound(Object handle) {
this.container = new NbtElement<Map<String,Object>>(handle);
}
@Override
public Object getHandle() {
return container.getHandle();
}
@Override
public NbtType getType() {
return NbtType.TAG_COMPOUND;
}
@Override
public String getName() {
return container.getName();
}
@Override
public void setName(String name) {
container.setName(name);
}
/**
* Retrieve a Set view of the keys of each entry in this compound.
* @return The keys of each entry.
*/
public Set<String> getKeys() {
return getValue().keySet();
}
/**
* Retrieve a Collection view of the entries in this compound.
* @return A view of each NBT tag in this compound.
*/
public Collection<NbtBase<?>> asCollection(){
return getValue().values();
}
@Override
public Map<String, NbtBase<?>> getValue() {
// Return a wrapper map
if (savedMap == null) {
savedMap = new ConvertedMap<String, Object, NbtBase<?>>(container.getValue()) {
@Override
protected Object toInner(NbtBase<?> outer) {
if (outer == null)
return null;
return NbtFactory.fromBase(outer).getHandle();
}
protected NbtBase<?> toOuter(Object inner) {
if (inner == null)
return null;
return NbtFactory.fromNMS(inner);
};
@Override
public String toString() {
return NbtCompound.this.toString();
}
};
}
return savedMap;
}
@Override
public void setValue(Map<String, NbtBase<?>> newValue) {
// Write all the entries
for (Map.Entry<String, NbtBase<?>> entry : newValue.entrySet()) {
put(entry.getValue());
}
}
/**
* Retrieve the value of a given entry.
* @param key - key of the entry to retrieve.
* @return The value of this entry.
*/
@SuppressWarnings("unchecked")
public <T> NbtBase<T> getValue(String key) {
return (NbtBase<T>) getValue().get(key);
}
/**
* Retrieve a value, or throw an exception.
* @param key - the key to retrieve.
* @return The value of the entry.
*/
private <T> NbtBase<T> getValueExact(String key) {
NbtBase<T> value = getValue(key);
// Only return a legal key
if (value != null)
return value;
else
throw new IllegalArgumentException("Cannot find key " + key);
}
@SuppressWarnings({"unchecked", "rawtypes"})
public NbtBase<Map<String, NbtBase<?>>> clone() {
return (NbtBase) container.clone();
}
/**
* Set a entry based on its name.
* @param entry - entry with a name and value.
* @return This compound, for chaining.
*/
public <T> NbtCompound put(NbtBase<T> entry) {
getValue().put(entry.getName(), entry);
return this;
}
/**
* Retrieve the string value of an entry identified by a given key.
* @param key - the key of the entry.
* @return The string value of the entry.
*/
public String getString(String key) {
return (String) getValueExact(key).getValue();
}
/**
* Associate a NBT string value with the given key.
* @param key - the key and NBT name.
* @param value - the value.
* @return This current compound, for chaining.
*/
public NbtCompound put(String key, String value) {
getValue().put(key, NbtFactory.of(key, value));
return this;
}
/**
* Retrieve the byte value of an entry identified by a given key.
* @param key - the key of the entry.
* @return The byte value of the entry.
*/
public Byte getByte(String key) {
return (Byte) getValueExact(key).getValue();
}
/**
* Associate a NBT byte value with the given key.
* @param key - the key and NBT name.
* @param value - the value.
* @return This current compound, for chaining.
*/
public NbtCompound put(String key, byte value) {
getValue().put(key, NbtFactory.of(key, value));
return this;
}
/**
* Retrieve the short value of an entry identified by a given key.
* @param key - the key of the entry.
* @return The short value of the entry.
*/
public Short getShort(String key) {
return (Short) getValueExact(key).getValue();
}
/**
* Associate a NBT short value with the given key.
* @param key - the key and NBT name.
* @param value - the value.
* @return This current compound, for chaining.
*/
public NbtCompound put(String key, short value) {
getValue().put(key, NbtFactory.of(key, value));
return this;
}
/**
* Retrieve the integer value of an entry identified by a given key.
* @param key - the key of the entry.
* @return The integer value of the entry.
*/
public Integer getInteger(String key) {
return (Integer) getValueExact(key).getValue();
}
/**
* Associate a NBT integer value with the given key.
* @param key - the key and NBT name.
* @param value - the value.
* @return This current compound, for chaining.
*/
public NbtCompound put(String key, int value) {
getValue().put(key, NbtFactory.of(key, value));
return this;
}
/**
* Retrieve the long value of an entry identified by a given key.
* @param key - the key of the entry.
* @return The long value of the entry.
*/
public Long getLong(String key) {
return (Long) getValueExact(key).getValue();
}
/**
* Associate a NBT long value with the given key.
* @param key - the key and NBT name.
* @param value - the value.
* @return This current compound, for chaining.
*/
public NbtCompound put(String key, long value) {
getValue().put(key, NbtFactory.of(key, value));
return this;
}
/**
* Retrieve the float value of an entry identified by a given key.
* @param key - the key of the entry.
* @return The float value of the entry.
*/
public Float getFloat(String key) {
return (Float) getValueExact(key).getValue();
}
/**
* Associate a NBT float value with the given key.
* @param key - the key and NBT name.
* @param value - the value.
* @return This current compound, for chaining.
*/
public NbtCompound put(String key, float value) {
getValue().put(key, NbtFactory.of(key, value));
return this;
}
/**
* Retrieve the double value of an entry identified by a given key.
* @param key - the key of the entry.
* @return The double value of the entry.
*/
public Double getDouble(String key) {
return (Double) getValueExact(key).getValue();
}
/**
* Associate a NBT double value with the given key.
* @param key - the key and NBT name.
* @param value - the value.
* @return This current compound, for chaining.
*/
public NbtCompound put(String key, double value) {
getValue().put(key, NbtFactory.of(key, value));
return this;
}
/**
* Retrieve the byte array value of an entry identified by a given key.
* @param key - the key of the entry.
* @return The byte array value of the entry.
*/
public byte[] getByteArray(String key) {
return (byte[]) getValueExact(key).getValue();
}
/**
* Associate a NBT byte array value with the given key.
* @param key - the key and NBT name.
* @param value - the value.
* @return This current compound, for chaining.
*/
public NbtCompound put(String key, byte[] value) {
getValue().put(key, NbtFactory.of(key, value));
return this;
}
/**
* Retrieve the integer array value of an entry identified by a given key.
* @param key - the key of the entry.
* @return The integer array value of the entry.
*/
public int[] getIntegerArray(String key) {
return (int[]) getValueExact(key).getValue();
}
/**
* Associate a NBT integer array value with the given key.
* @param key - the key and NBT name.
* @param value - the value.
* @return This current compound, for chaining.
*/
public NbtCompound put(String key, int[] value) {
getValue().put(key, NbtFactory.of(key, value));
return this;
}
/**
* Retrieve the compound (map) value of an entry identified by a given key.
* @param key - the key of the entry.
* @return The compound value of the entry.
*/
@SuppressWarnings("rawtypes")
public NbtCompound getCompound(String key) {
return (NbtCompound) ((NbtBase) getValueExact(key));
}
/**
* Associate a NBT compound with its name as key.
* @param compound - the compound value.
* @return This current compound, for chaining.
*/
public NbtCompound put(NbtCompound compound) {
getValue().put(compound.getName(), compound);
return this;
}
/**
* Retrieve the NBT list value of an entry identified by a given key.
* @param key - the key of the entry.
* @return The NBT list value of the entry.
*/
@SuppressWarnings({"unchecked", "rawtypes"})
public <T> NbtList<T> getList(String key) {
return (NbtList) getValueExact(key);
}
/**
* Associate a NBT list with the given key.
* @param list - the list value.
* @return This current compound, for chaining.
*/
public <T> NbtCompound put(NbtList<T> list) {
getValue().put(list.getName(), list);
return this;
}
/**
* Associate a new NBT list with the given key.
* @param key - the key and name of the new NBT list.
* @param list - the list of NBT elements.
* @return This current compound, for chaining.
*/
public <T> NbtCompound put(String key, Collection<? extends NbtBase<T>> list) {
return put(NbtList.fromList(key, list));
}
@Override
public void write(DataOutput destination) {
NbtFactory.toStream(container, destination);
}
@Override
public boolean equals(Object obj) {
if (obj instanceof NbtCompound) {
NbtCompound other = (NbtCompound) obj;
return container.equals(other.container);
}
return false;
}
@Override
public int hashCode() {
return container.hashCode();
}
@Override
public Iterator<NbtBase<?>> iterator() {
return getValue().values().iterator();
}
@Override
public String toString() {
StringBuilder builder = new StringBuilder();
builder.append("{");
builder.append("\"name\": \"" + getName() + "\"");
for (NbtBase<?> element : this) {
builder.append(", ");
// Wrap in quotation marks
if (element.getType() == NbtType.TAG_STRING)
builder.append("\"" + element.getName() + "\": \"" + element.getValue() + "\"");
else
builder.append("\"" + element.getName() + "\": " + element.getValue());
}
builder.append("}");
return builder.toString();
}
}

View File

@ -0,0 +1,213 @@
package com.comphenix.protocol.wrappers.nbt;
import java.io.DataOutput;
import java.lang.reflect.Method;
import com.comphenix.protocol.reflect.FieldAccessException;
import com.comphenix.protocol.reflect.FuzzyReflection;
import com.comphenix.protocol.reflect.StructureModifier;
import com.comphenix.protocol.utility.MinecraftReflection;
import com.google.common.base.Objects;
/**
* Represents an arbitrary NBT tag element, composite or not.
* <p>
* Use {@link NbtFactory} to load or create an instance.
* @author Kristian
*
* @param <TType> - type of the value field.
*/
public class NbtElement<TType> implements NbtWrapper<TType> {
// Structure modifier for the base class
private static StructureModifier<Object> baseModifier;
// For retrieving the current type ID
private static Method methodGetTypeID;
// For cloning handles
private static Method methodClone;
// Structure modifiers for the different NBT elements
private static StructureModifier<?>[] modifiers = new StructureModifier<?>[NbtType.values().length];
// The underlying NBT object
private Object handle;
// Saved type
private NbtType type;
/**
* Initialize a NBT wrapper for a generic element.
* @param handle - the NBT element to wrap.
*/
NbtElement(Object handle) {
this.handle = handle;
}
/**
* Retrieve the modifier (with no target) that is used to read and write the NBT name.
* @return A modifier for accessing the NBT name.
*/
protected static StructureModifier<String> getBaseModifier() {
if (baseModifier == null) {
Class<?> base = MinecraftReflection.getNBTBaseClass();
// This will be the same for all classes, so we'll share modifier
baseModifier = new StructureModifier<Object>(base, Object.class, false).withType(String.class);
}
return baseModifier.withType(String.class);
}
/**
* Retrieve a modifier (with no target) that is used to read and write the NBT value.
* @return The value modifier.
*/
protected StructureModifier<TType> getCurrentModifier() {
NbtType type = getType();
return getCurrentBaseModifier().withType(type.getValueType());
}
/**
* Get the object modifier (with no target) for the current underlying NBT object.
* @return The generic modifier.
*/
@SuppressWarnings("unchecked")
protected StructureModifier<Object> getCurrentBaseModifier() {
int index = getType().ordinal();
StructureModifier<Object> modifier = (StructureModifier<Object>) modifiers[index];
// Double checked locking
if (modifier == null) {
synchronized (this) {
if (modifiers[index] == null) {
modifiers[index] = new StructureModifier<Object>(handle.getClass(), MinecraftReflection.getNBTBaseClass(), false);
}
modifier = (StructureModifier<Object>) modifiers[index];
}
}
return modifier;
}
/**
* Retrieve the underlying NBT tag object.
* @return The underlying Minecraft tag object.
*/
@Override
public Object getHandle() {
return handle;
}
@Override
public NbtType getType() {
if (methodGetTypeID == null) {
// Use the base class
methodGetTypeID = FuzzyReflection.fromClass(MinecraftReflection.getNBTBaseClass()).
getMethodByParameters("getTypeID", byte.class, new Class<?>[0]);
}
if (type == null) {
try {
type = NbtType.getTypeFromID((Byte) methodGetTypeID.invoke(handle));
} catch (Exception e) {
throw new FieldAccessException("Cannot get NBT type of " + handle, e);
}
}
return type;
}
NbtType getSubType() {
int subID = getCurrentBaseModifier().<Byte>withType(byte.class).withTarget(handle).read(0);
return NbtType.getTypeFromID(subID);
}
void setSubType(NbtType type) {
byte subID = (byte) type.getRawID();
getCurrentBaseModifier().<Byte>withType(byte.class).withTarget(handle).write(0, subID);
}
@Override
public String getName() {
return getBaseModifier().withTarget(handle).read(0);
}
@Override
public void setName(String name) {
getBaseModifier().withTarget(handle).write(0, name);
}
@Override
public TType getValue() {
return getCurrentModifier().withTarget(handle).read(0);
}
@Override
public void setValue(TType newValue) {
getCurrentModifier().withTarget(handle).write(0, newValue);
}
@Override
public void write(DataOutput destination) {
NbtFactory.toStream(this, destination);
}
@Override
public NbtBase<TType> clone() {
if (methodClone == null) {
Class<?> base = MinecraftReflection.getNBTBaseClass();
// Use the base class
methodClone = FuzzyReflection.fromClass(base).
getMethodByParameters("clone", base, new Class<?>[0]);
}
try {
return NbtFactory.fromNMS(methodClone.invoke(handle));
} catch (Exception e) {
throw new FieldAccessException("Unable to clone " + handle, e);
}
}
@Override
public int hashCode() {
return Objects.hashCode(getName(), getType(), getValue());
}
@Override
public boolean equals(Object obj) {
if (obj instanceof NbtBase) {
NbtBase<?> other = (NbtBase<?>) obj;
// Make sure we're dealing with the same type
if (other.getType().equals(getType())) {
return Objects.equal(getName(), other.getName()) &&
Objects.equal(getValue(), other.getValue());
}
}
return false;
}
@Override
public String toString() {
StringBuilder result = new StringBuilder();
String name = getName();
result.append("{");
if (name != null && name.length() > 0)
result.append("name: '" + name + "', ");
result.append("value: ");
// Wrap quotation marks
if (getType() == NbtType.TAG_STRING)
result.append("'" + getValue() + "'");
else
result.append(getValue());
result.append("}");
return result.toString();
}
}

View File

@ -0,0 +1,250 @@
package com.comphenix.protocol.wrappers.nbt;
import java.io.DataInput;
import java.io.DataOutput;
import java.lang.reflect.Method;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import com.comphenix.protocol.reflect.FieldAccessException;
import com.comphenix.protocol.reflect.FuzzyReflection;
import com.comphenix.protocol.utility.MinecraftReflection;
/**
* Factory methods for creating NBT elements, lists and compounds.
*
* @author Kristian
*/
public class NbtFactory {
// Used to create the underlying tag
private static Method methodCreateTag;
// Used to read and write NBT
private static Method methodWrite;
private static Method methodLoad;
/**
* Get a NBT wrapper from a NBT base.
* @param base - the base class.
* @return A NBT wrapper.
*/
@SuppressWarnings("unchecked")
public static <T> NbtWrapper<T> fromBase(NbtBase<T> base) {
if (base instanceof NbtElement) {
return (NbtElement<T>) base;
} else if (base instanceof NbtCompound) {
return (NbtWrapper<T>) base;
} else if (base instanceof NbtList) {
return (NbtWrapper<T>) base;
} else {
if (base.getType() == NbtType.TAG_COMPOUND) {
// Load into a NBT-backed wrapper
NbtCompound copy = NbtCompound.fromName(base.getName());
T value = base.getValue();
copy.setValue((Map<String, NbtBase<?>>) value);
return (NbtWrapper<T>) copy;
} else if (base.getType() == NbtType.TAG_LIST) {
// As above
NbtList<T> copy = NbtList.fromName(base.getName());
copy.setValue((List<NbtBase<T>>) base.getValue());
return (NbtWrapper<T>) copy;
} else {
// Copy directly
NbtWrapper<T> copy = ofType(base.getType(), base.getName());
copy.setValue(base.getValue());
return copy;
}
}
}
/**
* Initialize a NBT wrapper.
* @param handle - the underlying net.minecraft.server object to wrap.
* @return A NBT wrapper.
*/
@SuppressWarnings({"unchecked", "rawtypes"})
public static <T> NbtWrapper<T> fromNMS(Object handle) {
NbtElement<T> partial = new NbtElement<T>(handle);
// See if this is actually a compound tag
if (partial.getType() == NbtType.TAG_COMPOUND)
return (NbtWrapper<T>) new NbtCompound(handle);
else if (partial.getType() == NbtType.TAG_LIST)
return new NbtList(handle);
else
return partial;
}
/**
* Write the content of a wrapped NBT tag to a stream.
* @param value - the NBT tag to write.
* @param destination - the destination stream.
*/
public static <TType> void toStream(NbtBase<TType> value, DataOutput destination) {
if (methodWrite == null) {
Class<?> base = MinecraftReflection.getNBTBaseClass();
// Use the base class
methodWrite = FuzzyReflection.fromClass(base).
getMethodByParameters("writeNBT", base, DataOutput.class);
}
try {
methodWrite.invoke(null, fromBase(value).getHandle(), destination);
} catch (Exception e) {
throw new FieldAccessException("Unable to write NBT " + value, e);
}
}
/**
* Load an NBT tag from a stream.
* @param source - the input stream.
* @return An NBT tag.
*/
public static NbtBase<?> fromStream(DataInput source) {
if (methodLoad == null) {
Class<?> base = MinecraftReflection.getNBTBaseClass();
// Use the base class
methodLoad = FuzzyReflection.fromClass(base).
getMethodByParameters("load", base, new Class<?>[] { DataInput.class });
}
try {
return fromNMS(methodLoad.invoke(null, source));
} catch (Exception e) {
throw new FieldAccessException("Unable to read NBT from " + source, e);
}
}
public static NbtBase<String> of(String name, String value) {
return ofType(NbtType.TAG_STRING, name, value);
}
public static NbtBase<Byte> of(String name, byte value) {
return ofType(NbtType.TAG_BYTE, name, value);
}
public static NbtBase<Short> of(String name, short value) {
return ofType(NbtType.TAG_SHORT, name, value);
}
public static NbtBase<Integer> of(String name, int value) {
return ofType(NbtType.TAG_INT, name, value);
}
public static NbtBase<Long> of(String name, long value) {
return ofType(NbtType.TAG_LONG, name, value);
}
public static NbtBase<Float> of(String name, float value) {
return ofType(NbtType.TAG_FLOAT, name, value);
}
public static NbtBase<Double> of(String name, double value) {
return ofType(NbtType.TAG_DOUBlE, name, value);
}
public static NbtBase<byte[]> of(String name, byte[] value) {
return ofType(NbtType.TAG_BYTE_ARRAY, name, value);
}
public static NbtBase<int[]> of(String name, int[] value) {
return ofType(NbtType.TAG_INT_ARRAY, name, value);
}
/**
* Construct a new NBT compound wrapper initialized with a given list of NBT values.
* @param name - the name of the compound wrapper.
* @param list - the list of elements to add.
* @return The new wrapped NBT compound.
*/
public static <T> NbtCompound ofCompound(String name, Collection<? extends NbtBase<T>> list) {
return NbtCompound.fromList(name, list);
}
/**
* Construct a NBT list of out an array of values.
* @param name - name of this list.
* @param elements - elements to add.
* @return The new filled NBT list.
*/
public static <T> NbtList<T> ofList(String name, T... elements) {
return NbtList.fromArray(name, elements);
}
/**
* Create a new NBT wrapper from a given type.
* @param type - the NBT type.
* @param name - the name of the NBT tag.
* @return The new wrapped NBT tag.
* @throws FieldAccessException If we're unable to create the underlying tag.
*/
@SuppressWarnings({"unchecked", "rawtypes"})
public static <T> NbtWrapper<T> ofType(NbtType type, String name) {
if (type == null)
throw new IllegalArgumentException("type cannot be NULL.");
if (type == NbtType.TAG_END)
throw new IllegalArgumentException("Cannot create a TAG_END.");
if (methodCreateTag == null) {
Class<?> base = MinecraftReflection.getNBTBaseClass();
// Use the base class
methodCreateTag = FuzzyReflection.fromClass(base).
getMethodByParameters("createTag", base, new Class<?>[] { byte.class, String.class });
}
try {
Object handle = methodCreateTag.invoke(null, (byte) type.getRawID(), name);
if (type == NbtType.TAG_COMPOUND)
return (NbtWrapper<T>) new NbtCompound(handle);
else if (type == NbtType.TAG_LIST)
return (NbtWrapper<T>) new NbtList(handle);
else
return new NbtElement<T>(handle);
} catch (Exception e) {
// Inform the caller
throw new FieldAccessException(
String.format("Cannot create NBT element %s (type: %s)", name, type),
e);
}
}
/**
* Create a new NBT wrapper from a given type.
* @param type - the NBT type.
* @param name - the name of the NBT tag.
* @param value - the value of the new tag.
* @return The new wrapped NBT tag.
* @throws FieldAccessException If we're unable to create the underlying tag.
*/
public static <T> NbtWrapper<T> ofType(NbtType type, String name, T value) {
NbtWrapper<T> created = ofType(type, name);
// Update the value
created.setValue(value);
return created;
}
/**
* Create a new NBT wrapper from a given type.
* @param type - type of the NBT value.
* @param name - the name of the NBT tag.
* @param value - the value of the new tag.
* @return The new wrapped NBT tag.
* @throws FieldAccessException If we're unable to create the underlying tag.
* @throws IllegalArgumentException If the given class type is not valid NBT.
*/
public static <T> NbtWrapper<T> ofType(Class<?> type, String name, T value) {
return ofType(NbtType.getTypeFromClass(type), name, value);
}
}

View File

@ -0,0 +1,311 @@
package com.comphenix.protocol.wrappers.nbt;
import java.io.DataOutput;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import javax.annotation.Nullable;
import com.google.common.base.Function;
import com.google.common.base.Joiner;
import com.google.common.collect.Iterables;
/**
* Represents a list of NBT tags of the same type without names.
* <p>
* Use {@link NbtFactory} to load or create an instance.
*
* @author Kristian
*
* @param <TType> - the value type of each NBT tag.
*/
public class NbtList<TType> implements NbtWrapper<List<NbtBase<TType>>>, Iterable<TType> {
/**
* The name of every NBT tag in a list.
*/
public static String EMPTY_NAME = "";
// A list container
private NbtElement<List<Object>> container;
// Saved wrapper list
private ConvertedList<Object, NbtBase<TType>> savedList;
/**
* Construct a new empty NBT list.
* @param name - name of this list.
* @return The new empty NBT list.
*/
public static <T> NbtList<T> fromName(String name) {
return (NbtList<T>) NbtFactory.<List<NbtBase<T>>>ofType(NbtType.TAG_LIST, name);
}
/**
* Construct a NBT list of out an array of values..
* @param name - name of this list.
* @param elements - values to add.
* @return The new filled NBT list.
*/
public static <T> NbtList<T> fromArray(String name, T... elements) {
NbtList<T> result = fromName(name);
for (T element : elements) {
if (element == null)
throw new IllegalArgumentException("An NBT list cannot contain a null element!");
result.add(NbtFactory.ofType(element.getClass(), EMPTY_NAME, element));
}
return result;
}
/**
* Construct a NBT list of out a list of NBT elements.
* @param name - name of this list.
* @param elements - elements to add.
* @return The new filled NBT list.
*/
public static <T> NbtList<T> fromList(String name, Collection<? extends T> elements) {
NbtList<T> result = fromName(name);
for (T element : elements) {
if (element == null)
throw new IllegalArgumentException("An NBT list cannot contain a null element!");
result.add(NbtFactory.ofType(element.getClass(), EMPTY_NAME, element));
}
return result;
}
NbtList(Object handle) {
this.container = new NbtElement<List<Object>>(handle);
}
@Override
public Object getHandle() {
return container.getHandle();
}
@Override
public NbtType getType() {
return NbtType.TAG_LIST;
}
/**
* Get the type of each element.
* @return Element type.
*/
public NbtType getElementType() {
return container.getSubType();
}
@Override
public String getName() {
return container.getName();
}
@Override
public void setName(String name) {
container.setName(name);
}
@Override
public List<NbtBase<TType>> getValue() {
if (savedList == null) {
savedList = new ConvertedList<Object, NbtBase<TType>>(container.getValue()) {
@Override
public boolean add(NbtBase<TType> e) {
if (e == null)
throw new IllegalArgumentException("Cannot store NULL elements in list.");
if (!e.getName().equals(EMPTY_NAME))
throw new IllegalArgumentException("Cannot add a named NBT tag " + e + " to a list.");
if (size() == 0)
container.setSubType(e.getType());
return super.add(e);
}
@Override
public void add(int index, NbtBase<TType> element) {
if (element == null)
throw new IllegalArgumentException("Cannot store NULL elements in list.");
if (!element.getName().equals(EMPTY_NAME))
throw new IllegalArgumentException("Cannot add a the named NBT tag " + element + " to a list.");
if (index == 0)
container.setSubType(element.getType());
super.add(index, element);
}
@Override
public boolean addAll(Collection<? extends NbtBase<TType>> c) {
boolean empty = size() == 0;
boolean result = false;
for (NbtBase<TType> element : c) {
add(element);
result = true;
}
// See if we now added our first object(s)
if (empty && result) {
container.setSubType(get(0).getType());
}
return result;
}
@Override
protected Object toInner(NbtBase<TType> outer) {
if (outer == null)
return null;
return NbtFactory.fromBase(outer).getHandle();
}
@Override
protected NbtBase<TType> toOuter(Object inner) {
if (inner == null)
return null;
return NbtFactory.fromNMS(inner);
}
@Override
public String toString() {
return NbtList.this.toString();
}
};
}
return savedList;
}
@SuppressWarnings({"unchecked", "rawtypes"})
public NbtBase<List<NbtBase<TType>>> clone() {
return (NbtBase) container.clone();
}
public void add(NbtBase<TType> element) {
getValue().add(element);
}
@SuppressWarnings("unchecked")
public void add(String value) {
add((NbtBase<TType>) NbtFactory.of(EMPTY_NAME, value));
}
@SuppressWarnings("unchecked")
public void add(byte value) {
add((NbtBase<TType>) NbtFactory.of(EMPTY_NAME, value));
}
@SuppressWarnings("unchecked")
public void add(short value) {
add((NbtBase<TType>) NbtFactory.of(EMPTY_NAME, value));
}
@SuppressWarnings("unchecked")
public void add(int value) {
add((NbtBase<TType>) NbtFactory.of(EMPTY_NAME, value));
}
@SuppressWarnings("unchecked")
public void add(long value) {
add((NbtBase<TType>) NbtFactory.of(EMPTY_NAME, value));
}
@SuppressWarnings("unchecked")
public void add(double value) {
add((NbtBase<TType>) NbtFactory.of(EMPTY_NAME, value));
}
@SuppressWarnings("unchecked")
public void add(byte[] value) {
add((NbtBase<TType>) NbtFactory.of(EMPTY_NAME, value));
}
@SuppressWarnings("unchecked")
public void add(int[] value) {
add((NbtBase<TType>) NbtFactory.of(EMPTY_NAME, value));
}
public int size() {
return getValue().size();
}
public TType getValue(int index) {
return getValue().get(index).getValue();
}
/**
* Retrieve each NBT tag in this list.
* @return A view of NBT tag in this list.
*/
public Collection<NbtBase<TType>> asCollection() {
return getValue();
}
@Override
public void setValue(List<NbtBase<TType>> newValue) {
NbtBase<TType> lastElement = null;
List<Object> list = container.getValue();
list.clear();
// Set each underlying element
for (NbtBase<TType> type : newValue) {
if (type != null) {
lastElement = type;
list.add(NbtFactory.fromBase(type).getHandle());
} else {
list.add(null);
}
}
// Update the sub type as well
if (lastElement != null) {
container.setSubType(lastElement.getType());
}
}
@Override
public void write(DataOutput destination) {
NbtFactory.toStream(container, destination);
}
@Override
public boolean equals(Object obj) {
if (obj instanceof NbtList) {
@SuppressWarnings("unchecked")
NbtList<TType> other = (NbtList<TType>) obj;
return container.equals(other.container);
}
return false;
}
@Override
public int hashCode() {
return container.hashCode();
}
@Override
public Iterator<TType> iterator() {
return Iterables.transform(getValue(), new Function<NbtBase<TType>, TType>() {
@Override
public TType apply(@Nullable NbtBase<TType> param) {
return param.getValue();
}
}).iterator();
}
@Override
public String toString() {
StringBuilder builder = new StringBuilder();
builder.append("{\"name\": \"" + getName() + "\", \"value\": [");
if (size() > 0) {
if (getElementType() == NbtType.TAG_STRING)
builder.append("\"" + Joiner.on("\", \"").join(this) + "\"");
else
builder.append(Joiner.on(", ").join(this));
}
builder.append("]}");
return builder.toString();
}
}

View File

@ -0,0 +1,141 @@
package com.comphenix.protocol.wrappers.nbt;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* Represents all the element types
*
* @author Kristian
*/
public enum NbtType {
/**
* Used to mark the end of compound tags. CANNOT be constructed.
*/
TAG_END(0, Void.class),
/**
* A signed 1 byte integral type. Sometimes used for booleans.
*/
TAG_BYTE(1, byte.class),
/**
* A signed 2 byte integral type.
*/
TAG_SHORT(2, short.class),
/**
* A signed 4 byte integral type.
*/
TAG_INT(3, int.class),
/**
* A signed 8 byte integral type.
*/
TAG_LONG(4, long.class),
/**
* A signed 4 byte floating point type.
*/
TAG_FLOAT(5, float.class),
/**
* A signed 8 byte floating point type.
*/
TAG_DOUBlE(6, double.class),
/**
* An array of bytes.
*/
TAG_BYTE_ARRAY(7, byte[].class),
/**
* An array of TAG_Int's payloads..
*/
TAG_INT_ARRAY(11, int[].class),
/**
* A UTF-8 string
*/
TAG_STRING(8, String.class),
/**
* A list of tag payloads, without repeated tag IDs or any tag names.
*/
TAG_LIST(9, List.class),
/**
* A list of fully formed tags, including their IDs, names, and payloads. No two tags may have the same name.
*/
TAG_COMPOUND(10, Map.class);
private int rawID;
private Class<?> valueType;
// Used to lookup a specified NBT
private static NbtType[] lookup;
// Lookup NBT by class
private static Map<Class<?>, NbtType> classLookup;
static {
NbtType[] values = values();
lookup = new NbtType[values.length];
classLookup = new HashMap<Class<?>, NbtType>();
// Initialize lookup tables
for (NbtType type : values) {
lookup[type.getRawID()] = type;
classLookup.put(type.getValueType(), type);
}
}
private NbtType(int rawID, Class<?> valueType) {
this.rawID = rawID;
this.valueType = valueType;
}
/**
* Retrieves the raw unique integer that identifies the type of the parent NBT element.
* @return Integer that uniquely identifying the type.
*/
public int getRawID() {
return rawID;
}
/**
* Retrieves the type of the value stored in the NBT element.
* @return Type of the stored value.
*/
public Class<?> getValueType() {
return valueType;
}
/**
* Retrieve an NBT type from a given raw ID.
* @param rawID - the raw ID to lookup.
* @return The associated NBT value.
*/
public static NbtType getTypeFromID(int rawID) {
if (rawID < 0 || rawID >= lookup.length)
throw new IllegalArgumentException("Unrecognized raw ID " + rawID);
return lookup[rawID];
}
/**
* Retrieve an NBT type from the given Java class.
* @param clazz - type of the value the NBT type can contain.
* @return The NBT type.
* @throws IllegalArgumentException If this class type cannot be represented by NBT tags.
*/
public static NbtType getTypeFromClass(Class<?> clazz) {
NbtType result = classLookup.get(clazz);
// Try to lookup this value
if (result != null)
return result;
else
throw new IllegalArgumentException("No NBT tag can represent a " + clazz);
}
}

View File

@ -0,0 +1,24 @@
package com.comphenix.protocol.wrappers.nbt;
import java.io.DataOutput;
/**
* Indicates that this NBT wraps an underlying net.minecraft.server instance.
*
* @author Kristian
*
* @param <TType> - type of the value that is stored.
*/
public interface NbtWrapper<TType> extends NbtBase<TType> {
/**
* Retrieve the underlying net.minecraft.server instance.
* @return The NMS instance.
*/
public Object getHandle();
/**
* Write the current NBT tag to an output stream.
* @param destination - the destination stream.
*/
public void write(DataOutput destination);
}

View File

@ -0,0 +1,46 @@
package com.comphenix.protocol.wrappers.nbt;
import static org.junit.Assert.*;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataInput;
import java.io.DataInputStream;
import java.io.DataOutput;
import java.io.DataOutputStream;
import org.junit.BeforeClass;
import org.junit.Test;
import com.comphenix.protocol.utility.MinecraftReflection;
public class NbtFactoryTest {
@BeforeClass
public static void initializeBukkit() {
// Initialize reflection
MinecraftReflection.setMinecraftPackage("net.minecraft.server.v1_4_6", "org.bukkit.craftbukkit.v1_4_6");
}
@Test
public void testFromStream() {
NbtCompound compound = NbtCompound.fromName("tag");
compound.put("name", "Test Testerson");
compound.put("age", 42);
compound.put(NbtFactory.ofList("nicknames", "a", "b", "c"));
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
DataOutput test = new DataOutputStream(buffer);
compound.write(test);
ByteArrayInputStream source = new ByteArrayInputStream(buffer.toByteArray());
DataInput input = new DataInputStream(source);
NbtCompound cloned = (NbtCompound) NbtFactory.fromStream(input);
assertEquals(compound.getString("name"), cloned.getString("name"));
assertEquals(compound.getInteger("age"), cloned.getInteger("age"));
assertEquals(compound.getList("nicknames"), cloned.getList("nicknames"));
}
}