Add support for reading and writing NBT tags in packets.
This wraps the internal NBT read/write system in Minecraft.
Dieser Commit ist enthalten in:
Ursprung
73e71ff954
Commit
671654aaaf
@ -56,6 +56,7 @@ import com.comphenix.protocol.wrappers.BukkitConverters;
|
||||
import com.comphenix.protocol.wrappers.ChunkPosition;
|
||||
import com.comphenix.protocol.wrappers.WrappedDataWatcher;
|
||||
import com.comphenix.protocol.wrappers.WrappedWatchableObject;
|
||||
import com.comphenix.protocol.wrappers.nbt.NbtWrapper;
|
||||
import com.google.common.base.Function;
|
||||
import com.google.common.collect.Maps;
|
||||
|
||||
@ -364,6 +365,17 @@ public class PacketContainer implements Serializable {
|
||||
ChunkPosition.getConverter());
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a read/write structure for NBT classes.
|
||||
* @return A modifier for NBT classes.
|
||||
*/
|
||||
public StructureModifier<NbtWrapper<?>> getNbtModifier() {
|
||||
// Allow access to the NBT class in packet 130
|
||||
return structureModifier.withType(
|
||||
MinecraftReflection.getNBTBaseClass(),
|
||||
BukkitConverters.getNbtConverter());
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a read/write structure for collections of chunk positions.
|
||||
* <p>
|
||||
|
@ -24,8 +24,6 @@ import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.RejectedExecutionException;
|
||||
import java.util.concurrent.ThreadFactory;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
|
@ -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.
|
||||
|
@ -34,6 +34,8 @@ import com.comphenix.protocol.reflect.EquivalentConverter;
|
||||
import com.comphenix.protocol.reflect.FieldAccessException;
|
||||
import com.comphenix.protocol.reflect.instances.DefaultInstances;
|
||||
import com.comphenix.protocol.utility.MinecraftReflection;
|
||||
import com.comphenix.protocol.wrappers.nbt.NbtFactory;
|
||||
import com.comphenix.protocol.wrappers.nbt.NbtWrapper;
|
||||
|
||||
/**
|
||||
* Contains several useful equivalent converters for normal Bukkit types.
|
||||
@ -208,6 +210,32 @@ public class BukkitConverters {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve an equivalent converter for net.minecraft.server NBT classes and their wrappers.
|
||||
* @return An equivalent converter for NBT.
|
||||
*/
|
||||
public static EquivalentConverter<NbtWrapper<?>> getNbtConverter() {
|
||||
return getIgnoreNull(new EquivalentConverter<NbtWrapper<?>>() {
|
||||
@Override
|
||||
public Object getGeneric(Class<?> genericType, NbtWrapper<?> specific) {
|
||||
return specific.getHandle();
|
||||
}
|
||||
|
||||
@Override
|
||||
public NbtWrapper<?> getSpecific(Object generic) {
|
||||
return NbtFactory.fromNMS(generic);
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public Class<NbtWrapper<?>> getSpecificType() {
|
||||
// Damn you Java AGAIN
|
||||
Class<?> dummy = NbtWrapper.class;
|
||||
return (Class<NbtWrapper<?>>) dummy;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a converter for NMS entities and Bukkit entities.
|
||||
* @param world - the current world.
|
||||
|
@ -0,0 +1,34 @@
|
||||
package com.comphenix.protocol.wrappers.nbt;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import com.google.common.base.Function;
|
||||
|
||||
abstract class AbstractConverted<VInner, VOuter> {
|
||||
/**
|
||||
* Convert a value from the inner map to the outer visible map.
|
||||
* @param inner - the inner value.
|
||||
* @return The outer value.
|
||||
*/
|
||||
protected abstract VOuter toOuter(VInner inner);
|
||||
|
||||
/**
|
||||
* Convert a value from the outer map to the internal inner map.
|
||||
* @param outer - the outer value.
|
||||
* @return The inner value.
|
||||
*/
|
||||
protected abstract VInner toInner(VOuter outer);
|
||||
|
||||
/**
|
||||
* Retrieve a function delegate that converts inner objects to outer objects.
|
||||
* @return A function delegate.
|
||||
*/
|
||||
protected Function<VInner, VOuter> getOuterConverter() {
|
||||
return new Function<VInner, VOuter>() {
|
||||
@Override
|
||||
public VOuter apply(@Nullable VInner param) {
|
||||
return toOuter(param);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
@ -0,0 +1,119 @@
|
||||
package com.comphenix.protocol.wrappers.nbt;
|
||||
|
||||
import java.lang.reflect.Array;
|
||||
import java.util.Collection;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
import com.google.common.collect.Iterators;
|
||||
import com.google.common.collect.Lists;
|
||||
|
||||
abstract class ConvertedCollection<VInner, VOuter> extends AbstractConverted<VInner, VOuter> implements Collection<VOuter> {
|
||||
// Inner collection
|
||||
private Collection<VInner> inner;
|
||||
|
||||
public ConvertedCollection(Collection<VInner> inner) {
|
||||
this.inner = inner;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean add(VOuter e) {
|
||||
return inner.add(toInner(e));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean addAll(Collection<? extends VOuter> c) {
|
||||
boolean modified = false;
|
||||
|
||||
for (VOuter outer : c)
|
||||
modified |= add(outer);
|
||||
return modified;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clear() {
|
||||
inner.clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public boolean contains(Object o) {
|
||||
return inner.contains(toInner((VOuter) o));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean containsAll(Collection<?> c) {
|
||||
for (Object outer : c) {
|
||||
if (!contains(outer))
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEmpty() {
|
||||
return inner.isEmpty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<VOuter> iterator() {
|
||||
return Iterators.transform(inner.iterator(), getOuterConverter());
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public boolean remove(Object o) {
|
||||
return inner.remove(toInner((VOuter) o));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean removeAll(Collection<?> c) {
|
||||
boolean modified = false;
|
||||
|
||||
for (Object outer : c)
|
||||
modified |= remove(outer);
|
||||
return modified;
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public boolean retainAll(Collection<?> c) {
|
||||
List<VInner> innerCopy = Lists.newArrayList();
|
||||
|
||||
// Convert all the elements
|
||||
for (Object outer : c)
|
||||
innerCopy.add(toInner((VOuter) outer));
|
||||
return inner.retainAll(innerCopy);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int size() {
|
||||
return inner.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public Object[] toArray() {
|
||||
Object[] array = inner.toArray();
|
||||
|
||||
for (int i = 0; i < array.length; i++)
|
||||
array[i] = toOuter((VInner) array[i]);
|
||||
return array;
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T> T[] toArray(T[] a) {
|
||||
T[] array = a;
|
||||
int index = 0;
|
||||
|
||||
if (array.length < size()) {
|
||||
array = (T[]) Array.newInstance(a.getClass().getComponentType(), size());
|
||||
}
|
||||
|
||||
// Build the output array
|
||||
for (VInner innerValue : inner)
|
||||
array[index++] = (T) toOuter(innerValue);
|
||||
return array;
|
||||
}
|
||||
}
|
@ -0,0 +1,138 @@
|
||||
package com.comphenix.protocol.wrappers.nbt;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.ListIterator;
|
||||
|
||||
abstract class ConvertedList<VInner, VOuter> extends ConvertedCollection<VInner, VOuter> implements List<VOuter> {
|
||||
private List<VInner> inner;
|
||||
|
||||
public ConvertedList(List<VInner> inner) {
|
||||
super(inner);
|
||||
this.inner = inner;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void add(int index, VOuter element) {
|
||||
inner.add(index, toInner(element));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean addAll(int index, Collection<? extends VOuter> c) {
|
||||
return inner.addAll(index, getInnerCollection(c));
|
||||
}
|
||||
|
||||
@Override
|
||||
public VOuter get(int index) {
|
||||
return toOuter(inner.get(index));
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public int indexOf(Object o) {
|
||||
return inner.indexOf(toInner((VOuter) o));
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public int lastIndexOf(Object o) {
|
||||
return inner.lastIndexOf(toInner((VOuter) o));
|
||||
}
|
||||
|
||||
@Override
|
||||
public ListIterator<VOuter> listIterator() {
|
||||
return listIterator(0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ListIterator<VOuter> listIterator(int index) {
|
||||
final ListIterator<VInner> innerIterator = inner.listIterator(index);
|
||||
|
||||
return new ListIterator<VOuter>() {
|
||||
@Override
|
||||
public void add(VOuter e) {
|
||||
innerIterator.add(toInner(e));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasNext() {
|
||||
return innerIterator.hasNext();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasPrevious() {
|
||||
return innerIterator.hasPrevious();
|
||||
}
|
||||
|
||||
@Override
|
||||
public VOuter next() {
|
||||
return toOuter(innerIterator.next());
|
||||
}
|
||||
|
||||
@Override
|
||||
public int nextIndex() {
|
||||
return innerIterator.nextIndex();
|
||||
}
|
||||
|
||||
@Override
|
||||
public VOuter previous() {
|
||||
return toOuter(innerIterator.previous());
|
||||
}
|
||||
|
||||
@Override
|
||||
public int previousIndex() {
|
||||
return innerIterator.previousIndex();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void remove() {
|
||||
innerIterator.remove();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void set(VOuter e) {
|
||||
innerIterator.set(toInner(e));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public VOuter remove(int index) {
|
||||
return toOuter(inner.remove(index));
|
||||
}
|
||||
|
||||
@Override
|
||||
public VOuter set(int index, VOuter element) {
|
||||
return toOuter(inner.set(index, toInner(element)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<VOuter> subList(int fromIndex, int toIndex) {
|
||||
return new ConvertedList<VInner, VOuter>(inner.subList(fromIndex, toIndex)) {
|
||||
@Override
|
||||
protected VInner toInner(VOuter outer) {
|
||||
return ConvertedList.this.toInner(outer);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected VOuter toOuter(VInner inner) {
|
||||
return ConvertedList.this.toOuter(inner);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@SuppressWarnings({"rawtypes", "unchecked"})
|
||||
private ConvertedCollection<VOuter, VInner> getInnerCollection(Collection c) {
|
||||
return new ConvertedCollection<VOuter, VInner>(c) {
|
||||
@Override
|
||||
protected VOuter toInner(VInner outer) {
|
||||
return ConvertedList.this.toOuter(outer);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected VInner toOuter(VOuter inner) {
|
||||
return ConvertedList.this.toInner(inner);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
@ -0,0 +1,139 @@
|
||||
package com.comphenix.protocol.wrappers.nbt;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
abstract class ConvertedMap<Key, VInner, VOuter> extends AbstractConverted<VInner, VOuter> implements Map<Key, VOuter> {
|
||||
// Inner map
|
||||
private Map<Key, VInner> inner;
|
||||
|
||||
public ConvertedMap(Map<Key, VInner> inner) {
|
||||
if (inner == null)
|
||||
throw new IllegalArgumentException("Inner map cannot be NULL.");
|
||||
this.inner = inner;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clear() {
|
||||
inner.clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean containsKey(Object key) {
|
||||
return inner.containsKey(key);
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public boolean containsValue(Object value) {
|
||||
return inner.containsValue(toInner((VOuter) value));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<Entry<Key, VOuter>> entrySet() {
|
||||
return new ConvertedSet<Entry<Key,VInner>, Entry<Key,VOuter>>(inner.entrySet()) {
|
||||
@Override
|
||||
protected Entry<Key, VInner> toInner(final Entry<Key, VOuter> outer) {
|
||||
return new Entry<Key, VInner>() {
|
||||
@Override
|
||||
public Key getKey() {
|
||||
return outer.getKey();
|
||||
}
|
||||
|
||||
@Override
|
||||
public VInner getValue() {
|
||||
return ConvertedMap.this.toInner(outer.getValue());
|
||||
}
|
||||
|
||||
@Override
|
||||
public VInner setValue(VInner value) {
|
||||
return ConvertedMap.this.toInner(outer.setValue(ConvertedMap.this.toOuter(value)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("\"%s\": %s", getKey(), getValue());
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Entry<Key, VOuter> toOuter(final Entry<Key, VInner> inner) {
|
||||
return new Entry<Key, VOuter>() {
|
||||
@Override
|
||||
public Key getKey() {
|
||||
return inner.getKey();
|
||||
}
|
||||
|
||||
@Override
|
||||
public VOuter getValue() {
|
||||
return ConvertedMap.this.toOuter(inner.getValue());
|
||||
}
|
||||
|
||||
@Override
|
||||
public VOuter setValue(VOuter value) {
|
||||
return ConvertedMap.this.toOuter(inner.setValue(ConvertedMap.this.toInner(value)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("\"%s\": %s", getKey(), getValue());
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public VOuter get(Object key) {
|
||||
return toOuter(inner.get(key));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEmpty() {
|
||||
return inner.isEmpty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<Key> keySet() {
|
||||
return inner.keySet();
|
||||
}
|
||||
|
||||
@Override
|
||||
public VOuter put(Key key, VOuter value) {
|
||||
return toOuter(inner.put(key, toInner(value)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void putAll(Map<? extends Key, ? extends VOuter> m) {
|
||||
for (Entry<? extends Key, ? extends VOuter> entry : m.entrySet()) {
|
||||
put(entry.getKey(), entry.getValue());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public VOuter remove(Object key) {
|
||||
return toOuter(inner.remove(key));
|
||||
}
|
||||
|
||||
@Override
|
||||
public int size() {
|
||||
return inner.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<VOuter> values() {
|
||||
return new ConvertedCollection<VInner, VOuter>(inner.values()) {
|
||||
@Override
|
||||
protected VOuter toOuter(VInner inner) {
|
||||
return ConvertedMap.this.toOuter(inner);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected VInner toInner(VOuter outer) {
|
||||
return ConvertedMap.this.toInner(outer);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
@ -0,0 +1,10 @@
|
||||
package com.comphenix.protocol.wrappers.nbt;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Set;
|
||||
|
||||
abstract class ConvertedSet<VInner, VOuter> extends ConvertedCollection<VInner, VOuter> implements Set<VOuter> {
|
||||
public ConvertedSet(Collection<VInner> inner) {
|
||||
super(inner);
|
||||
}
|
||||
}
|
@ -0,0 +1,52 @@
|
||||
package com.comphenix.protocol.wrappers.nbt;
|
||||
|
||||
import com.comphenix.protocol.wrappers.nbt.NbtBase;
|
||||
import com.comphenix.protocol.wrappers.nbt.NbtType;
|
||||
|
||||
/**
|
||||
* Represents a generic container for an NBT element.
|
||||
* @author Kristian
|
||||
*
|
||||
* @param <TType> - type of the value that is stored.
|
||||
*/
|
||||
public interface NbtBase<TType> {
|
||||
/**
|
||||
* Retrieve the type of this NBT element.
|
||||
* @return The type of this NBT element.
|
||||
*/
|
||||
public abstract NbtType getType();
|
||||
|
||||
/**
|
||||
* Retrieve the name of this NBT tag.
|
||||
* <p>
|
||||
* This will be an empty string if the NBT tag is stored in a list.
|
||||
* @return Name of the tag.
|
||||
*/
|
||||
public abstract String getName();
|
||||
|
||||
/**
|
||||
* Set the name of this NBT tag.
|
||||
* <p>
|
||||
* This will be ignored if the NBT tag is stored in a list.
|
||||
* @param name - name of the tag.
|
||||
*/
|
||||
public abstract void setName(String name);
|
||||
|
||||
/**
|
||||
* Retrieve the value of this NBT tag.
|
||||
* @return Value of this tag.
|
||||
*/
|
||||
public abstract TType getValue();
|
||||
|
||||
/**
|
||||
* Set the value of this NBT tag.
|
||||
* @param newValue - the new value of this tag.
|
||||
*/
|
||||
public abstract void setValue(TType newValue);
|
||||
|
||||
/**
|
||||
* Clone the current NBT tag.
|
||||
* @return The cloned tag.
|
||||
*/
|
||||
public abstract NbtBase<TType> clone();
|
||||
}
|
@ -0,0 +1,440 @@
|
||||
package com.comphenix.protocol.wrappers.nbt;
|
||||
|
||||
import java.io.DataOutput;
|
||||
import java.util.Collection;
|
||||
import java.util.Iterator;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Represents a mapping of arbitrary NBT elements and their unique names.
|
||||
* <p>
|
||||
* Use {@link NbtFactory} to load or create an instance.
|
||||
*
|
||||
* @author Kristian
|
||||
*/
|
||||
public class NbtCompound implements NbtWrapper<Map<String, NbtBase<?>>>, Iterable<NbtBase<?>> {
|
||||
// A list container
|
||||
private NbtElement<Map<String, Object>> container;
|
||||
|
||||
// Saved wrapper map
|
||||
private ConvertedMap<String, Object, NbtBase<?>> savedMap;
|
||||
|
||||
/**
|
||||
* Construct a new NBT compound wrapper.
|
||||
* @param name - the name of the wrapper.
|
||||
* @return The wrapped NBT compound.
|
||||
*/
|
||||
public static NbtCompound fromName(String name) {
|
||||
// Simplify things for the caller
|
||||
return (NbtCompound) NbtFactory.<Map<String, NbtBase<?>>>ofType(NbtType.TAG_COMPOUND, name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a new NBT compound wrapper initialized with a given list of NBT values.
|
||||
* @param name - the name of the compound wrapper.
|
||||
* @param list - the list of elements to add.
|
||||
* @return The new wrapped NBT compound.
|
||||
*/
|
||||
public static <T> NbtCompound fromList(String name, Collection<? extends NbtBase<T>> list) {
|
||||
NbtCompound copy = new NbtCompound(name);
|
||||
|
||||
for (NbtBase<T> base : list)
|
||||
copy.getValue().put(base.getName(), base);
|
||||
return copy;
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a wrapped compound from a given NMS handle.
|
||||
* @param handle - the NMS handle.
|
||||
*/
|
||||
NbtCompound(Object handle) {
|
||||
this.container = new NbtElement<Map<String,Object>>(handle);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getHandle() {
|
||||
return container.getHandle();
|
||||
}
|
||||
|
||||
@Override
|
||||
public NbtType getType() {
|
||||
return NbtType.TAG_COMPOUND;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return container.getName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setName(String name) {
|
||||
container.setName(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a Set view of the keys of each entry in this compound.
|
||||
* @return The keys of each entry.
|
||||
*/
|
||||
public Set<String> getKeys() {
|
||||
return getValue().keySet();
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a Collection view of the entries in this compound.
|
||||
* @return A view of each NBT tag in this compound.
|
||||
*/
|
||||
public Collection<NbtBase<?>> asCollection(){
|
||||
return getValue().values();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, NbtBase<?>> getValue() {
|
||||
// Return a wrapper map
|
||||
if (savedMap == null) {
|
||||
savedMap = new ConvertedMap<String, Object, NbtBase<?>>(container.getValue()) {
|
||||
@Override
|
||||
protected Object toInner(NbtBase<?> outer) {
|
||||
if (outer == null)
|
||||
return null;
|
||||
return NbtFactory.fromBase(outer).getHandle();
|
||||
}
|
||||
|
||||
protected NbtBase<?> toOuter(Object inner) {
|
||||
if (inner == null)
|
||||
return null;
|
||||
return NbtFactory.fromNMS(inner);
|
||||
};
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return NbtCompound.this.toString();
|
||||
}
|
||||
};
|
||||
}
|
||||
return savedMap;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setValue(Map<String, NbtBase<?>> newValue) {
|
||||
// Write all the entries
|
||||
for (Map.Entry<String, NbtBase<?>> entry : newValue.entrySet()) {
|
||||
put(entry.getValue());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the value of a given entry.
|
||||
* @param key - key of the entry to retrieve.
|
||||
* @return The value of this entry.
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T> NbtBase<T> getValue(String key) {
|
||||
return (NbtBase<T>) getValue().get(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a value, or throw an exception.
|
||||
* @param key - the key to retrieve.
|
||||
* @return The value of the entry.
|
||||
*/
|
||||
private <T> NbtBase<T> getValueExact(String key) {
|
||||
NbtBase<T> value = getValue(key);
|
||||
|
||||
// Only return a legal key
|
||||
if (value != null)
|
||||
return value;
|
||||
else
|
||||
throw new IllegalArgumentException("Cannot find key " + key);
|
||||
}
|
||||
|
||||
@SuppressWarnings({"unchecked", "rawtypes"})
|
||||
public NbtBase<Map<String, NbtBase<?>>> clone() {
|
||||
return (NbtBase) container.clone();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a entry based on its name.
|
||||
* @param entry - entry with a name and value.
|
||||
* @return This compound, for chaining.
|
||||
*/
|
||||
public <T> NbtCompound put(NbtBase<T> entry) {
|
||||
getValue().put(entry.getName(), entry);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the string value of an entry identified by a given key.
|
||||
* @param key - the key of the entry.
|
||||
* @return The string value of the entry.
|
||||
*/
|
||||
public String getString(String key) {
|
||||
return (String) getValueExact(key).getValue();
|
||||
}
|
||||
|
||||
/**
|
||||
* Associate a NBT string value with the given key.
|
||||
* @param key - the key and NBT name.
|
||||
* @param value - the value.
|
||||
* @return This current compound, for chaining.
|
||||
*/
|
||||
public NbtCompound put(String key, String value) {
|
||||
getValue().put(key, NbtFactory.of(key, value));
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the byte value of an entry identified by a given key.
|
||||
* @param key - the key of the entry.
|
||||
* @return The byte value of the entry.
|
||||
*/
|
||||
public Byte getByte(String key) {
|
||||
return (Byte) getValueExact(key).getValue();
|
||||
}
|
||||
|
||||
/**
|
||||
* Associate a NBT byte value with the given key.
|
||||
* @param key - the key and NBT name.
|
||||
* @param value - the value.
|
||||
* @return This current compound, for chaining.
|
||||
*/
|
||||
public NbtCompound put(String key, byte value) {
|
||||
getValue().put(key, NbtFactory.of(key, value));
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the short value of an entry identified by a given key.
|
||||
* @param key - the key of the entry.
|
||||
* @return The short value of the entry.
|
||||
*/
|
||||
public Short getShort(String key) {
|
||||
return (Short) getValueExact(key).getValue();
|
||||
}
|
||||
|
||||
/**
|
||||
* Associate a NBT short value with the given key.
|
||||
* @param key - the key and NBT name.
|
||||
* @param value - the value.
|
||||
* @return This current compound, for chaining.
|
||||
*/
|
||||
public NbtCompound put(String key, short value) {
|
||||
getValue().put(key, NbtFactory.of(key, value));
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the integer value of an entry identified by a given key.
|
||||
* @param key - the key of the entry.
|
||||
* @return The integer value of the entry.
|
||||
*/
|
||||
public Integer getInteger(String key) {
|
||||
return (Integer) getValueExact(key).getValue();
|
||||
}
|
||||
|
||||
/**
|
||||
* Associate a NBT integer value with the given key.
|
||||
* @param key - the key and NBT name.
|
||||
* @param value - the value.
|
||||
* @return This current compound, for chaining.
|
||||
*/
|
||||
public NbtCompound put(String key, int value) {
|
||||
getValue().put(key, NbtFactory.of(key, value));
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the long value of an entry identified by a given key.
|
||||
* @param key - the key of the entry.
|
||||
* @return The long value of the entry.
|
||||
*/
|
||||
public Long getLong(String key) {
|
||||
return (Long) getValueExact(key).getValue();
|
||||
}
|
||||
|
||||
/**
|
||||
* Associate a NBT long value with the given key.
|
||||
* @param key - the key and NBT name.
|
||||
* @param value - the value.
|
||||
* @return This current compound, for chaining.
|
||||
*/
|
||||
public NbtCompound put(String key, long value) {
|
||||
getValue().put(key, NbtFactory.of(key, value));
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the float value of an entry identified by a given key.
|
||||
* @param key - the key of the entry.
|
||||
* @return The float value of the entry.
|
||||
*/
|
||||
public Float getFloat(String key) {
|
||||
return (Float) getValueExact(key).getValue();
|
||||
}
|
||||
|
||||
/**
|
||||
* Associate a NBT float value with the given key.
|
||||
* @param key - the key and NBT name.
|
||||
* @param value - the value.
|
||||
* @return This current compound, for chaining.
|
||||
*/
|
||||
public NbtCompound put(String key, float value) {
|
||||
getValue().put(key, NbtFactory.of(key, value));
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the double value of an entry identified by a given key.
|
||||
* @param key - the key of the entry.
|
||||
* @return The double value of the entry.
|
||||
*/
|
||||
public Double getDouble(String key) {
|
||||
return (Double) getValueExact(key).getValue();
|
||||
}
|
||||
|
||||
/**
|
||||
* Associate a NBT double value with the given key.
|
||||
* @param key - the key and NBT name.
|
||||
* @param value - the value.
|
||||
* @return This current compound, for chaining.
|
||||
*/
|
||||
public NbtCompound put(String key, double value) {
|
||||
getValue().put(key, NbtFactory.of(key, value));
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the byte array value of an entry identified by a given key.
|
||||
* @param key - the key of the entry.
|
||||
* @return The byte array value of the entry.
|
||||
*/
|
||||
public byte[] getByteArray(String key) {
|
||||
return (byte[]) getValueExact(key).getValue();
|
||||
}
|
||||
|
||||
/**
|
||||
* Associate a NBT byte array value with the given key.
|
||||
* @param key - the key and NBT name.
|
||||
* @param value - the value.
|
||||
* @return This current compound, for chaining.
|
||||
*/
|
||||
public NbtCompound put(String key, byte[] value) {
|
||||
getValue().put(key, NbtFactory.of(key, value));
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the integer array value of an entry identified by a given key.
|
||||
* @param key - the key of the entry.
|
||||
* @return The integer array value of the entry.
|
||||
*/
|
||||
public int[] getIntegerArray(String key) {
|
||||
return (int[]) getValueExact(key).getValue();
|
||||
}
|
||||
|
||||
/**
|
||||
* Associate a NBT integer array value with the given key.
|
||||
* @param key - the key and NBT name.
|
||||
* @param value - the value.
|
||||
* @return This current compound, for chaining.
|
||||
*/
|
||||
public NbtCompound put(String key, int[] value) {
|
||||
getValue().put(key, NbtFactory.of(key, value));
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the compound (map) value of an entry identified by a given key.
|
||||
* @param key - the key of the entry.
|
||||
* @return The compound value of the entry.
|
||||
*/
|
||||
@SuppressWarnings("rawtypes")
|
||||
public NbtCompound getCompound(String key) {
|
||||
return (NbtCompound) ((NbtBase) getValueExact(key));
|
||||
}
|
||||
|
||||
/**
|
||||
* Associate a NBT compound with its name as key.
|
||||
* @param compound - the compound value.
|
||||
* @return This current compound, for chaining.
|
||||
*/
|
||||
public NbtCompound put(NbtCompound compound) {
|
||||
getValue().put(compound.getName(), compound);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the NBT list value of an entry identified by a given key.
|
||||
* @param key - the key of the entry.
|
||||
* @return The NBT list value of the entry.
|
||||
*/
|
||||
@SuppressWarnings({"unchecked", "rawtypes"})
|
||||
public <T> NbtList<T> getList(String key) {
|
||||
return (NbtList) getValueExact(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Associate a NBT list with the given key.
|
||||
* @param list - the list value.
|
||||
* @return This current compound, for chaining.
|
||||
*/
|
||||
public <T> NbtCompound put(NbtList<T> list) {
|
||||
getValue().put(list.getName(), list);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Associate a new NBT list with the given key.
|
||||
* @param key - the key and name of the new NBT list.
|
||||
* @param list - the list of NBT elements.
|
||||
* @return This current compound, for chaining.
|
||||
*/
|
||||
public <T> NbtCompound put(String key, Collection<? extends NbtBase<T>> list) {
|
||||
return put(NbtList.fromList(key, list));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(DataOutput destination) {
|
||||
NbtFactory.toStream(container, destination);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (obj instanceof NbtCompound) {
|
||||
NbtCompound other = (NbtCompound) obj;
|
||||
return container.equals(other.container);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return container.hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<NbtBase<?>> iterator() {
|
||||
return getValue().values().iterator();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder builder = new StringBuilder();
|
||||
|
||||
builder.append("{");
|
||||
builder.append("\"name\": \"" + getName() + "\"");
|
||||
|
||||
for (NbtBase<?> element : this) {
|
||||
builder.append(", ");
|
||||
|
||||
// Wrap in quotation marks
|
||||
if (element.getType() == NbtType.TAG_STRING)
|
||||
builder.append("\"" + element.getName() + "\": \"" + element.getValue() + "\"");
|
||||
else
|
||||
builder.append("\"" + element.getName() + "\": " + element.getValue());
|
||||
}
|
||||
|
||||
builder.append("}");
|
||||
return builder.toString();
|
||||
}
|
||||
}
|
@ -0,0 +1,213 @@
|
||||
package com.comphenix.protocol.wrappers.nbt;
|
||||
|
||||
import java.io.DataOutput;
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
import com.comphenix.protocol.reflect.FieldAccessException;
|
||||
import com.comphenix.protocol.reflect.FuzzyReflection;
|
||||
import com.comphenix.protocol.reflect.StructureModifier;
|
||||
import com.comphenix.protocol.utility.MinecraftReflection;
|
||||
import com.google.common.base.Objects;
|
||||
|
||||
/**
|
||||
* Represents an arbitrary NBT tag element, composite or not.
|
||||
* <p>
|
||||
* Use {@link NbtFactory} to load or create an instance.
|
||||
* @author Kristian
|
||||
*
|
||||
* @param <TType> - type of the value field.
|
||||
*/
|
||||
public class NbtElement<TType> implements NbtWrapper<TType> {
|
||||
// Structure modifier for the base class
|
||||
private static StructureModifier<Object> baseModifier;
|
||||
|
||||
// For retrieving the current type ID
|
||||
private static Method methodGetTypeID;
|
||||
|
||||
// For cloning handles
|
||||
private static Method methodClone;
|
||||
|
||||
// Structure modifiers for the different NBT elements
|
||||
private static StructureModifier<?>[] modifiers = new StructureModifier<?>[NbtType.values().length];
|
||||
|
||||
// The underlying NBT object
|
||||
private Object handle;
|
||||
|
||||
// Saved type
|
||||
private NbtType type;
|
||||
|
||||
/**
|
||||
* Initialize a NBT wrapper for a generic element.
|
||||
* @param handle - the NBT element to wrap.
|
||||
*/
|
||||
NbtElement(Object handle) {
|
||||
this.handle = handle;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the modifier (with no target) that is used to read and write the NBT name.
|
||||
* @return A modifier for accessing the NBT name.
|
||||
*/
|
||||
protected static StructureModifier<String> getBaseModifier() {
|
||||
if (baseModifier == null) {
|
||||
Class<?> base = MinecraftReflection.getNBTBaseClass();
|
||||
|
||||
// This will be the same for all classes, so we'll share modifier
|
||||
baseModifier = new StructureModifier<Object>(base, Object.class, false).withType(String.class);
|
||||
}
|
||||
|
||||
return baseModifier.withType(String.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a modifier (with no target) that is used to read and write the NBT value.
|
||||
* @return The value modifier.
|
||||
*/
|
||||
protected StructureModifier<TType> getCurrentModifier() {
|
||||
NbtType type = getType();
|
||||
|
||||
return getCurrentBaseModifier().withType(type.getValueType());
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the object modifier (with no target) for the current underlying NBT object.
|
||||
* @return The generic modifier.
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
protected StructureModifier<Object> getCurrentBaseModifier() {
|
||||
int index = getType().ordinal();
|
||||
StructureModifier<Object> modifier = (StructureModifier<Object>) modifiers[index];
|
||||
|
||||
// Double checked locking
|
||||
if (modifier == null) {
|
||||
synchronized (this) {
|
||||
if (modifiers[index] == null) {
|
||||
modifiers[index] = new StructureModifier<Object>(handle.getClass(), MinecraftReflection.getNBTBaseClass(), false);
|
||||
}
|
||||
modifier = (StructureModifier<Object>) modifiers[index];
|
||||
}
|
||||
}
|
||||
|
||||
return modifier;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the underlying NBT tag object.
|
||||
* @return The underlying Minecraft tag object.
|
||||
*/
|
||||
@Override
|
||||
public Object getHandle() {
|
||||
return handle;
|
||||
}
|
||||
|
||||
@Override
|
||||
public NbtType getType() {
|
||||
if (methodGetTypeID == null) {
|
||||
// Use the base class
|
||||
methodGetTypeID = FuzzyReflection.fromClass(MinecraftReflection.getNBTBaseClass()).
|
||||
getMethodByParameters("getTypeID", byte.class, new Class<?>[0]);
|
||||
}
|
||||
if (type == null) {
|
||||
try {
|
||||
type = NbtType.getTypeFromID((Byte) methodGetTypeID.invoke(handle));
|
||||
} catch (Exception e) {
|
||||
throw new FieldAccessException("Cannot get NBT type of " + handle, e);
|
||||
}
|
||||
}
|
||||
|
||||
return type;
|
||||
}
|
||||
|
||||
NbtType getSubType() {
|
||||
int subID = getCurrentBaseModifier().<Byte>withType(byte.class).withTarget(handle).read(0);
|
||||
return NbtType.getTypeFromID(subID);
|
||||
}
|
||||
|
||||
void setSubType(NbtType type) {
|
||||
byte subID = (byte) type.getRawID();
|
||||
getCurrentBaseModifier().<Byte>withType(byte.class).withTarget(handle).write(0, subID);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return getBaseModifier().withTarget(handle).read(0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setName(String name) {
|
||||
getBaseModifier().withTarget(handle).write(0, name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TType getValue() {
|
||||
return getCurrentModifier().withTarget(handle).read(0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setValue(TType newValue) {
|
||||
getCurrentModifier().withTarget(handle).write(0, newValue);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(DataOutput destination) {
|
||||
NbtFactory.toStream(this, destination);
|
||||
}
|
||||
|
||||
@Override
|
||||
public NbtBase<TType> clone() {
|
||||
if (methodClone == null) {
|
||||
Class<?> base = MinecraftReflection.getNBTBaseClass();
|
||||
|
||||
// Use the base class
|
||||
methodClone = FuzzyReflection.fromClass(base).
|
||||
getMethodByParameters("clone", base, new Class<?>[0]);
|
||||
}
|
||||
|
||||
try {
|
||||
return NbtFactory.fromNMS(methodClone.invoke(handle));
|
||||
} catch (Exception e) {
|
||||
throw new FieldAccessException("Unable to clone " + handle, e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hashCode(getName(), getType(), getValue());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (obj instanceof NbtBase) {
|
||||
NbtBase<?> other = (NbtBase<?>) obj;
|
||||
|
||||
// Make sure we're dealing with the same type
|
||||
if (other.getType().equals(getType())) {
|
||||
return Objects.equal(getName(), other.getName()) &&
|
||||
Objects.equal(getValue(), other.getValue());
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder result = new StringBuilder();
|
||||
String name = getName();
|
||||
|
||||
result.append("{");
|
||||
|
||||
if (name != null && name.length() > 0)
|
||||
result.append("name: '" + name + "', ");
|
||||
|
||||
result.append("value: ");
|
||||
|
||||
// Wrap quotation marks
|
||||
if (getType() == NbtType.TAG_STRING)
|
||||
result.append("'" + getValue() + "'");
|
||||
else
|
||||
result.append(getValue());
|
||||
|
||||
result.append("}");
|
||||
return result.toString();
|
||||
}
|
||||
}
|
@ -0,0 +1,250 @@
|
||||
package com.comphenix.protocol.wrappers.nbt;
|
||||
|
||||
import java.io.DataInput;
|
||||
import java.io.DataOutput;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import com.comphenix.protocol.reflect.FieldAccessException;
|
||||
import com.comphenix.protocol.reflect.FuzzyReflection;
|
||||
import com.comphenix.protocol.utility.MinecraftReflection;
|
||||
|
||||
/**
|
||||
* Factory methods for creating NBT elements, lists and compounds.
|
||||
*
|
||||
* @author Kristian
|
||||
*/
|
||||
public class NbtFactory {
|
||||
// Used to create the underlying tag
|
||||
private static Method methodCreateTag;
|
||||
|
||||
// Used to read and write NBT
|
||||
private static Method methodWrite;
|
||||
private static Method methodLoad;
|
||||
|
||||
/**
|
||||
* Get a NBT wrapper from a NBT base.
|
||||
* @param base - the base class.
|
||||
* @return A NBT wrapper.
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public static <T> NbtWrapper<T> fromBase(NbtBase<T> base) {
|
||||
if (base instanceof NbtElement) {
|
||||
return (NbtElement<T>) base;
|
||||
} else if (base instanceof NbtCompound) {
|
||||
return (NbtWrapper<T>) base;
|
||||
} else if (base instanceof NbtList) {
|
||||
return (NbtWrapper<T>) base;
|
||||
} else {
|
||||
if (base.getType() == NbtType.TAG_COMPOUND) {
|
||||
// Load into a NBT-backed wrapper
|
||||
NbtCompound copy = NbtCompound.fromName(base.getName());
|
||||
T value = base.getValue();
|
||||
|
||||
copy.setValue((Map<String, NbtBase<?>>) value);
|
||||
return (NbtWrapper<T>) copy;
|
||||
|
||||
} else if (base.getType() == NbtType.TAG_LIST) {
|
||||
// As above
|
||||
NbtList<T> copy = NbtList.fromName(base.getName());
|
||||
|
||||
copy.setValue((List<NbtBase<T>>) base.getValue());
|
||||
return (NbtWrapper<T>) copy;
|
||||
|
||||
} else {
|
||||
// Copy directly
|
||||
NbtWrapper<T> copy = ofType(base.getType(), base.getName());
|
||||
|
||||
copy.setValue(base.getValue());
|
||||
return copy;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize a NBT wrapper.
|
||||
* @param handle - the underlying net.minecraft.server object to wrap.
|
||||
* @return A NBT wrapper.
|
||||
*/
|
||||
@SuppressWarnings({"unchecked", "rawtypes"})
|
||||
public static <T> NbtWrapper<T> fromNMS(Object handle) {
|
||||
NbtElement<T> partial = new NbtElement<T>(handle);
|
||||
|
||||
// See if this is actually a compound tag
|
||||
if (partial.getType() == NbtType.TAG_COMPOUND)
|
||||
return (NbtWrapper<T>) new NbtCompound(handle);
|
||||
else if (partial.getType() == NbtType.TAG_LIST)
|
||||
return new NbtList(handle);
|
||||
else
|
||||
return partial;
|
||||
}
|
||||
|
||||
/**
|
||||
* Write the content of a wrapped NBT tag to a stream.
|
||||
* @param value - the NBT tag to write.
|
||||
* @param destination - the destination stream.
|
||||
*/
|
||||
public static <TType> void toStream(NbtBase<TType> value, DataOutput destination) {
|
||||
if (methodWrite == null) {
|
||||
Class<?> base = MinecraftReflection.getNBTBaseClass();
|
||||
|
||||
// Use the base class
|
||||
methodWrite = FuzzyReflection.fromClass(base).
|
||||
getMethodByParameters("writeNBT", base, DataOutput.class);
|
||||
}
|
||||
|
||||
try {
|
||||
methodWrite.invoke(null, fromBase(value).getHandle(), destination);
|
||||
} catch (Exception e) {
|
||||
throw new FieldAccessException("Unable to write NBT " + value, e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Load an NBT tag from a stream.
|
||||
* @param source - the input stream.
|
||||
* @return An NBT tag.
|
||||
*/
|
||||
public static NbtBase<?> fromStream(DataInput source) {
|
||||
if (methodLoad == null) {
|
||||
Class<?> base = MinecraftReflection.getNBTBaseClass();
|
||||
|
||||
// Use the base class
|
||||
methodLoad = FuzzyReflection.fromClass(base).
|
||||
getMethodByParameters("load", base, new Class<?>[] { DataInput.class });
|
||||
}
|
||||
|
||||
try {
|
||||
return fromNMS(methodLoad.invoke(null, source));
|
||||
} catch (Exception e) {
|
||||
throw new FieldAccessException("Unable to read NBT from " + source, e);
|
||||
}
|
||||
}
|
||||
|
||||
public static NbtBase<String> of(String name, String value) {
|
||||
return ofType(NbtType.TAG_STRING, name, value);
|
||||
}
|
||||
|
||||
public static NbtBase<Byte> of(String name, byte value) {
|
||||
return ofType(NbtType.TAG_BYTE, name, value);
|
||||
}
|
||||
|
||||
public static NbtBase<Short> of(String name, short value) {
|
||||
return ofType(NbtType.TAG_SHORT, name, value);
|
||||
}
|
||||
|
||||
public static NbtBase<Integer> of(String name, int value) {
|
||||
return ofType(NbtType.TAG_INT, name, value);
|
||||
}
|
||||
|
||||
public static NbtBase<Long> of(String name, long value) {
|
||||
return ofType(NbtType.TAG_LONG, name, value);
|
||||
}
|
||||
|
||||
public static NbtBase<Float> of(String name, float value) {
|
||||
return ofType(NbtType.TAG_FLOAT, name, value);
|
||||
}
|
||||
|
||||
public static NbtBase<Double> of(String name, double value) {
|
||||
return ofType(NbtType.TAG_DOUBlE, name, value);
|
||||
}
|
||||
|
||||
public static NbtBase<byte[]> of(String name, byte[] value) {
|
||||
return ofType(NbtType.TAG_BYTE_ARRAY, name, value);
|
||||
}
|
||||
|
||||
public static NbtBase<int[]> of(String name, int[] value) {
|
||||
return ofType(NbtType.TAG_INT_ARRAY, name, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a new NBT compound wrapper initialized with a given list of NBT values.
|
||||
* @param name - the name of the compound wrapper.
|
||||
* @param list - the list of elements to add.
|
||||
* @return The new wrapped NBT compound.
|
||||
*/
|
||||
public static <T> NbtCompound ofCompound(String name, Collection<? extends NbtBase<T>> list) {
|
||||
return NbtCompound.fromList(name, list);
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a NBT list of out an array of values.
|
||||
* @param name - name of this list.
|
||||
* @param elements - elements to add.
|
||||
* @return The new filled NBT list.
|
||||
*/
|
||||
public static <T> NbtList<T> ofList(String name, T... elements) {
|
||||
return NbtList.fromArray(name, elements);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new NBT wrapper from a given type.
|
||||
* @param type - the NBT type.
|
||||
* @param name - the name of the NBT tag.
|
||||
* @return The new wrapped NBT tag.
|
||||
* @throws FieldAccessException If we're unable to create the underlying tag.
|
||||
*/
|
||||
@SuppressWarnings({"unchecked", "rawtypes"})
|
||||
public static <T> NbtWrapper<T> ofType(NbtType type, String name) {
|
||||
if (type == null)
|
||||
throw new IllegalArgumentException("type cannot be NULL.");
|
||||
if (type == NbtType.TAG_END)
|
||||
throw new IllegalArgumentException("Cannot create a TAG_END.");
|
||||
|
||||
if (methodCreateTag == null) {
|
||||
Class<?> base = MinecraftReflection.getNBTBaseClass();
|
||||
|
||||
// Use the base class
|
||||
methodCreateTag = FuzzyReflection.fromClass(base).
|
||||
getMethodByParameters("createTag", base, new Class<?>[] { byte.class, String.class });
|
||||
}
|
||||
|
||||
try {
|
||||
Object handle = methodCreateTag.invoke(null, (byte) type.getRawID(), name);
|
||||
|
||||
if (type == NbtType.TAG_COMPOUND)
|
||||
return (NbtWrapper<T>) new NbtCompound(handle);
|
||||
else if (type == NbtType.TAG_LIST)
|
||||
return (NbtWrapper<T>) new NbtList(handle);
|
||||
else
|
||||
return new NbtElement<T>(handle);
|
||||
|
||||
} catch (Exception e) {
|
||||
// Inform the caller
|
||||
throw new FieldAccessException(
|
||||
String.format("Cannot create NBT element %s (type: %s)", name, type),
|
||||
e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new NBT wrapper from a given type.
|
||||
* @param type - the NBT type.
|
||||
* @param name - the name of the NBT tag.
|
||||
* @param value - the value of the new tag.
|
||||
* @return The new wrapped NBT tag.
|
||||
* @throws FieldAccessException If we're unable to create the underlying tag.
|
||||
*/
|
||||
public static <T> NbtWrapper<T> ofType(NbtType type, String name, T value) {
|
||||
NbtWrapper<T> created = ofType(type, name);
|
||||
|
||||
// Update the value
|
||||
created.setValue(value);
|
||||
return created;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new NBT wrapper from a given type.
|
||||
* @param type - type of the NBT value.
|
||||
* @param name - the name of the NBT tag.
|
||||
* @param value - the value of the new tag.
|
||||
* @return The new wrapped NBT tag.
|
||||
* @throws FieldAccessException If we're unable to create the underlying tag.
|
||||
* @throws IllegalArgumentException If the given class type is not valid NBT.
|
||||
*/
|
||||
public static <T> NbtWrapper<T> ofType(Class<?> type, String name, T value) {
|
||||
return ofType(NbtType.getTypeFromClass(type), name, value);
|
||||
}
|
||||
}
|
@ -0,0 +1,311 @@
|
||||
package com.comphenix.protocol.wrappers.nbt;
|
||||
|
||||
import java.io.DataOutput;
|
||||
import java.util.Collection;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import com.google.common.base.Function;
|
||||
import com.google.common.base.Joiner;
|
||||
import com.google.common.collect.Iterables;
|
||||
|
||||
/**
|
||||
* Represents a list of NBT tags of the same type without names.
|
||||
* <p>
|
||||
* Use {@link NbtFactory} to load or create an instance.
|
||||
*
|
||||
* @author Kristian
|
||||
*
|
||||
* @param <TType> - the value type of each NBT tag.
|
||||
*/
|
||||
public class NbtList<TType> implements NbtWrapper<List<NbtBase<TType>>>, Iterable<TType> {
|
||||
/**
|
||||
* The name of every NBT tag in a list.
|
||||
*/
|
||||
public static String EMPTY_NAME = "";
|
||||
|
||||
// A list container
|
||||
private NbtElement<List<Object>> container;
|
||||
|
||||
// Saved wrapper list
|
||||
private ConvertedList<Object, NbtBase<TType>> savedList;
|
||||
|
||||
/**
|
||||
* Construct a new empty NBT list.
|
||||
* @param name - name of this list.
|
||||
* @return The new empty NBT list.
|
||||
*/
|
||||
public static <T> NbtList<T> fromName(String name) {
|
||||
return (NbtList<T>) NbtFactory.<List<NbtBase<T>>>ofType(NbtType.TAG_LIST, name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a NBT list of out an array of values..
|
||||
* @param name - name of this list.
|
||||
* @param elements - values to add.
|
||||
* @return The new filled NBT list.
|
||||
*/
|
||||
public static <T> NbtList<T> fromArray(String name, T... elements) {
|
||||
NbtList<T> result = fromName(name);
|
||||
|
||||
for (T element : elements) {
|
||||
if (element == null)
|
||||
throw new IllegalArgumentException("An NBT list cannot contain a null element!");
|
||||
result.add(NbtFactory.ofType(element.getClass(), EMPTY_NAME, element));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a NBT list of out a list of NBT elements.
|
||||
* @param name - name of this list.
|
||||
* @param elements - elements to add.
|
||||
* @return The new filled NBT list.
|
||||
*/
|
||||
public static <T> NbtList<T> fromList(String name, Collection<? extends T> elements) {
|
||||
NbtList<T> result = fromName(name);
|
||||
|
||||
for (T element : elements) {
|
||||
if (element == null)
|
||||
throw new IllegalArgumentException("An NBT list cannot contain a null element!");
|
||||
result.add(NbtFactory.ofType(element.getClass(), EMPTY_NAME, element));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
NbtList(Object handle) {
|
||||
this.container = new NbtElement<List<Object>>(handle);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getHandle() {
|
||||
return container.getHandle();
|
||||
}
|
||||
|
||||
@Override
|
||||
public NbtType getType() {
|
||||
return NbtType.TAG_LIST;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the type of each element.
|
||||
* @return Element type.
|
||||
*/
|
||||
public NbtType getElementType() {
|
||||
return container.getSubType();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return container.getName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setName(String name) {
|
||||
container.setName(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<NbtBase<TType>> getValue() {
|
||||
if (savedList == null) {
|
||||
savedList = new ConvertedList<Object, NbtBase<TType>>(container.getValue()) {
|
||||
@Override
|
||||
public boolean add(NbtBase<TType> e) {
|
||||
if (e == null)
|
||||
throw new IllegalArgumentException("Cannot store NULL elements in list.");
|
||||
if (!e.getName().equals(EMPTY_NAME))
|
||||
throw new IllegalArgumentException("Cannot add a named NBT tag " + e + " to a list.");
|
||||
if (size() == 0)
|
||||
container.setSubType(e.getType());
|
||||
|
||||
return super.add(e);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void add(int index, NbtBase<TType> element) {
|
||||
if (element == null)
|
||||
throw new IllegalArgumentException("Cannot store NULL elements in list.");
|
||||
if (!element.getName().equals(EMPTY_NAME))
|
||||
throw new IllegalArgumentException("Cannot add a the named NBT tag " + element + " to a list.");
|
||||
if (index == 0)
|
||||
container.setSubType(element.getType());
|
||||
|
||||
super.add(index, element);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean addAll(Collection<? extends NbtBase<TType>> c) {
|
||||
boolean empty = size() == 0;
|
||||
boolean result = false;
|
||||
|
||||
for (NbtBase<TType> element : c) {
|
||||
add(element);
|
||||
result = true;
|
||||
}
|
||||
|
||||
// See if we now added our first object(s)
|
||||
if (empty && result) {
|
||||
container.setSubType(get(0).getType());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Object toInner(NbtBase<TType> outer) {
|
||||
if (outer == null)
|
||||
return null;
|
||||
return NbtFactory.fromBase(outer).getHandle();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected NbtBase<TType> toOuter(Object inner) {
|
||||
if (inner == null)
|
||||
return null;
|
||||
return NbtFactory.fromNMS(inner);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return NbtList.this.toString();
|
||||
}
|
||||
};
|
||||
}
|
||||
return savedList;
|
||||
}
|
||||
|
||||
@SuppressWarnings({"unchecked", "rawtypes"})
|
||||
public NbtBase<List<NbtBase<TType>>> clone() {
|
||||
return (NbtBase) container.clone();
|
||||
}
|
||||
|
||||
public void add(NbtBase<TType> element) {
|
||||
getValue().add(element);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public void add(String value) {
|
||||
add((NbtBase<TType>) NbtFactory.of(EMPTY_NAME, value));
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public void add(byte value) {
|
||||
add((NbtBase<TType>) NbtFactory.of(EMPTY_NAME, value));
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public void add(short value) {
|
||||
add((NbtBase<TType>) NbtFactory.of(EMPTY_NAME, value));
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public void add(int value) {
|
||||
add((NbtBase<TType>) NbtFactory.of(EMPTY_NAME, value));
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public void add(long value) {
|
||||
add((NbtBase<TType>) NbtFactory.of(EMPTY_NAME, value));
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public void add(double value) {
|
||||
add((NbtBase<TType>) NbtFactory.of(EMPTY_NAME, value));
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public void add(byte[] value) {
|
||||
add((NbtBase<TType>) NbtFactory.of(EMPTY_NAME, value));
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public void add(int[] value) {
|
||||
add((NbtBase<TType>) NbtFactory.of(EMPTY_NAME, value));
|
||||
}
|
||||
|
||||
public int size() {
|
||||
return getValue().size();
|
||||
}
|
||||
|
||||
public TType getValue(int index) {
|
||||
return getValue().get(index).getValue();
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve each NBT tag in this list.
|
||||
* @return A view of NBT tag in this list.
|
||||
*/
|
||||
public Collection<NbtBase<TType>> asCollection() {
|
||||
return getValue();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setValue(List<NbtBase<TType>> newValue) {
|
||||
NbtBase<TType> lastElement = null;
|
||||
List<Object> list = container.getValue();
|
||||
list.clear();
|
||||
|
||||
// Set each underlying element
|
||||
for (NbtBase<TType> type : newValue) {
|
||||
if (type != null) {
|
||||
lastElement = type;
|
||||
list.add(NbtFactory.fromBase(type).getHandle());
|
||||
} else {
|
||||
list.add(null);
|
||||
}
|
||||
}
|
||||
|
||||
// Update the sub type as well
|
||||
if (lastElement != null) {
|
||||
container.setSubType(lastElement.getType());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(DataOutput destination) {
|
||||
NbtFactory.toStream(container, destination);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (obj instanceof NbtList) {
|
||||
@SuppressWarnings("unchecked")
|
||||
NbtList<TType> other = (NbtList<TType>) obj;
|
||||
return container.equals(other.container);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return container.hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<TType> iterator() {
|
||||
return Iterables.transform(getValue(), new Function<NbtBase<TType>, TType>() {
|
||||
@Override
|
||||
public TType apply(@Nullable NbtBase<TType> param) {
|
||||
return param.getValue();
|
||||
}
|
||||
}).iterator();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder builder = new StringBuilder();
|
||||
|
||||
builder.append("{\"name\": \"" + getName() + "\", \"value\": [");
|
||||
|
||||
if (size() > 0) {
|
||||
if (getElementType() == NbtType.TAG_STRING)
|
||||
builder.append("\"" + Joiner.on("\", \"").join(this) + "\"");
|
||||
else
|
||||
builder.append(Joiner.on(", ").join(this));
|
||||
}
|
||||
|
||||
builder.append("]}");
|
||||
return builder.toString();
|
||||
}
|
||||
}
|
@ -0,0 +1,141 @@
|
||||
package com.comphenix.protocol.wrappers.nbt;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Represents all the element types
|
||||
*
|
||||
* @author Kristian
|
||||
*/
|
||||
public enum NbtType {
|
||||
/**
|
||||
* Used to mark the end of compound tags. CANNOT be constructed.
|
||||
*/
|
||||
TAG_END(0, Void.class),
|
||||
|
||||
/**
|
||||
* A signed 1 byte integral type. Sometimes used for booleans.
|
||||
*/
|
||||
TAG_BYTE(1, byte.class),
|
||||
|
||||
/**
|
||||
* A signed 2 byte integral type.
|
||||
*/
|
||||
TAG_SHORT(2, short.class),
|
||||
|
||||
/**
|
||||
* A signed 4 byte integral type.
|
||||
*/
|
||||
TAG_INT(3, int.class),
|
||||
|
||||
/**
|
||||
* A signed 8 byte integral type.
|
||||
*/
|
||||
TAG_LONG(4, long.class),
|
||||
|
||||
/**
|
||||
* A signed 4 byte floating point type.
|
||||
*/
|
||||
TAG_FLOAT(5, float.class),
|
||||
|
||||
/**
|
||||
* A signed 8 byte floating point type.
|
||||
*/
|
||||
TAG_DOUBlE(6, double.class),
|
||||
|
||||
/**
|
||||
* An array of bytes.
|
||||
*/
|
||||
TAG_BYTE_ARRAY(7, byte[].class),
|
||||
|
||||
/**
|
||||
* An array of TAG_Int's payloads..
|
||||
*/
|
||||
TAG_INT_ARRAY(11, int[].class),
|
||||
|
||||
/**
|
||||
* A UTF-8 string
|
||||
*/
|
||||
TAG_STRING(8, String.class),
|
||||
|
||||
/**
|
||||
* A list of tag payloads, without repeated tag IDs or any tag names.
|
||||
*/
|
||||
TAG_LIST(9, List.class),
|
||||
|
||||
/**
|
||||
* A list of fully formed tags, including their IDs, names, and payloads. No two tags may have the same name.
|
||||
*/
|
||||
TAG_COMPOUND(10, Map.class);
|
||||
|
||||
private int rawID;
|
||||
private Class<?> valueType;
|
||||
|
||||
// Used to lookup a specified NBT
|
||||
private static NbtType[] lookup;
|
||||
|
||||
// Lookup NBT by class
|
||||
private static Map<Class<?>, NbtType> classLookup;
|
||||
|
||||
static {
|
||||
NbtType[] values = values();
|
||||
lookup = new NbtType[values.length];
|
||||
classLookup = new HashMap<Class<?>, NbtType>();
|
||||
|
||||
// Initialize lookup tables
|
||||
for (NbtType type : values) {
|
||||
lookup[type.getRawID()] = type;
|
||||
classLookup.put(type.getValueType(), type);
|
||||
}
|
||||
}
|
||||
|
||||
private NbtType(int rawID, Class<?> valueType) {
|
||||
this.rawID = rawID;
|
||||
this.valueType = valueType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the raw unique integer that identifies the type of the parent NBT element.
|
||||
* @return Integer that uniquely identifying the type.
|
||||
*/
|
||||
public int getRawID() {
|
||||
return rawID;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the type of the value stored in the NBT element.
|
||||
* @return Type of the stored value.
|
||||
*/
|
||||
public Class<?> getValueType() {
|
||||
return valueType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve an NBT type from a given raw ID.
|
||||
* @param rawID - the raw ID to lookup.
|
||||
* @return The associated NBT value.
|
||||
*/
|
||||
public static NbtType getTypeFromID(int rawID) {
|
||||
if (rawID < 0 || rawID >= lookup.length)
|
||||
throw new IllegalArgumentException("Unrecognized raw ID " + rawID);
|
||||
return lookup[rawID];
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve an NBT type from the given Java class.
|
||||
* @param clazz - type of the value the NBT type can contain.
|
||||
* @return The NBT type.
|
||||
* @throws IllegalArgumentException If this class type cannot be represented by NBT tags.
|
||||
*/
|
||||
public static NbtType getTypeFromClass(Class<?> clazz) {
|
||||
NbtType result = classLookup.get(clazz);
|
||||
|
||||
// Try to lookup this value
|
||||
if (result != null)
|
||||
return result;
|
||||
else
|
||||
throw new IllegalArgumentException("No NBT tag can represent a " + clazz);
|
||||
}
|
||||
}
|
@ -0,0 +1,24 @@
|
||||
package com.comphenix.protocol.wrappers.nbt;
|
||||
|
||||
import java.io.DataOutput;
|
||||
|
||||
/**
|
||||
* Indicates that this NBT wraps an underlying net.minecraft.server instance.
|
||||
*
|
||||
* @author Kristian
|
||||
*
|
||||
* @param <TType> - type of the value that is stored.
|
||||
*/
|
||||
public interface NbtWrapper<TType> extends NbtBase<TType> {
|
||||
/**
|
||||
* Retrieve the underlying net.minecraft.server instance.
|
||||
* @return The NMS instance.
|
||||
*/
|
||||
public Object getHandle();
|
||||
|
||||
/**
|
||||
* Write the current NBT tag to an output stream.
|
||||
* @param destination - the destination stream.
|
||||
*/
|
||||
public void write(DataOutput destination);
|
||||
}
|
@ -0,0 +1,46 @@
|
||||
package com.comphenix.protocol.wrappers.nbt;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.DataInput;
|
||||
import java.io.DataInputStream;
|
||||
import java.io.DataOutput;
|
||||
import java.io.DataOutputStream;
|
||||
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
|
||||
import com.comphenix.protocol.utility.MinecraftReflection;
|
||||
|
||||
public class NbtFactoryTest {
|
||||
@BeforeClass
|
||||
public static void initializeBukkit() {
|
||||
// Initialize reflection
|
||||
MinecraftReflection.setMinecraftPackage("net.minecraft.server.v1_4_6", "org.bukkit.craftbukkit.v1_4_6");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFromStream() {
|
||||
NbtCompound compound = NbtCompound.fromName("tag");
|
||||
|
||||
compound.put("name", "Test Testerson");
|
||||
compound.put("age", 42);
|
||||
|
||||
compound.put(NbtFactory.ofList("nicknames", "a", "b", "c"));
|
||||
|
||||
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
|
||||
DataOutput test = new DataOutputStream(buffer);
|
||||
compound.write(test);
|
||||
|
||||
ByteArrayInputStream source = new ByteArrayInputStream(buffer.toByteArray());
|
||||
DataInput input = new DataInputStream(source);
|
||||
|
||||
NbtCompound cloned = (NbtCompound) NbtFactory.fromStream(input);
|
||||
|
||||
assertEquals(compound.getString("name"), cloned.getString("name"));
|
||||
assertEquals(compound.getInteger("age"), cloned.getInteger("age"));
|
||||
assertEquals(compound.getList("nicknames"), cloned.getList("nicknames"));
|
||||
}
|
||||
}
|
In neuem Issue referenzieren
Einen Benutzer sperren