source) {
+ StructureKey key = new StructureKey(source);
+
+ // See if there's a need to lookup the class name
+ if (compiledCache.containsKey(key)) {
+ return true;
+ }
+
+ try {
+ String className = getCompiledName(source);
+
+ // This class might have been generated before. Try to load it.
+ Class> before = loader.loadClass(PACKAGE_NAME.replace('/', '.') + "." + className);
+
+ if (before != null) {
+ compiledCache.put(key, before);
+ return true;
+ }
+ } catch (ClassNotFoundException e) {
+ // That's ok.
+ }
+
+ // We need to compile the class
+ return false;
+ }
+
/**
* Compiles the given structure modifier.
*
@@ -158,7 +193,7 @@ public final class StructureCompiler {
return source;
}
- StructureKey key = new StructureKey(source.getTargetType(), source.getFieldType());
+ StructureKey key = new StructureKey(source);
Class> compiledClass = compiledCache.get(key);
if (!compiledCache.containsKey(key)) {
@@ -195,29 +230,35 @@ public final class StructureCompiler {
return type.getCanonicalName().replace("[]", "Array").replace(".", "_");
}
+ /**
+ * Retrieve the compiled name of a given structure modifier.
+ * @param source - the structure modifier.
+ * @return The unique, compiled name of a compiled structure modifier.
+ */
+ private String getCompiledName(StructureModifier> source) {
+ Class> targetType = source.getTargetType();
+
+ // Concat class and field type
+ return "CompiledStructure$" +
+ getSafeTypeName(targetType) + "$" +
+ getSafeTypeName(source.getFieldType());
+ }
+
+ /**
+ * Compile a structure modifier.
+ * @param source - structure modifier.
+ * @return The compiled structure modifier.
+ */
private Class> generateClass(StructureModifier source) {
ClassWriter cw = new ClassWriter(0);
-
- @SuppressWarnings("rawtypes")
- Class targetType = source.getTargetType();
+ Class> targetType = source.getTargetType();
- String className = "CompiledStructure$" +
- getSafeTypeName(targetType) + "$" +
- getSafeTypeName(source.getFieldType());
+ String className = getCompiledName(source);
String targetSignature = Type.getDescriptor(targetType);
String targetName = targetType.getName().replace('.', '/');
- try {
- // This class might have been generated before. Try to load it.
- Class> before = loader.loadClass(PACKAGE_NAME.replace('/', '.') + "." + className);
-
- if (before != null)
- return before;
- } catch (ClassNotFoundException e) {
- // That's ok.
- }
-
+ // Define class
cw.visit(Opcodes.V1_6, Opcodes.ACC_PUBLIC + Opcodes.ACC_SUPER, PACKAGE_NAME + "/" + className,
null, COMPILED_CLASS, null);
diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/utility/MinecraftReflection.java b/ProtocolLib/src/main/java/com/comphenix/protocol/utility/MinecraftReflection.java
index 31094d57..f9924a3a 100644
--- a/ProtocolLib/src/main/java/com/comphenix/protocol/utility/MinecraftReflection.java
+++ b/ProtocolLib/src/main/java/com/comphenix/protocol/utility/MinecraftReflection.java
@@ -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.
diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/utility/StreamSerializer.java b/ProtocolLib/src/main/java/com/comphenix/protocol/utility/StreamSerializer.java
new file mode 100644
index 00000000..ac4fe360
--- /dev/null
+++ b/ProtocolLib/src/main/java/com/comphenix/protocol/utility/StreamSerializer.java
@@ -0,0 +1,103 @@
+package com.comphenix.protocol.utility;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.IOException;
+import java.lang.reflect.Method;
+import org.bukkit.inventory.ItemStack;
+import org.yaml.snakeyaml.external.biz.base64Coder.Base64Coder;
+
+import com.comphenix.protocol.reflect.FuzzyReflection;
+
+/**
+ * Utility methods for reading and writing Minecraft objects to streams.
+ *
+ * @author Kristian
+ */
+public class StreamSerializer {
+ // Cached methods
+ private static Method readItemMethod;
+ private static Method writeItemMethod;
+
+ /**
+ * Read or deserialize an item stack from an underlying input stream.
+ *
+ * To supply a byte array, wrap it in a {@link java.io.ByteArrayInputStream ByteArrayInputStream}
+ * and {@link java.io.DataInputStream DataInputStream}.
+ *
+ * @param input - the target input stream.
+ * @return The resulting item stack.
+ * @throws IOException If the operation failed due to reflection or corrupt data.
+ */
+ public ItemStack deserializeItemStack(DataInputStream input) throws IOException {
+ if (readItemMethod == null)
+ readItemMethod = FuzzyReflection.fromClass(MinecraftReflection.getPacketClass()).
+ getMethodByParameters("readPacket",
+ MinecraftReflection.getItemStackClass(),
+ new Class>[] {DataInputStream.class});
+ try {
+ Object nmsItem = readItemMethod.invoke(null, input);
+
+ // Convert back to a Bukkit item stack
+ return MinecraftReflection.getBukkitItemStack(nmsItem);
+
+ } catch (Exception e) {
+ throw new IOException("Cannot read item stack.", e);
+ }
+ }
+
+ /**
+ * Deserialize an item stack from a base-64 encoded string.
+ * @param input - base-64 encoded string.
+ * @return A deserialized item stack.
+ * @throws IOException If the operation failed due to reflection or corrupt data.
+ */
+ public ItemStack deserializeItemStack(String input) throws IOException {
+ ByteArrayInputStream inputStream = new ByteArrayInputStream(Base64Coder.decodeLines(input));
+
+ return deserializeItemStack(new DataInputStream(inputStream));
+ }
+
+ /**
+ * Write or serialize an item stack to the given output stream.
+ *
+ * To supply a byte array, wrap it in a {@link java.io.ByteArrayOutputStream ByteArrayOutputStream}
+ * and {@link java.io.DataOutputStream DataOutputStream}.
+ *
+ * @param output - the target output stream.
+ * @param stack - the item stack that will be written.
+ * @throws IOException If the operation fails due to reflection problems.
+ */
+ public void serializeItemStack(DataOutputStream output, ItemStack stack) throws IOException {
+ Object nmsItem = MinecraftReflection.getMinecraftItemStack(stack);
+
+ if (writeItemMethod == null)
+ writeItemMethod = FuzzyReflection.fromClass(MinecraftReflection.getPacketClass()).
+ getMethodByParameters("writePacket", new Class>[] {
+ MinecraftReflection.getItemStackClass(),
+ DataOutputStream.class });
+ try {
+ writeItemMethod.invoke(null, nmsItem, output);
+ } catch (Exception e) {
+ throw new IOException("Cannot write item stack " + stack, e);
+ }
+ }
+
+ /**
+ * Serialize an item stack as a base-64 encoded string.
+ * @param stack - the item stack to serialize.
+ * @return A base-64 representation of the given item stack.
+ * @throws IOException If the operation fails due to reflection problems.
+ */
+ public String serializeItemStack(ItemStack stack) throws IOException {
+ ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+ DataOutputStream dataOutput = new DataOutputStream(outputStream);
+
+ serializeItemStack(dataOutput, stack);
+
+ // Serialize that array
+ return Base64Coder.encodeLines(outputStream.toByteArray());
+ }
+}
diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/BukkitConverters.java b/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/BukkitConverters.java
index 7d6f421e..95606336 100644
--- a/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/BukkitConverters.java
+++ b/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/BukkitConverters.java
@@ -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.NbtBase;
+import com.comphenix.protocol.wrappers.nbt.NbtFactory;
/**
* 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> getNbtConverter() {
+ return getIgnoreNull(new EquivalentConverter>() {
+ @Override
+ public Object getGeneric(Class> genericType, NbtBase> specific) {
+ return NbtFactory.fromBase(specific).getHandle();
+ }
+
+ @Override
+ public NbtBase> getSpecific(Object generic) {
+ return NbtFactory.fromNMS(generic);
+ }
+
+ @Override
+ @SuppressWarnings("unchecked")
+ public Class> getSpecificType() {
+ // Damn you Java AGAIN
+ Class> dummy = NbtBase.class;
+ return (Class>) dummy;
+ }
+ });
+ }
+
/**
* Retrieve a converter for NMS entities and Bukkit entities.
* @param world - the current world.
diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/nbt/AbstractConverted.java b/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/nbt/AbstractConverted.java
new file mode 100644
index 00000000..554d4c6b
--- /dev/null
+++ b/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/nbt/AbstractConverted.java
@@ -0,0 +1,59 @@
+/*
+ * ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
+ * Copyright (C) 2012 Kristian S. Stangeland
+ *
+ * This program is free software; you can redistribute it and/or modify it under the terms of the
+ * GNU General Public License as published by the Free Software Foundation; either version 2 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with this program;
+ * if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
+ * 02111-1307 USA
+ */
+
+package com.comphenix.protocol.wrappers.nbt;
+
+import javax.annotation.Nullable;
+
+import com.google.common.base.Function;
+
+/**
+ * Represents an object that transform elements of type VInner to type VOuter and back again.
+ *
+ * @author Kristian
+ *
+ * @param - the first type.
+ * @param - the second type.
+ */
+abstract class AbstractConverted {
+ /**
+ * 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 getOuterConverter() {
+ return new Function() {
+ @Override
+ public VOuter apply(@Nullable VInner param) {
+ return toOuter(param);
+ }
+ };
+ }
+}
diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/nbt/ConvertedCollection.java b/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/nbt/ConvertedCollection.java
new file mode 100644
index 00000000..dea203e6
--- /dev/null
+++ b/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/nbt/ConvertedCollection.java
@@ -0,0 +1,144 @@
+/*
+ * ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
+ * Copyright (C) 2012 Kristian S. Stangeland
+ *
+ * This program is free software; you can redistribute it and/or modify it under the terms of the
+ * GNU General Public License as published by the Free Software Foundation; either version 2 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with this program;
+ * if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
+ * 02111-1307 USA
+ */
+
+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;
+
+/**
+ * Represents a collection that wraps another collection by transforming the elements going in and out.
+ *
+ * @author Kristian
+ *
+ * @param - type of the element in the inner invisible collection.
+ * @param - type of the elements publically accessible in the outer collection.
+ */
+abstract class ConvertedCollection extends AbstractConverted implements Collection {
+ // Inner collection
+ private Collection inner;
+
+ public ConvertedCollection(Collection 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 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 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[] 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;
+ }
+}
diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/nbt/ConvertedList.java b/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/nbt/ConvertedList.java
new file mode 100644
index 00000000..3562a7c5
--- /dev/null
+++ b/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/nbt/ConvertedList.java
@@ -0,0 +1,163 @@
+/*
+ * ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
+ * Copyright (C) 2012 Kristian S. Stangeland
+ *
+ * This program is free software; you can redistribute it and/or modify it under the terms of the
+ * GNU General Public License as published by the Free Software Foundation; either version 2 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with this program;
+ * if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
+ * 02111-1307 USA
+ */
+
+package com.comphenix.protocol.wrappers.nbt;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.ListIterator;
+
+/**
+ * Represents a list that wraps another list by transforming the items going in and out.
+ *
+ * @author Kristian
+ *
+ * @param - type of the items in the inner invisible list.
+ * @param - type of the items publically accessible in the outer list.
+ */
+abstract class ConvertedList extends ConvertedCollection implements List {
+ private List inner;
+
+ public ConvertedList(List 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 listIterator() {
+ return listIterator(0);
+ }
+
+ @Override
+ public ListIterator listIterator(int index) {
+ final ListIterator innerIterator = inner.listIterator(index);
+
+ return new ListIterator() {
+ @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 subList(int fromIndex, int toIndex) {
+ return new ConvertedList(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 getInnerCollection(Collection c) {
+ return new ConvertedCollection(c) {
+ @Override
+ protected VOuter toInner(VInner outer) {
+ return ConvertedList.this.toOuter(outer);
+ }
+
+ @Override
+ protected VInner toOuter(VOuter inner) {
+ return ConvertedList.this.toInner(inner);
+ }
+ };
+ }
+}
diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/nbt/ConvertedMap.java b/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/nbt/ConvertedMap.java
new file mode 100644
index 00000000..b662d0da
--- /dev/null
+++ b/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/nbt/ConvertedMap.java
@@ -0,0 +1,164 @@
+/*
+ * ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
+ * Copyright (C) 2012 Kristian S. Stangeland
+ *
+ * This program is free software; you can redistribute it and/or modify it under the terms of the
+ * GNU General Public License as published by the Free Software Foundation; either version 2 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with this program;
+ * if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
+ * 02111-1307 USA
+ */
+
+package com.comphenix.protocol.wrappers.nbt;
+
+import java.util.Collection;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Represents a map that wraps another map by transforming the entries going in and out.
+ *
+ * @author Kristian
+ *
+ * @param - type of the value in the entries in the inner invisible map.
+ * @param - type of the value in the entries publically accessible in the outer map.
+ */
+abstract class ConvertedMap extends AbstractConverted implements Map {
+ // Inner map
+ private Map inner;
+
+ public ConvertedMap(Map 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> entrySet() {
+ return new ConvertedSet, Entry>(inner.entrySet()) {
+ @Override
+ protected Entry toInner(final Entry outer) {
+ return new Entry() {
+ @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 toOuter(final Entry inner) {
+ return new Entry() {
+ @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 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 values() {
+ return new ConvertedCollection(inner.values()) {
+ @Override
+ protected VOuter toOuter(VInner inner) {
+ return ConvertedMap.this.toOuter(inner);
+ }
+
+ @Override
+ protected VInner toInner(VOuter outer) {
+ return ConvertedMap.this.toInner(outer);
+ }
+ };
+ }
+}
diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/nbt/ConvertedSet.java b/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/nbt/ConvertedSet.java
new file mode 100644
index 00000000..5aa0d203
--- /dev/null
+++ b/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/nbt/ConvertedSet.java
@@ -0,0 +1,35 @@
+/*
+ * ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
+ * Copyright (C) 2012 Kristian S. Stangeland
+ *
+ * This program is free software; you can redistribute it and/or modify it under the terms of the
+ * GNU General Public License as published by the Free Software Foundation; either version 2 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with this program;
+ * if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
+ * 02111-1307 USA
+ */
+
+package com.comphenix.protocol.wrappers.nbt;
+
+import java.util.Collection;
+import java.util.Set;
+
+/**
+ * Represents a set that wraps another set by transforming the items going in and out.
+ *
+ * @author Kristian
+ *
+ * @param - type of the element in the inner invisible set.
+ * @param - type of the elements publically accessible in the outer set.
+ */
+abstract class ConvertedSet extends ConvertedCollection implements Set {
+ public ConvertedSet(Collection inner) {
+ super(inner);
+ }
+}
diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/nbt/NbtBase.java b/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/nbt/NbtBase.java
new file mode 100644
index 00000000..4fda61b8
--- /dev/null
+++ b/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/nbt/NbtBase.java
@@ -0,0 +1,86 @@
+/*
+ * ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
+ * Copyright (C) 2012 Kristian S. Stangeland
+ *
+ * This program is free software; you can redistribute it and/or modify it under the terms of the
+ * GNU General Public License as published by the Free Software Foundation; either version 2 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with this program;
+ * if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
+ * 02111-1307 USA
+ */
+
+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.
+ *
+ * Use {@link NbtFactory} to load or create an instance.
+ *
+ * @author Kristian
+ * @param - type of the value that is stored.
+ */
+public interface NbtBase {
+ /**
+ * Accepts a NBT visitor.
+ * @param visitor - the hierarchical NBT visitor.
+ * @return TRUE if the parent should continue processing children at the current level, FALSE otherwise.
+ */
+ public abstract boolean accept(NbtVisitor visitor);
+
+ /**
+ * 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.
+ *
+ * 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.
+ *
+ * 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.
+ *
+ * Is either a primitive {@link java.lang.Number wrapper}, {@link java.lang.String String},
+ * {@link java.util.List List} or a {@link java.util.Map Map}.
+ *
+ * All operations that modify collections directly, such as {@link java.util.List#add(Object) List.add(Object)} or
+ * {@link java.util.Map#clear() Map.clear()}, are considered optional. This also include members in {@link java.util.Iterator Iterator} and
+ * {@link java.util.ListIterator ListIterator}. Operations that are not implemented throw a
+ * {@link java.lang.UnsupportedOperationException UnsupportedOperationException}.
+ * @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 deepClone();
+}
\ No newline at end of file
diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/nbt/NbtCompound.java b/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/nbt/NbtCompound.java
new file mode 100644
index 00000000..ba27a6cb
--- /dev/null
+++ b/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/nbt/NbtCompound.java
@@ -0,0 +1,312 @@
+package com.comphenix.protocol.wrappers.nbt;
+
+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.
+ *
+ * Use {@link NbtFactory} to load or create an instance.
+ *
+ * The {@link NbtBase#getValue()} method returns a {@link java.util.Map} that will return the full content
+ * of this NBT compound, but may throw an {@link UnsupportedOperationException} for any of the write operations.
+ *
+ * @author Kristian
+ */
+public interface NbtCompound extends NbtBase>>, Iterable> {
+ /**
+ * Determine if an entry with the given key exists or not.
+ * @param key - the key to lookup.
+ * @return TRUE if an entry with the given key exists, FALSE otherwise.
+ */
+ public abstract boolean containsKey(String key);
+
+ /**
+ * Retrieve a Set view of the keys of each entry in this compound.
+ * @return The keys of each entry.
+ */
+ public abstract Set getKeys();
+
+ /**
+ * Retrieve the value of a given entry.
+ * @param key - key of the entry to retrieve.
+ * @return The value of this entry, or NULL if not found.
+ */
+ public abstract NbtBase getValue(String key);
+
+ /**
+ * Retrieve a value by its key, or assign and return a new NBT element if it doesn't exist.
+ * @param key - the key of the entry to find or create.
+ * @param type - the NBT element we will create if not found.
+ * @return The value that was retrieved or just created.
+ */
+ public abstract NbtBase> getValueOrDefault(String key, NbtType type);
+
+ /**
+ * Set a entry based on its name.
+ * @param entry - entry with a name and value.
+ * @return This compound, for chaining.
+ */
+ public abstract NbtCompound put(NbtBase entry);
+
+ /**
+ * 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.
+ * @throws IllegalArgumentException If the key doesn't exist.
+ */
+ public abstract String getString(String key);
+
+ /**
+ * Retrieve the string value of an existing entry, or from a new default entry if it doesn't exist.
+ * @param key - the key of the entry.
+ * @return The value that was retrieved or just created.
+ */
+ public abstract String getStringOrDefault(String key);
+
+ /**
+ * 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 abstract NbtCompound put(String key, String value);
+
+ /**
+ * Inserts an entry after cloning it and renaming it to "key".
+ * @param key - the name of the entry.
+ * @param entry - the entry to insert.
+ * @return This current compound, for chaining.
+ */
+ public abstract NbtCompound put(String key, NbtBase> entry);
+
+ /**
+ * 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.
+ * @throws IllegalArgumentException If the key doesn't exist.
+ */
+ public abstract byte getByte(String key);
+
+ /**
+ * Retrieve the byte value of an existing entry, or from a new default entry if it doesn't exist.
+ * @param key - the key of the entry.
+ * @return The value that was retrieved or just created.
+ */
+ public abstract byte getByteOrDefault(String key);
+
+ /**
+ * 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 abstract NbtCompound put(String key, byte value);
+
+ /**
+ * 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.
+ * @throws IllegalArgumentException If the key doesn't exist.
+ */
+ public abstract Short getShort(String key);
+
+ /**
+ * Retrieve the short value of an existing entry, or from a new default entry if it doesn't exist.
+ * @param key - the key of the entry.
+ * @return The value that was retrieved or just created.
+ */
+ public abstract short getShortOrDefault(String key);
+
+ /**
+ * 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 abstract NbtCompound put(String key, short value);
+
+ /**
+ * 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.
+ * @throws IllegalArgumentException If the key doesn't exist.
+ */
+ public abstract int getInteger(String key);
+
+ /**
+ * Retrieve the integer value of an existing entry, or from a new default entry if it doesn't exist.
+ * @param key - the key of the entry.
+ * @return The value that was retrieved or just created.
+ */
+ public abstract int getIntegerOrDefault(String key);
+
+ /**
+ * 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 abstract NbtCompound put(String key, int value);
+
+ /**
+ * 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.
+ * @throws IllegalArgumentException If the key doesn't exist.
+ */
+ public abstract long getLong(String key);
+
+ /**
+ * Retrieve the long value of an existing entry, or from a new default entry if it doesn't exist.
+ * @param key - the key of the entry.
+ * @return The value that was retrieved or just created.
+ */
+ public abstract long getLongOrDefault(String key);
+
+ /**
+ * 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 abstract NbtCompound put(String key, long value);
+
+ /**
+ * 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.
+ * @throws IllegalArgumentException If the key doesn't exist.
+ */
+ public abstract float getFloat(String key);
+
+ /**
+ * Retrieve the float value of an existing entry, or from a new default entry if it doesn't exist.
+ * @param key - the key of the entry.
+ * @return The value that was retrieved or just created.
+ */
+ public abstract float getFloatOrDefault(String key);
+
+ /**
+ * 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 abstract NbtCompound put(String key, float value);
+
+ /**
+ * 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.
+ * @throws IllegalArgumentException If the key doesn't exist.
+ */
+ public abstract double getDouble(String key);
+
+ /**
+ * Retrieve the double value of an existing entry, or from a new default entry if it doesn't exist.
+ * @param key - the key of the entry.
+ * @return The value that was retrieved or just created.
+ */
+ public abstract double getDoubleOrDefault(String key);
+
+ /**
+ * 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 abstract NbtCompound put(String key, double value);
+
+ /**
+ * 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.
+ * @throws IllegalArgumentException If the key doesn't exist.
+ */
+ public abstract byte[] getByteArray(String key);
+
+ /**
+ * 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 abstract NbtCompound put(String key, byte[] value);
+
+ /**
+ * 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.
+ * @throws IllegalArgumentException If the key doesn't exist.
+ */
+ public abstract int[] getIntegerArray(String key);
+
+ /**
+ * 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 abstract NbtCompound put(String key, int[] value);
+
+ /**
+ * 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.
+ * @throws IllegalArgumentException If the key doesn't exist.
+ */
+ public abstract NbtCompound getCompound(String key);
+
+ /**
+ * Retrieve a compound (map) value by its key, or create a new compound if it doesn't exist.
+ * @param key - the key of the entry to find or create.
+ * @return The compound value that was retrieved or just created.
+ */
+ public abstract NbtCompound getCompoundOrDefault(String key);
+
+ /**
+ * Associate a NBT compound with its name as key.
+ * @param compound - the compound value.
+ * @return This current compound, for chaining.
+ */
+ public abstract NbtCompound put(WrappedCompound compound);
+
+ /**
+ * 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.
+ * @throws IllegalArgumentException If the key doesn't exist.
+ */
+ public abstract NbtList getList(String key);
+
+ /**
+ * Retrieve a NBT list value by its key, or create a new list if it doesn't exist.
+ * @param key - the key of the entry to find or create.
+ * @return The compound value that was retrieved or just created.
+ */
+ public abstract NbtList getListOrDefault(String key);
+
+ /**
+ * Associate a NBT list with the given key.
+ * @param list - the list value.
+ * @return This current compound, for chaining.
+ */
+ public abstract NbtCompound put(NbtList list);
+
+ /**
+ * 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 abstract NbtCompound put(String key, Collection extends NbtBase> list);
+
+ /**
+ * Retrieve an iterator view of the NBT tags stored in this compound.
+ * @return The tags stored in this compound.
+ */
+ public abstract Iterator> iterator();
+}
\ No newline at end of file
diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/nbt/NbtFactory.java b/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/nbt/NbtFactory.java
new file mode 100644
index 00000000..6ae15713
--- /dev/null
+++ b/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/nbt/NbtFactory.java
@@ -0,0 +1,363 @@
+/*
+ * ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
+ * Copyright (C) 2012 Kristian S. Stangeland
+ *
+ * This program is free software; you can redistribute it and/or modify it under the terms of the
+ * GNU General Public License as published by the Free Software Foundation; either version 2 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with this program;
+ * if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
+ * 02111-1307 USA
+ */
+
+package com.comphenix.protocol.wrappers.nbt;
+
+import java.lang.reflect.Method;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+
+import org.bukkit.inventory.ItemStack;
+
+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.comphenix.protocol.wrappers.BukkitConverters;
+
+/**
+ * Factory methods for creating NBT elements, lists and compounds.
+ *
+ * @author Kristian
+ */
+public class NbtFactory {
+ // Used to create the underlying tag
+ private static Method methodCreateTag;
+
+ // Item stack trickery
+ private static StructureModifier itemStackModifier;
+
+ /**
+ * Attempt to cast this NBT tag as a compund.
+ * @param tag - the NBT tag to cast.
+ * @return This instance as a compound.
+ * @throws UnsupportedOperationException If this is not a compound.
+ */
+ public static NbtCompound asCompound(NbtBase> tag) {
+ if (tag instanceof NbtCompound)
+ return (NbtCompound) tag;
+ else if (tag != null)
+ throw new UnsupportedOperationException(
+ "Cannot cast a " + tag.getClass() + "( " + tag.getType() + ") to TAG_COMPUND.");
+ else
+ throw new IllegalArgumentException("Tag cannot be NULL.");
+ }
+
+ /**
+ * Attempt to cast this NBT tag as a list.
+ * @param tag - the NBT tag to cast.
+ * @return This instance as a list.
+ * @throws UnsupportedOperationException If this is not a list.
+ */
+ public static NbtList> asList(NbtBase> tag) {
+ if (tag instanceof NbtList)
+ return (NbtList>) tag;
+ else if (tag != null)
+ throw new UnsupportedOperationException(
+ "Cannot cast a " + tag.getClass() + "( " + tag.getType() + ") to TAG_LIST.");
+ else
+ throw new IllegalArgumentException("Tag cannot be NULL.");
+ }
+
+ /**
+ * Get a NBT wrapper from a NBT base.
+ *
+ * This may clone the content if the NbtBase is not a NbtWrapper.
+ * @param base - the base class.
+ * @return A NBT wrapper.
+ */
+ @SuppressWarnings("unchecked")
+ public static NbtWrapper fromBase(NbtBase base) {
+ if (base instanceof NbtWrapper) {
+ return (NbtWrapper) base;
+ } else {
+ if (base.getType() == NbtType.TAG_COMPOUND) {
+ // Load into a NBT-backed wrapper
+ WrappedCompound copy = WrappedCompound.fromName(base.getName());
+ T value = base.getValue();
+
+ copy.setValue((Map>) value);
+ return (NbtWrapper) copy;
+
+ } else if (base.getType() == NbtType.TAG_LIST) {
+ // As above
+ NbtList copy = WrappedList.fromName(base.getName());
+
+ copy.setValue((List>) base.getValue());
+ return (NbtWrapper) copy;
+
+ } else {
+ // Copy directly
+ NbtWrapper copy = ofWrapper(base.getType(), base.getName());
+
+ copy.setValue(base.getValue());
+ return copy;
+ }
+ }
+ }
+
+ /**
+ * Construct a wrapper for an NBT tag stored (in memory) in an item stack. This is where
+ * auxillary data such as enchanting, name and lore is stored. It doesn't include the items
+ * material, damage value or count.
+ *
+ * The item stack must be a wrapper for a CraftItemStack. Use
+ * {@link MinecraftReflection#getBukkitItemStack(ItemStack)} if not.
+ * @param stack - the item stack.
+ * @return A wrapper for its NBT tag.
+ */
+ public static NbtWrapper> fromItemTag(ItemStack stack) {
+ if (!MinecraftReflection.isCraftItemStack(stack))
+ throw new IllegalArgumentException("Stack must be a CraftItemStack.");
+
+ Object nmsStack = MinecraftReflection.getMinecraftItemStack(stack);
+
+ if (itemStackModifier == null) {
+ itemStackModifier = new StructureModifier(nmsStack.getClass(), Object.class, false);
+ }
+
+ // Use the first and best NBT tag
+ StructureModifier> modifier = itemStackModifier.
+ withTarget(nmsStack).
+ withType(MinecraftReflection.getNBTBaseClass(), BukkitConverters.getNbtConverter());
+ NbtBase> result = modifier.read(0);
+
+ // Create the tag if it doesn't exist
+ if (result == null) {
+ result = NbtFactory.ofCompound("tag");
+ modifier.write(0, result);
+ }
+ return fromBase(result);
+ }
+
+ /**
+ * Initialize a NBT wrapper.
+ * @param handle - the underlying net.minecraft.server object to wrap.
+ * @return A NBT wrapper.
+ */
+ @SuppressWarnings({"unchecked", "rawtypes"})
+ public static NbtWrapper fromNMS(Object handle) {
+ WrappedElement partial = new WrappedElement(handle);
+
+ // See if this is actually a compound tag
+ if (partial.getType() == NbtType.TAG_COMPOUND)
+ return (NbtWrapper) new WrappedCompound(handle);
+ else if (partial.getType() == NbtType.TAG_LIST)
+ return new WrappedList(handle);
+ else
+ return partial;
+ }
+
+ /**
+ * Constructs a NBT tag of type string.
+ * @param name - name of the tag.
+ * @param value - value of the tag.
+ * @return The constructed NBT tag.
+ */
+ public static NbtBase of(String name, String value) {
+ return ofWrapper(NbtType.TAG_STRING, name, value);
+ }
+
+ /**
+ * Constructs a NBT tag of type byte.
+ * @param name - name of the tag.
+ * @param value - value of the tag.
+ * @return The constructed NBT tag.
+ */
+ public static NbtBase of(String name, byte value) {
+ return ofWrapper(NbtType.TAG_BYTE, name, value);
+ }
+
+ /**
+ * Constructs a NBT tag of type short.
+ * @param name - name of the tag.
+ * @param value - value of the tag.
+ * @return The constructed NBT tag.
+ */
+ public static NbtBase of(String name, short value) {
+ return ofWrapper(NbtType.TAG_SHORT, name, value);
+ }
+
+ /**
+ * Constructs a NBT tag of type int.
+ * @param name - name of the tag.
+ * @param value - value of the tag.
+ * @return The constructed NBT tag.
+ */
+ public static NbtBase of(String name, int value) {
+ return ofWrapper(NbtType.TAG_INT, name, value);
+ }
+
+ /**
+ * Constructs a NBT tag of type long.
+ * @param name - name of the tag.
+ * @param value - value of the tag.
+ * @return The constructed NBT tag.
+ */
+ public static NbtBase of(String name, long value) {
+ return ofWrapper(NbtType.TAG_LONG, name, value);
+ }
+
+ /**
+ * Constructs a NBT tag of type float.
+ * @param name - name of the tag.
+ * @param value - value of the tag.
+ * @return The constructed NBT tag.
+ */
+ public static NbtBase of(String name, float value) {
+ return ofWrapper(NbtType.TAG_FLOAT, name, value);
+ }
+
+ /**
+ * Constructs a NBT tag of type double.
+ * @param name - name of the tag.
+ * @param value - value of the tag.
+ * @return The constructed NBT tag.
+ */
+ public static NbtBase of(String name, double value) {
+ return ofWrapper(NbtType.TAG_DOUBLE, name, value);
+ }
+
+ /**
+ * Constructs a NBT tag of type byte array.
+ * @param name - name of the tag.
+ * @param value - value of the tag.
+ * @return The constructed NBT tag.
+ */
+ public static NbtBase of(String name, byte[] value) {
+ return ofWrapper(NbtType.TAG_BYTE_ARRAY, name, value);
+ }
+
+ /**
+ * Constructs a NBT tag of type int array.
+ * @param name - name of the tag.
+ * @param value - value of the tag.
+ * @return The constructed NBT tag.
+ */
+ public static NbtBase of(String name, int[] value) {
+ return ofWrapper(NbtType.TAG_INT_ARRAY, name, value);
+ }
+
+ /**
+ * Construct a new NBT compound 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 NbtCompound ofCompound(String name, Collection extends NbtBase>> list) {
+ return WrappedCompound.fromList(name, list);
+ }
+
+ /**
+ * Construct a new NBT compound wrapper.
+ * @param name - the name of the compound wrapper.
+ * @return The new wrapped NBT compound.
+ */
+ public static NbtCompound ofCompound(String name) {
+ return WrappedCompound.fromName(name);
+ }
+
+ /**
+ * 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 NbtList ofList(String name, T... elements) {
+ return WrappedList.fromArray(name, elements);
+ }
+
+ /**
+ * Construct a NBT list of out a list of values.
+ * @param name - name of this list.
+ * @param elements - elements to add.
+ * @return The new filled NBT list.
+ */
+ public static NbtList ofList(String name, Collection extends T> elements) {
+ return WrappedList.fromList(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 NbtWrapper ofWrapper(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) new WrappedCompound(handle);
+ else if (type == NbtType.TAG_LIST)
+ return (NbtWrapper) new WrappedList(handle);
+ else
+ return new WrappedElement(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 NbtWrapper ofWrapper(NbtType type, String name, T value) {
+ NbtWrapper created = ofWrapper(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 NbtWrapper ofWrapper(Class> type, String name, T value) {
+ return ofWrapper(NbtType.getTypeFromClass(type), name, value);
+ }
+}
diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/nbt/NbtList.java b/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/nbt/NbtList.java
new file mode 100644
index 00000000..85ef13de
--- /dev/null
+++ b/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/nbt/NbtList.java
@@ -0,0 +1,139 @@
+package com.comphenix.protocol.wrappers.nbt;
+
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.List;
+
+/**
+ * Represents a list of NBT tags of the same type without names.
+ *
+ * Use {@link NbtFactory} to load or create an instance.
+ *
+ * The {@link NbtBase#getValue()} method returns a {@link java.util.List} that will correctly return the content
+ * of this NBT list, but may throw an {@link UnsupportedOperationException} for any of the write operations.
+ *
+ * @author Kristian
+ *
+ * @param - the value type of each NBT tag.
+ */
+public interface NbtList extends NbtBase>>, Iterable {
+ /**
+ * The name of every NBT tag in a list.
+ */
+ public static String EMPTY_NAME = "";
+
+ /**
+ * Get the type of each element.
+ *
+ * This will be {@link NbtType#TAG_END TAG_END} if the NBT list has just been created.
+ * @return Element type.
+ */
+ public abstract NbtType getElementType();
+
+ /**
+ * Set the type of each element.
+ * @param type - type of each element.
+ */
+ public abstract void setElementType(NbtType type);
+
+ /**
+ * Add a value to a typed list by attempting to convert it to the nearest value.
+ *
+ * Note that the list must be typed by setting {@link #setElementType(NbtType)} before calling this function.
+ * @param value - the value to add.
+ */
+ public abstract void addClosest(Object value);
+
+ /**
+ * Add a NBT list or NBT compound to the list.
+ * @param element
+ */
+ public abstract void add(NbtBase element);
+
+ /**
+ * Add a new string element to the list.
+ * @param value - the string element to add.
+ * @throws IllegalArgumentException If this is not a list of strings.
+ */
+ public abstract void add(String value);
+
+ /**
+ * Add a new byte element to the list.
+ * @param value - the byte element to add.
+ * @throws IllegalArgumentException If this is not a list of bytes.
+ */
+ public abstract void add(byte value);
+
+ /**
+ * Add a new short element to the list.
+ * @param value - the short element to add.
+ * @throws IllegalArgumentException If this is not a list of shorts.
+ */
+ public abstract void add(short value);
+
+ /**
+ * Add a new integer element to the list.
+ * @param value - the string element to add.
+ * @throws IllegalArgumentException If this is not a list of integers.
+ */
+ public abstract void add(int value);
+
+ /**
+ * Add a new long element to the list.
+ * @param value - the string element to add.
+ * @throws IllegalArgumentException If this is not a list of longs.
+ */
+ public abstract void add(long value);
+
+ /**
+ * Add a new double element to the list.
+ * @param value - the double element to add.
+ * @throws IllegalArgumentException If this is not a list of doubles.
+ */
+ public abstract void add(double value);
+
+ /**
+ * Add a new byte array element to the list.
+ * @param value - the byte array element to add.
+ * @throws IllegalArgumentException If this is not a list of byte arrays.
+ */
+ public abstract void add(byte[] value);
+
+ /**
+ * Add a new int array element to the list.
+ * @param value - the int array element to add.
+ * @throws IllegalArgumentException If this is not a list of int arrays.
+ */
+ public abstract void add(int[] value);
+
+ /**
+ * Remove a given object from the list.
+ * @param remove - the object to remove.
+ */
+ public abstract void remove(Object remove);
+
+ /**
+ * Retrieve an element by index.
+ * @param index - index of the element to retrieve.
+ * @return The element to retrieve.
+ * @throws IndexOutOfBoundsException If the index is out of range (index < 0 || index >= size())
+ */
+ public abstract TType getValue(int index);
+
+ /**
+ * Retrieve the number of elements in this list.
+ * @return The number of elements in this list.
+ */
+ public abstract int size();
+
+ /**
+ * Retrieve each NBT tag in this list.
+ * @return A view of NBT tag in this list.
+ */
+ public abstract Collection> asCollection();
+
+ /**
+ * Iterate over all the elements in this list.
+ */
+ public abstract Iterator iterator();
+}
\ No newline at end of file
diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/nbt/NbtType.java b/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/nbt/NbtType.java
new file mode 100644
index 00000000..2ef6e59d
--- /dev/null
+++ b/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/nbt/NbtType.java
@@ -0,0 +1,184 @@
+/*
+ * ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
+ * Copyright (C) 2012 Kristian S. Stangeland
+ *
+ * This program is free software; you can redistribute it and/or modify it under the terms of the
+ * GNU General Public License as published by the Free Software Foundation; either version 2 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with this program;
+ * if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
+ * 02111-1307 USA
+ */
+
+package com.comphenix.protocol.wrappers.nbt;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import com.google.common.primitives.Primitives;
+
+/**
+ * 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, NbtType> classLookup;
+
+ static {
+ NbtType[] values = values();
+ lookup = new NbtType[values.length];
+ classLookup = new HashMap, NbtType>();
+
+ // Initialize lookup tables
+ for (NbtType type : values) {
+ lookup[type.getRawID()] = type;
+ classLookup.put(type.getValueType(), type);
+
+ // Add a wrapper type
+ if (type.getValueType().isPrimitive()) {
+ classLookup.put(Primitives.wrap(type.getValueType()), type);
+ }
+ }
+
+ // Additional lookup
+ classLookup.put(NbtList.class, TAG_LIST);
+ classLookup.put(NbtCompound.class, TAG_COMPOUND);
+ }
+
+ private NbtType(int rawID, Class> valueType) {
+ this.rawID = rawID;
+ this.valueType = valueType;
+ }
+
+ /**
+ * Determine if the given NBT can store multiple children NBT tags.
+ * @return TRUE if this is a composite NBT tag, FALSE otherwise.
+ */
+ public boolean isComposite() {
+ return this == TAG_COMPOUND || this == TAG_LIST;
+ }
+
+ /**
+ * 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 {
+ // Look for interfaces
+ for (Class> implemented : clazz.getInterfaces()) {
+ if (classLookup.containsKey(implemented))
+ return classLookup.get(implemented);
+ }
+
+ throw new IllegalArgumentException("No NBT tag can represent a " + clazz);
+ }
+ }
+}
diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/nbt/NbtVisitor.java b/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/nbt/NbtVisitor.java
new file mode 100644
index 00000000..8a9a3ec9
--- /dev/null
+++ b/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/nbt/NbtVisitor.java
@@ -0,0 +1,43 @@
+package com.comphenix.protocol.wrappers.nbt;
+
+/**
+ * A visitor that can enumerate a NBT tree structure.
+ *
+ * @author Kristian
+ */
+public interface NbtVisitor {
+ /**
+ * Visit a leaf node, which is a NBT tag with a primitive or String value.
+ * @param node - the visited leaf node.
+ * @return TRUE to continue visiting children at this level, FALSE otherwise.
+ */
+ public boolean visit(NbtBase> node);
+
+ /**
+ * Begin visiting a list node that contains multiple child nodes of the same type.
+ * @param list - the NBT tag to process.
+ * @return TRUE to visit the child nodes of this list, FALSE otherwise.
+ */
+ public boolean visitEnter(NbtList> list);
+
+ /**
+ * Begin visiting a compound node that contains multiple child nodes of different types.
+ * @param compound - the NBT tag to process.
+ * @return TRUE to visit the child nodes of this compound, FALSE otherwise.
+ */
+ public boolean visitEnter(NbtCompound compound);
+
+ /**
+ * Stop visiting a list node.
+ * @param list - the list we're done visiting.
+ * @return TRUE for the parent to visit any subsequent sibling nodes, FALSE otherwise.
+ */
+ public boolean visitLeave(NbtList> list);
+
+ /**
+ * Stop visiting a compound node.
+ * @param compound - the compound we're done visting.
+ * @return TRUE for the parent to visit any subsequent sibling nodes, FALSE otherwise
+ */
+ public boolean visitLeave(NbtCompound compound);
+}
diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/nbt/NbtWrapper.java b/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/nbt/NbtWrapper.java
new file mode 100644
index 00000000..da1916f0
--- /dev/null
+++ b/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/nbt/NbtWrapper.java
@@ -0,0 +1,43 @@
+/*
+ * ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
+ * Copyright (C) 2012 Kristian S. Stangeland
+ *
+ * This program is free software; you can redistribute it and/or modify it under the terms of the
+ * GNU General Public License as published by the Free Software Foundation; either version 2 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with this program;
+ * if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
+ * 02111-1307 USA
+ */
+
+package com.comphenix.protocol.wrappers.nbt;
+
+import java.io.DataOutput;
+
+/**
+ * Indicates that this NBT wraps an underlying net.minecraft.server instance.
+ *
+ * Use {@link NbtFactory} to load or create instances.
+ *
+ * @author Kristian
+ *
+ * @param - type of the value that is stored.
+ */
+public interface NbtWrapper extends NbtBase {
+ /**
+ * 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);
+}
diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/nbt/WrappedCompound.java b/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/nbt/WrappedCompound.java
new file mode 100644
index 00000000..410e093d
--- /dev/null
+++ b/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/nbt/WrappedCompound.java
@@ -0,0 +1,630 @@
+/*
+ * ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
+ * Copyright (C) 2012 Kristian S. Stangeland
+ *
+ * This program is free software; you can redistribute it and/or modify it under the terms of the
+ * GNU General Public License as published by the Free Software Foundation; either version 2 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with this program;
+ * if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
+ * 02111-1307 USA
+ */
+
+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;
+
+import com.comphenix.protocol.wrappers.nbt.io.NbtBinarySerializer;
+
+/**
+ * A concrete implementation of an NbtCompound that wraps an underlying NMS Compound.
+ *
+ * @author Kristian
+ */
+class WrappedCompound implements NbtWrapper>>, Iterable>, NbtCompound {
+ // A list container
+ private WrappedElement> container;
+
+ // Saved wrapper map
+ private ConvertedMap> savedMap;
+
+ /**
+ * Construct a new NBT compound wrapper.
+ * @param name - the name of the wrapper.
+ * @return The wrapped NBT compound.
+ */
+ public static WrappedCompound fromName(String name) {
+ // Simplify things for the caller
+ return (WrappedCompound) NbtFactory.>>ofWrapper(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 NbtCompound fromList(String name, Collection extends NbtBase>> list) {
+ WrappedCompound copy = fromName(name);
+
+ for (NbtBase> base : list)
+ copy.getValue().put(base.getName(), base);
+ return copy;
+ }
+
+ /**
+ * Construct a wrapped compound from a given NMS handle.
+ * @param handle - the NMS handle.
+ */
+ public WrappedCompound(Object handle) {
+ this.container = new WrappedElement>(handle);
+ }
+
+ @Override
+ public boolean accept(NbtVisitor visitor) {
+ // Enter this node?
+ if (visitor.visitEnter(this)) {
+ for (NbtBase> node : this) {
+ if (!node.accept(visitor))
+ break;
+ }
+ }
+
+ return visitor.visitLeave(this);
+ }
+
+ @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);
+ }
+
+ /**
+ * Determine if an entry with the given key exists or not.
+ * @param key - the key to lookup.
+ * @return TRUE if an entry with the given key exists, FALSE otherwise.
+ */
+ @Override
+ public boolean containsKey(String key) {
+ return getValue().containsKey(key);
+ }
+
+ /**
+ * Retrieve a Set view of the keys of each entry in this compound.
+ * @return The keys of each entry.
+ */
+ @Override
+ public Set getKeys() {
+ return getValue().keySet();
+ }
+
+ @Override
+ public Map> getValue() {
+ // Return a wrapper map
+ if (savedMap == null) {
+ savedMap = new ConvertedMap>(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 WrappedCompound.this.toString();
+ }
+ };
+ }
+ return savedMap;
+ }
+
+ @Override
+ public void setValue(Map> newValue) {
+ // Write all the entries
+ for (Map.Entry> 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, or NULL if not found.
+ */
+ @Override
+ @SuppressWarnings("unchecked")
+ public NbtBase getValue(String key) {
+ return (NbtBase) getValue().get(key);
+ }
+
+ /**
+ * Retrieve a value by its key, or assign and return a new NBT element if it doesn't exist.
+ * @param key - the key of the entry to find or create.
+ * @param type - the NBT element we will create if not found.
+ * @return The value that was retrieved or just created.
+ */
+ @Override
+ public NbtBase> getValueOrDefault(String key, NbtType type) {
+ NbtBase> nbt = getValue(key);
+
+ // Create or get a compound
+ if (nbt == null)
+ put(nbt = NbtFactory.ofWrapper(type, key));
+ else if (nbt.getType() != type)
+ throw new IllegalArgumentException("Cannot get tag " + nbt + ": Not a " + type);
+
+ return nbt;
+ }
+
+ /**
+ * Retrieve a value, or throw an exception.
+ * @param key - the key to retrieve.
+ * @return The value of the entry.
+ * @throws IllegalArgumentException If the key doesn't exist.
+ */
+ private NbtBase getValueExact(String key) {
+ NbtBase value = getValue(key);
+
+ // Only return a legal key
+ if (value != null)
+ return value;
+ else
+ throw new IllegalArgumentException("Cannot find key " + key);
+ }
+
+ @Override
+ @SuppressWarnings({"unchecked", "rawtypes"})
+ public NbtBase>> deepClone() {
+ return (NbtBase) container.deepClone();
+ }
+
+ /**
+ * Set a entry based on its name.
+ * @param entry - entry with a name and value.
+ * @return This compound, for chaining.
+ */
+ @Override
+ public NbtCompound put(NbtBase 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.
+ * @throws IllegalArgumentException If the key doesn't exist.
+ */
+ @Override
+ public String getString(String key) {
+ return (String) getValueExact(key).getValue();
+ }
+
+ /**
+ * Retrieve the string value of an existing entry, or from a new default entry if it doesn't exist.
+ * @param key - the key of the entry.
+ * @return The value that was retrieved or just created.
+ */
+ @Override
+ public String getStringOrDefault(String key) {
+ return (String) getValueOrDefault(key, NbtType.TAG_STRING).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.
+ */
+ @Override
+ 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.
+ * @throws IllegalArgumentException If the key doesn't exist.
+ */
+ @Override
+ public byte getByte(String key) {
+ return (Byte) getValueExact(key).getValue();
+ }
+
+ /**
+ * Retrieve the byte value of an existing entry, or from a new default entry if it doesn't exist.
+ * @param key - the key of the entry.
+ * @return The value that was retrieved or just created.
+ */
+ @Override
+ public byte getByteOrDefault(String key) {
+ return (Byte) getValueOrDefault(key, NbtType.TAG_BYTE).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.
+ */
+ @Override
+ 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.
+ * @throws IllegalArgumentException If the key doesn't exist.
+ */
+ @Override
+ public Short getShort(String key) {
+ return (Short) getValueExact(key).getValue();
+ }
+
+ /**
+ * Retrieve the short value of an existing entry, or from a new default entry if it doesn't exist.
+ * @param key - the key of the entry.
+ * @return The value that was retrieved or just created.
+ */
+ @Override
+ public short getShortOrDefault(String key) {
+ return (Short) getValueOrDefault(key, NbtType.TAG_SHORT).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.
+ */
+ @Override
+ 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.
+ * @throws IllegalArgumentException If the key doesn't exist.
+ */
+ @Override
+ public int getInteger(String key) {
+ return (Integer) getValueExact(key).getValue();
+ }
+
+ /**
+ * Retrieve the integer value of an existing entry, or from a new default entry if it doesn't exist.
+ * @param key - the key of the entry.
+ * @return The value that was retrieved or just created.
+ */
+ @Override
+ public int getIntegerOrDefault(String key) {
+ return (Integer) getValueOrDefault(key, NbtType.TAG_INT).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.
+ */
+ @Override
+ 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.
+ * @throws IllegalArgumentException If the key doesn't exist.
+ */
+ @Override
+ public long getLong(String key) {
+ return (Long) getValueExact(key).getValue();
+ }
+
+ /**
+ * Retrieve the long value of an existing entry, or from a new default entry if it doesn't exist.
+ * @param key - the key of the entry.
+ * @return The value that was retrieved or just created.
+ */
+ @Override
+ public long getLongOrDefault(String key) {
+ return (Long) getValueOrDefault(key, NbtType.TAG_LONG).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.
+ */
+ @Override
+ 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.
+ * @throws IllegalArgumentException If the key doesn't exist.
+ */
+ @Override
+ public float getFloat(String key) {
+ return (Float) getValueExact(key).getValue();
+ }
+
+ /**
+ * Retrieve the float value of an existing entry, or from a new default entry if it doesn't exist.
+ * @param key - the key of the entry.
+ * @return The value that was retrieved or just created.
+ */
+ @Override
+ public float getFloatOrDefault(String key) {
+ return (Float) getValueOrDefault(key, NbtType.TAG_FLOAT).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.
+ */
+ @Override
+ 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.
+ * @throws IllegalArgumentException If the key doesn't exist.
+ */
+ @Override
+ public double getDouble(String key) {
+ return (Double) getValueExact(key).getValue();
+ }
+
+ /**
+ * Retrieve the double value of an existing entry, or from a new default entry if it doesn't exist.
+ * @param key - the key of the entry.
+ * @return The value that was retrieved or just created.
+ */
+ @Override
+ public double getDoubleOrDefault(String key) {
+ return (Double) getValueOrDefault(key, NbtType.TAG_DOUBLE).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.
+ */
+ @Override
+ 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.
+ * @throws IllegalArgumentException If the key doesn't exist.
+ */
+ @Override
+ 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.
+ */
+ @Override
+ 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.
+ * @throws IllegalArgumentException If the key doesn't exist.
+ */
+ @Override
+ 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.
+ */
+ @Override
+ 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.
+ * @throws IllegalArgumentException If the key doesn't exist.
+ */
+ @Override
+ @SuppressWarnings("rawtypes")
+ public NbtCompound getCompound(String key) {
+ return (NbtCompound) ((NbtBase) getValueExact(key));
+ }
+
+ /**
+ * Retrieve a compound (map) value by its key, or create a new compound if it doesn't exist.
+ * @param key - the key of the entry to find or create.
+ * @return The compound value that was retrieved or just created.
+ */
+ @Override
+ public NbtCompound getCompoundOrDefault(String key) {
+ return (NbtCompound) getValueOrDefault(key, NbtType.TAG_COMPOUND);
+ }
+
+ /**
+ * Associate a NBT compound with its name as key.
+ * @param compound - the compound value.
+ * @return This current compound, for chaining.
+ */
+ @Override
+ public NbtCompound put(WrappedCompound 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.
+ * @throws IllegalArgumentException If the key doesn't exist.
+ */
+ @Override
+ @SuppressWarnings({"unchecked", "rawtypes"})
+ public NbtList getList(String key) {
+ return (NbtList) getValueExact(key);
+ }
+
+ /**
+ * Retrieve a NBT list value by its key, or create a new list if it doesn't exist.
+ * @param key - the key of the entry to find or create.
+ * @return The compound value that was retrieved or just created.
+ */
+ @Override
+ @SuppressWarnings("unchecked")
+ public NbtList getListOrDefault(String key) {
+ return (NbtList) getValueOrDefault(key, NbtType.TAG_LIST);
+ }
+
+ /**
+ * Associate a NBT list with the given key.
+ * @param list - the list value.
+ * @return This current compound, for chaining.
+ */
+ @Override
+ public NbtCompound put(NbtList list) {
+ getValue().put(list.getName(), list);
+ return this;
+ }
+
+ @Override
+ public NbtCompound put(String key, NbtBase> entry) {
+ // Don't modify the original NBT
+ NbtBase> clone = entry.deepClone();
+
+ clone.setName(key);
+ return put(clone);
+ }
+
+ /**
+ * 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.
+ */
+ @Override
+ public NbtCompound put(String key, Collection extends NbtBase> list) {
+ return put(WrappedList.fromList(key, list));
+ }
+
+ @Override
+ public void write(DataOutput destination) {
+ NbtBinarySerializer.DEFAULT.serialize(container, destination);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (obj instanceof WrappedCompound) {
+ WrappedCompound other = (WrappedCompound) obj;
+ return container.equals(other.container);
+ }
+ return false;
+ }
+
+ @Override
+ public int hashCode() {
+ return container.hashCode();
+ }
+
+ @Override
+ public Iterator> 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();
+ }
+}
diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/nbt/WrappedElement.java b/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/nbt/WrappedElement.java
new file mode 100644
index 00000000..936f4c2b
--- /dev/null
+++ b/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/nbt/WrappedElement.java
@@ -0,0 +1,243 @@
+/*
+ * ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
+ * Copyright (C) 2012 Kristian S. Stangeland
+ *
+ * This program is free software; you can redistribute it and/or modify it under the terms of the
+ * GNU General Public License as published by the Free Software Foundation; either version 2 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with this program;
+ * if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
+ * 02111-1307 USA
+ */
+
+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.comphenix.protocol.wrappers.nbt.io.NbtBinarySerializer;
+import com.google.common.base.Objects;
+
+/**
+ * Represents a wrapped NBT tag element, composite or not.
+ *
+ * @author Kristian
+ * @param - type of the value field.
+ */
+class WrappedElement implements NbtWrapper {
+ // Structure modifier for the base class
+ private static volatile StructureModifier baseModifier;
+
+ // For retrieving the current type ID
+ private static volatile Method methodGetTypeID;
+
+ // For handling cloning
+ private static volatile 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.
+ */
+ public WrappedElement(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 getBaseModifier() {
+ if (baseModifier == null) {
+ Class> base = MinecraftReflection.getNBTBaseClass();
+
+ // This will be the same for all classes, so we'll share modifier
+ baseModifier = new StructureModifier(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 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 getCurrentBaseModifier() {
+ int index = getType().ordinal();
+ StructureModifier modifier = (StructureModifier) modifiers[index];
+
+ // Double checked locking
+ if (modifier == null) {
+ synchronized (this) {
+ if (modifiers[index] == null) {
+ modifiers[index] = new StructureModifier(handle.getClass(), MinecraftReflection.getNBTBaseClass(), false);
+ }
+ modifier = (StructureModifier) modifiers[index];
+ }
+ }
+
+ return modifier;
+ }
+
+ @Override
+ public boolean accept(NbtVisitor visitor) {
+ return visitor.visit(this);
+ }
+
+ /**
+ * 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;
+ }
+
+ /**
+ * Retrieve the sub element type of the underlying NMS NBT list.
+ * @return The NBT sub type.
+ */
+ public NbtType getSubType() {
+ int subID = getCurrentBaseModifier().withType(byte.class).withTarget(handle).read(0);
+ return NbtType.getTypeFromID(subID);
+ }
+
+ /**
+ * Set the sub element type of the underlying NMS NBT list.
+ * @param type - the new sub element type.
+ */
+ public void setSubType(NbtType type) {
+ byte subID = (byte) type.getRawID();
+ getCurrentBaseModifier().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) {
+ // No need to cache this object
+ NbtBinarySerializer.DEFAULT.serialize(this, destination);
+ }
+
+ @Override
+ public NbtBase deepClone() {
+ 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();
+ }
+}
diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/nbt/WrappedList.java b/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/nbt/WrappedList.java
new file mode 100644
index 00000000..7e281b04
--- /dev/null
+++ b/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/nbt/WrappedList.java
@@ -0,0 +1,407 @@
+/*
+ * ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
+ * Copyright (C) 2012 Kristian S. Stangeland
+ *
+ * This program is free software; you can redistribute it and/or modify it under the terms of the
+ * GNU General Public License as published by the Free Software Foundation; either version 2 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with this program;
+ * if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
+ * 02111-1307 USA
+ */
+
+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.comphenix.protocol.wrappers.nbt.io.NbtBinarySerializer;
+import com.google.common.base.Function;
+import com.google.common.base.Joiner;
+import com.google.common.collect.Iterables;
+
+/**
+ * Represents a concrete implementation of an NBT list that wraps an underlying NMS list.
+ * @author Kristian
+ *
+ * @param - the type of the value in each NBT sub element.
+ */
+class WrappedList implements NbtWrapper>>, Iterable, NbtList {
+ // A list container
+ private WrappedElement> container;
+
+ // Saved wrapper list
+ private ConvertedList> savedList;
+
+ // Element type
+ private NbtType elementType = NbtType.TAG_END;
+
+ /**
+ * Construct a new empty NBT list.
+ * @param name - name of this list.
+ * @return The new empty NBT list.
+ */
+ @SuppressWarnings("unchecked")
+ public static NbtList fromName(String name) {
+ return (NbtList) NbtFactory.>>ofWrapper(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.
+ */
+ @SuppressWarnings({"unchecked", "rawtypes"})
+ public static NbtList fromArray(String name, T... elements) {
+ NbtList result = fromName(name);
+
+ for (T element : elements) {
+ if (element == null)
+ throw new IllegalArgumentException("An NBT list cannot contain a null element!");
+
+ if (element instanceof NbtBase)
+ result.add((NbtBase) element);
+ else
+ result.add(NbtFactory.ofWrapper(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.
+ */
+ @SuppressWarnings({"unchecked", "rawtypes"})
+ public static NbtList fromList(String name, Collection extends T> elements) {
+ NbtList result = fromName(name);
+
+ for (T element : elements) {
+ if (element == null)
+ throw new IllegalArgumentException("An NBT list cannot contain a null element!");
+
+ if (element instanceof NbtBase)
+ result.add((NbtBase) element);
+ else
+ result.add(NbtFactory.ofWrapper(element.getClass(), EMPTY_NAME, element));
+ }
+ return result;
+ }
+
+ /**
+ * Construct a list from an NMS instance.
+ * @param handle - NMS instance.
+ */
+ public WrappedList(Object handle) {
+ this.container = new WrappedElement>(handle);
+ this.elementType = container.getSubType();
+ }
+
+ @Override
+ public boolean accept(NbtVisitor visitor) {
+ // Enter this node?
+ if (visitor.visitEnter(this)) {
+ for (NbtBase node : getValue()) {
+ if (!node.accept(visitor))
+ break;
+ }
+ }
+
+ return visitor.visitLeave(this);
+ }
+
+ @Override
+ public Object getHandle() {
+ return container.getHandle();
+ }
+
+ @Override
+ public NbtType getType() {
+ return NbtType.TAG_LIST;
+ }
+
+ @Override
+ public NbtType getElementType() {
+ return elementType;
+ }
+
+ @Override
+ public void setElementType(NbtType type) {
+ this.elementType = type;
+ container.setSubType(type);
+ }
+
+ @Override
+ public String getName() {
+ return container.getName();
+ }
+
+ @Override
+ public void setName(String name) {
+ container.setName(name);
+ }
+
+ @Override
+ public List> getValue() {
+ if (savedList == null) {
+ savedList = new ConvertedList>(container.getValue()) {
+ // Check and see if the element is valid
+ private void verifyElement(NbtBase 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.");
+
+ // Check element type
+ if (getElementType() != NbtType.TAG_END) {
+ if (!element.getType().equals(getElementType())) {
+ throw new IllegalArgumentException(
+ "Cannot add " + element + " of " + element.getType() + " to a list of type " + getElementType());
+ }
+ } else {
+ container.setSubType(element.getType());
+ }
+ }
+
+ @Override
+ public boolean add(NbtBase e) {
+ verifyElement(e);
+ return super.add(e);
+ }
+
+ @Override
+ public void add(int index, NbtBase element) {
+ verifyElement(element);
+ super.add(index, element);
+ }
+
+ @Override
+ public boolean addAll(Collection extends NbtBase> c) {
+ boolean result = false;
+
+ for (NbtBase element : c) {
+ add(element);
+ result = true;
+ }
+ return result;
+ }
+
+ @Override
+ protected Object toInner(NbtBase outer) {
+ if (outer == null)
+ return null;
+ return NbtFactory.fromBase(outer).getHandle();
+ }
+
+ @Override
+ protected NbtBase toOuter(Object inner) {
+ if (inner == null)
+ return null;
+ return NbtFactory.fromNMS(inner);
+ }
+
+ @Override
+ public String toString() {
+ return WrappedList.this.toString();
+ }
+ };
+ }
+ return savedList;
+ }
+
+ @Override
+ @SuppressWarnings({"unchecked", "rawtypes"})
+ public NbtBase>> deepClone() {
+ return (NbtBase) container.deepClone();
+ }
+
+ @Override
+ @SuppressWarnings("unchecked")
+ public void addClosest(Object value) {
+ if (getElementType() == NbtType.TAG_END)
+ throw new IllegalStateException("This list has not been typed yet.");
+
+ if (value instanceof Number) {
+ Number number = (Number) value;
+
+ // Convert the number
+ switch (getElementType()) {
+ case TAG_BYTE: add(number.byteValue()); break;
+ case TAG_SHORT: add(number.shortValue()); break;
+ case TAG_INT: add(number.intValue()); break;
+ case TAG_LONG: add(number.longValue()); break;
+ case TAG_FLOAT: add(number.floatValue()); break;
+ case TAG_DOUBLE: add(number.doubleValue()); break;
+ case TAG_STRING: add(number.toString()); break;
+ default:
+ throw new IllegalArgumentException("Cannot convert " + value + " to " + getType());
+ }
+
+ } else if (value instanceof NbtBase) {
+ // Add the element itself
+ add((NbtBase) value);
+
+ } else {
+ // Just add it
+ add((NbtBase) NbtFactory.ofWrapper(getElementType(), EMPTY_NAME, value));
+ }
+ }
+
+ @Override
+ public void add(NbtBase element) {
+ getValue().add(element);
+ }
+
+ @Override
+ @SuppressWarnings("unchecked")
+ public void add(String value) {
+ add((NbtBase) NbtFactory.of(EMPTY_NAME, value));
+ }
+
+ @Override
+ @SuppressWarnings("unchecked")
+ public void add(byte value) {
+ add((NbtBase) NbtFactory.of(EMPTY_NAME, value));
+ }
+
+ @Override
+ @SuppressWarnings("unchecked")
+ public void add(short value) {
+ add((NbtBase) NbtFactory.of(EMPTY_NAME, value));
+ }
+
+ @Override
+ @SuppressWarnings("unchecked")
+ public void add(int value) {
+ add((NbtBase) NbtFactory.of(EMPTY_NAME, value));
+ }
+
+ @Override
+ @SuppressWarnings("unchecked")
+ public void add(long value) {
+ add((NbtBase) NbtFactory.of(EMPTY_NAME, value));
+ }
+
+ @Override
+ @SuppressWarnings("unchecked")
+ public void add(double value) {
+ add((NbtBase) NbtFactory.of(EMPTY_NAME, value));
+ }
+
+ @Override
+ @SuppressWarnings("unchecked")
+ public void add(byte[] value) {
+ add((NbtBase) NbtFactory.of(EMPTY_NAME, value));
+ }
+
+ @Override
+ @SuppressWarnings("unchecked")
+ public void add(int[] value) {
+ add((NbtBase) NbtFactory.of(EMPTY_NAME, value));
+ }
+
+ @Override
+ public int size() {
+ return getValue().size();
+ }
+
+ @Override
+ 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.
+ */
+ @Override
+ public Collection> asCollection() {
+ return getValue();
+ }
+
+ @Override
+ public void setValue(List> newValue) {
+ NbtBase lastElement = null;
+ List list = container.getValue();
+ list.clear();
+
+ // Set each underlying element
+ for (NbtBase 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) {
+ NbtBinarySerializer.DEFAULT.serialize(container, destination);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (obj instanceof WrappedList) {
+ @SuppressWarnings("unchecked")
+ WrappedList other = (WrappedList) obj;
+ return container.equals(other.container);
+ }
+ return false;
+ }
+
+ @Override
+ public int hashCode() {
+ return container.hashCode();
+ }
+
+ @Override
+ public Iterator iterator() {
+ return Iterables.transform(getValue(), new Function