mirror of
https://github.com/dmulloy2/ProtocolLib.git
synced 2024-11-01 00:11:39 +01:00
Added the ability to serialize and deserialize NBT to many formats.
This commit is contained in:
parent
3426306805
commit
dbc28c0035
@ -29,6 +29,13 @@ import com.comphenix.protocol.wrappers.nbt.NbtType;
|
|||||||
* @param <TType> - type of the value that is stored.
|
* @param <TType> - type of the value that is stored.
|
||||||
*/
|
*/
|
||||||
public interface NbtBase<TType> {
|
public interface NbtBase<TType> {
|
||||||
|
/**
|
||||||
|
* 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.
|
* Retrieve the type of this NBT element.
|
||||||
* @return The type of this NBT element.
|
* @return The type of this NBT element.
|
||||||
|
@ -10,7 +10,7 @@ import java.util.Set;
|
|||||||
* <p>
|
* <p>
|
||||||
* Use {@link NbtFactory} to load or create an instance.
|
* Use {@link NbtFactory} to load or create an instance.
|
||||||
* <p>
|
* <p>
|
||||||
* The {@link NbtBase#getValue()} method returns a {@link java.util.Map} that will correctly return the content
|
* 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.
|
* of this NBT compound, but may throw an {@link UnsupportedOperationException} for any of the write operations.
|
||||||
*
|
*
|
||||||
* @author Kristian
|
* @author Kristian
|
||||||
@ -74,6 +74,14 @@ public interface NbtCompound extends NbtBase<Map<String, NbtBase<?>>>, Iterable<
|
|||||||
*/
|
*/
|
||||||
public abstract NbtCompound put(String key, String value);
|
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.
|
* Retrieve the byte value of an entry identified by a given key.
|
||||||
* @param key - the key of the entry.
|
* @param key - the key of the entry.
|
||||||
@ -286,7 +294,7 @@ public interface NbtCompound extends NbtBase<Map<String, NbtBase<?>>>, Iterable<
|
|||||||
* @param list - the list value.
|
* @param list - the list value.
|
||||||
* @return This current compound, for chaining.
|
* @return This current compound, for chaining.
|
||||||
*/
|
*/
|
||||||
public abstract <T> NbtCompound put(WrappedList<T> list);
|
public abstract <T> NbtCompound put(NbtList<T> list);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Associate a new NBT list with the given key.
|
* Associate a new NBT list with the given key.
|
||||||
|
@ -17,8 +17,6 @@
|
|||||||
|
|
||||||
package com.comphenix.protocol.wrappers.nbt;
|
package com.comphenix.protocol.wrappers.nbt;
|
||||||
|
|
||||||
import java.io.DataInput;
|
|
||||||
import java.io.DataOutput;
|
|
||||||
import java.lang.reflect.Method;
|
import java.lang.reflect.Method;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@ -41,10 +39,6 @@ public class NbtFactory {
|
|||||||
// Used to create the underlying tag
|
// Used to create the underlying tag
|
||||||
private static Method methodCreateTag;
|
private static Method methodCreateTag;
|
||||||
|
|
||||||
// Used to read and write NBT
|
|
||||||
private static Method methodWrite;
|
|
||||||
private static Method methodLoad;
|
|
||||||
|
|
||||||
// Item stack trickery
|
// Item stack trickery
|
||||||
private static StructureModifier<Object> itemStackModifier;
|
private static StructureModifier<Object> itemStackModifier;
|
||||||
|
|
||||||
@ -102,7 +96,7 @@ public class NbtFactory {
|
|||||||
|
|
||||||
} else if (base.getType() == NbtType.TAG_LIST) {
|
} else if (base.getType() == NbtType.TAG_LIST) {
|
||||||
// As above
|
// As above
|
||||||
WrappedList<T> copy = WrappedList.fromName(base.getName());
|
NbtList<T> copy = WrappedList.fromName(base.getName());
|
||||||
|
|
||||||
copy.setValue((List<NbtBase<T>>) base.getValue());
|
copy.setValue((List<NbtBase<T>>) base.getValue());
|
||||||
return (NbtWrapper<T>) copy;
|
return (NbtWrapper<T>) copy;
|
||||||
@ -167,48 +161,6 @@ public class NbtFactory {
|
|||||||
return partial;
|
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(NbtWrapper<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 NbtWrapper<?> 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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructs a NBT tag of type string.
|
* Constructs a NBT tag of type string.
|
||||||
* @param name - name of the tag.
|
* @param name - name of the tag.
|
||||||
@ -276,7 +228,7 @@ public class NbtFactory {
|
|||||||
* @return The constructed NBT tag.
|
* @return The constructed NBT tag.
|
||||||
*/
|
*/
|
||||||
public static NbtBase<Double> of(String name, double value) {
|
public static NbtBase<Double> of(String name, double value) {
|
||||||
return ofWrapper(NbtType.TAG_DOUBlE, name, value);
|
return ofWrapper(NbtType.TAG_DOUBLE, name, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -24,10 +24,26 @@ public interface NbtList<TType> extends NbtBase<List<NbtBase<TType>>>, Iterable<
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the type of each element.
|
* Get the type of each element.
|
||||||
|
* <p>
|
||||||
|
* This will be {@link NbtType#TAG_END TAG_END} if the NBT list has just been created.
|
||||||
* @return Element type.
|
* @return Element type.
|
||||||
*/
|
*/
|
||||||
public abstract NbtType getElementType();
|
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.
|
||||||
|
* <p>
|
||||||
|
* 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.
|
* Add a NBT list or NBT compound to the list.
|
||||||
* @param element
|
* @param element
|
||||||
|
@ -62,7 +62,7 @@ public enum NbtType {
|
|||||||
/**
|
/**
|
||||||
* A signed 8 byte floating point type.
|
* A signed 8 byte floating point type.
|
||||||
*/
|
*/
|
||||||
TAG_DOUBlE(6, double.class),
|
TAG_DOUBLE(6, double.class),
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An array of bytes.
|
* An array of bytes.
|
||||||
@ -113,6 +113,10 @@ public enum NbtType {
|
|||||||
classLookup.put(Primitives.wrap(type.getValueType()), type);
|
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) {
|
private NbtType(int rawID, Class<?> valueType) {
|
||||||
@ -120,6 +124,14 @@ public enum NbtType {
|
|||||||
this.valueType = valueType;
|
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.
|
* Retrieves the raw unique integer that identifies the type of the parent NBT element.
|
||||||
* @return Integer that uniquely identifying the type.
|
* @return Integer that uniquely identifying the type.
|
||||||
@ -157,9 +169,16 @@ public enum NbtType {
|
|||||||
NbtType result = classLookup.get(clazz);
|
NbtType result = classLookup.get(clazz);
|
||||||
|
|
||||||
// Try to lookup this value
|
// Try to lookup this value
|
||||||
if (result != null)
|
if (result != null) {
|
||||||
return result;
|
return result;
|
||||||
else
|
} 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);
|
throw new IllegalArgumentException("No NBT tag can represent a " + clazz);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
@ -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);
|
||||||
|
}
|
@ -23,6 +23,8 @@ import java.util.Iterator;
|
|||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
|
import com.comphenix.protocol.wrappers.nbt.io.NbtBinarySerializer;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A concrete implementation of an NbtCompound that wraps an underlying NMS Compound.
|
* A concrete implementation of an NbtCompound that wraps an underlying NMS Compound.
|
||||||
*
|
*
|
||||||
@ -67,6 +69,19 @@ class WrappedCompound implements NbtWrapper<Map<String, NbtBase<?>>>, Iterable<N
|
|||||||
this.container = new WrappedElement<Map<String,Object>>(handle);
|
this.container = new WrappedElement<Map<String,Object>>(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
|
@Override
|
||||||
public Object getHandle() {
|
public Object getHandle() {
|
||||||
return container.getHandle();
|
return container.getHandle();
|
||||||
@ -420,7 +435,7 @@ class WrappedCompound implements NbtWrapper<Map<String, NbtBase<?>>>, Iterable<N
|
|||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public double getDoubleOrDefault(String key) {
|
public double getDoubleOrDefault(String key) {
|
||||||
return (Double) getValueOrDefault(key, NbtType.TAG_DOUBlE).getValue();
|
return (Double) getValueOrDefault(key, NbtType.TAG_DOUBLE).getValue();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -543,11 +558,20 @@ class WrappedCompound implements NbtWrapper<Map<String, NbtBase<?>>>, Iterable<N
|
|||||||
* @return This current compound, for chaining.
|
* @return This current compound, for chaining.
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public <T> NbtCompound put(WrappedList<T> list) {
|
public <T> NbtCompound put(NbtList<T> list) {
|
||||||
getValue().put(list.getName(), list);
|
getValue().put(list.getName(), list);
|
||||||
return this;
|
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.
|
* Associate a new NBT list with the given key.
|
||||||
* @param key - the key and name of the new NBT list.
|
* @param key - the key and name of the new NBT list.
|
||||||
@ -561,7 +585,7 @@ class WrappedCompound implements NbtWrapper<Map<String, NbtBase<?>>>, Iterable<N
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void write(DataOutput destination) {
|
public void write(DataOutput destination) {
|
||||||
NbtFactory.toStream(container, destination);
|
NbtBinarySerializer.DEFAULT.serialize(container, destination);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -24,6 +24,7 @@ import com.comphenix.protocol.reflect.FieldAccessException;
|
|||||||
import com.comphenix.protocol.reflect.FuzzyReflection;
|
import com.comphenix.protocol.reflect.FuzzyReflection;
|
||||||
import com.comphenix.protocol.reflect.StructureModifier;
|
import com.comphenix.protocol.reflect.StructureModifier;
|
||||||
import com.comphenix.protocol.utility.MinecraftReflection;
|
import com.comphenix.protocol.utility.MinecraftReflection;
|
||||||
|
import com.comphenix.protocol.wrappers.nbt.io.NbtBinarySerializer;
|
||||||
import com.google.common.base.Objects;
|
import com.google.common.base.Objects;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -106,6 +107,11 @@ class WrappedElement<TType> implements NbtWrapper<TType> {
|
|||||||
return modifier;
|
return modifier;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean accept(NbtVisitor visitor) {
|
||||||
|
return visitor.visit(this);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieve the underlying NBT tag object.
|
* Retrieve the underlying NBT tag object.
|
||||||
* @return The underlying Minecraft tag object.
|
* @return The underlying Minecraft tag object.
|
||||||
@ -173,7 +179,8 @@ class WrappedElement<TType> implements NbtWrapper<TType> {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void write(DataOutput destination) {
|
public void write(DataOutput destination) {
|
||||||
NbtFactory.toStream(this, destination);
|
// No need to cache this object
|
||||||
|
NbtBinarySerializer.DEFAULT.serialize(this, destination);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -24,6 +24,7 @@ import java.util.List;
|
|||||||
|
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
|
import com.comphenix.protocol.wrappers.nbt.io.NbtBinarySerializer;
|
||||||
import com.google.common.base.Function;
|
import com.google.common.base.Function;
|
||||||
import com.google.common.base.Joiner;
|
import com.google.common.base.Joiner;
|
||||||
import com.google.common.collect.Iterables;
|
import com.google.common.collect.Iterables;
|
||||||
@ -41,13 +42,17 @@ class WrappedList<TType> implements NbtWrapper<List<NbtBase<TType>>>, Iterable<T
|
|||||||
// Saved wrapper list
|
// Saved wrapper list
|
||||||
private ConvertedList<Object, NbtBase<TType>> savedList;
|
private ConvertedList<Object, NbtBase<TType>> savedList;
|
||||||
|
|
||||||
|
// Element type
|
||||||
|
private NbtType elementType = NbtType.TAG_END;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Construct a new empty NBT list.
|
* Construct a new empty NBT list.
|
||||||
* @param name - name of this list.
|
* @param name - name of this list.
|
||||||
* @return The new empty NBT list.
|
* @return The new empty NBT list.
|
||||||
*/
|
*/
|
||||||
public static <T> WrappedList<T> fromName(String name) {
|
@SuppressWarnings("unchecked")
|
||||||
return (WrappedList<T>) NbtFactory.<List<NbtBase<T>>>ofWrapper(NbtType.TAG_LIST, name);
|
public static <T> NbtList<T> fromName(String name) {
|
||||||
|
return (NbtList<T>) NbtFactory.<List<NbtBase<T>>>ofWrapper(NbtType.TAG_LIST, name);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -56,12 +61,17 @@ class WrappedList<TType> implements NbtWrapper<List<NbtBase<TType>>>, Iterable<T
|
|||||||
* @param elements - values to add.
|
* @param elements - values to add.
|
||||||
* @return The new filled NBT list.
|
* @return The new filled NBT list.
|
||||||
*/
|
*/
|
||||||
public static <T> WrappedList<T> fromArray(String name, T... elements) {
|
@SuppressWarnings({"unchecked", "rawtypes"})
|
||||||
WrappedList<T> result = fromName(name);
|
public static <T> NbtList<T> fromArray(String name, T... elements) {
|
||||||
|
NbtList<T> result = fromName(name);
|
||||||
|
|
||||||
for (T element : elements) {
|
for (T element : elements) {
|
||||||
if (element == null)
|
if (element == null)
|
||||||
throw new IllegalArgumentException("An NBT list cannot contain a null element!");
|
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));
|
result.add(NbtFactory.ofWrapper(element.getClass(), EMPTY_NAME, element));
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
@ -73,19 +83,42 @@ class WrappedList<TType> implements NbtWrapper<List<NbtBase<TType>>>, Iterable<T
|
|||||||
* @param elements - elements to add.
|
* @param elements - elements to add.
|
||||||
* @return The new filled NBT list.
|
* @return The new filled NBT list.
|
||||||
*/
|
*/
|
||||||
public static <T> WrappedList<T> fromList(String name, Collection<? extends T> elements) {
|
@SuppressWarnings({"unchecked", "rawtypes"})
|
||||||
WrappedList<T> result = fromName(name);
|
public static <T> NbtList<T> fromList(String name, Collection<? extends T> elements) {
|
||||||
|
NbtList<T> result = fromName(name);
|
||||||
|
|
||||||
for (T element : elements) {
|
for (T element : elements) {
|
||||||
if (element == null)
|
if (element == null)
|
||||||
throw new IllegalArgumentException("An NBT list cannot contain a null element!");
|
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));
|
result.add(NbtFactory.ofWrapper(element.getClass(), EMPTY_NAME, element));
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Construct a list from an NMS instance.
|
||||||
|
* @param handle - NMS instance.
|
||||||
|
*/
|
||||||
public WrappedList(Object handle) {
|
public WrappedList(Object handle) {
|
||||||
this.container = new WrappedElement<List<Object>>(handle);
|
this.container = new WrappedElement<List<Object>>(handle);
|
||||||
|
this.elementType = container.getSubType();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean accept(NbtVisitor visitor) {
|
||||||
|
// Enter this node?
|
||||||
|
if (visitor.visitEnter(this)) {
|
||||||
|
for (NbtBase<TType> node : getValue()) {
|
||||||
|
if (!node.accept(visitor))
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return visitor.visitLeave(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -98,13 +131,15 @@ class WrappedList<TType> implements NbtWrapper<List<NbtBase<TType>>>, Iterable<T
|
|||||||
return NbtType.TAG_LIST;
|
return NbtType.TAG_LIST;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the type of each element.
|
|
||||||
* @return Element type.
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
public NbtType getElementType() {
|
public NbtType getElementType() {
|
||||||
return container.getSubType();
|
return elementType;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setElementType(NbtType type) {
|
||||||
|
this.elementType = type;
|
||||||
|
container.setSubType(type);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -129,7 +164,7 @@ class WrappedList<TType> implements NbtWrapper<List<NbtBase<TType>>>, Iterable<T
|
|||||||
throw new IllegalArgumentException("Cannot add a the named NBT tag " + element + " to a list.");
|
throw new IllegalArgumentException("Cannot add a the named NBT tag " + element + " to a list.");
|
||||||
|
|
||||||
// Check element type
|
// Check element type
|
||||||
if (size() > 0) {
|
if (getElementType() != NbtType.TAG_END) {
|
||||||
if (!element.getType().equals(getElementType())) {
|
if (!element.getType().equals(getElementType())) {
|
||||||
throw new IllegalArgumentException(
|
throw new IllegalArgumentException(
|
||||||
"Cannot add " + element + " of " + element.getType() + " to a list of type " + getElementType());
|
"Cannot add " + element + " of " + element.getType() + " to a list of type " + getElementType());
|
||||||
@ -153,18 +188,12 @@ class WrappedList<TType> implements NbtWrapper<List<NbtBase<TType>>>, Iterable<T
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean addAll(Collection<? extends NbtBase<TType>> c) {
|
public boolean addAll(Collection<? extends NbtBase<TType>> c) {
|
||||||
boolean empty = size() == 0;
|
|
||||||
boolean result = false;
|
boolean result = false;
|
||||||
|
|
||||||
for (NbtBase<TType> element : c) {
|
for (NbtBase<TType> element : c) {
|
||||||
add(element);
|
add(element);
|
||||||
result = true;
|
result = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// See if we now added our first object(s)
|
|
||||||
if (empty && result) {
|
|
||||||
container.setSubType(get(0).getType());
|
|
||||||
}
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -197,6 +226,38 @@ class WrappedList<TType> implements NbtWrapper<List<NbtBase<TType>>>, Iterable<T
|
|||||||
return (NbtBase) container.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<TType>) value);
|
||||||
|
|
||||||
|
} else {
|
||||||
|
// Just add it
|
||||||
|
add((NbtBase<TType>) NbtFactory.ofWrapper(getElementType(), EMPTY_NAME, value));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void add(NbtBase<TType> element) {
|
public void add(NbtBase<TType> element) {
|
||||||
getValue().add(element);
|
getValue().add(element);
|
||||||
@ -293,7 +354,7 @@ class WrappedList<TType> implements NbtWrapper<List<NbtBase<TType>>>, Iterable<T
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void write(DataOutput destination) {
|
public void write(DataOutput destination) {
|
||||||
NbtFactory.toStream(container, destination);
|
NbtBinarySerializer.DEFAULT.serialize(container, destination);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -0,0 +1,88 @@
|
|||||||
|
package com.comphenix.protocol.wrappers.nbt.io;
|
||||||
|
|
||||||
|
import java.io.DataInput;
|
||||||
|
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.utility.MinecraftReflection;
|
||||||
|
import com.comphenix.protocol.wrappers.nbt.NbtBase;
|
||||||
|
import com.comphenix.protocol.wrappers.nbt.NbtCompound;
|
||||||
|
import com.comphenix.protocol.wrappers.nbt.NbtFactory;
|
||||||
|
import com.comphenix.protocol.wrappers.nbt.NbtList;
|
||||||
|
import com.comphenix.protocol.wrappers.nbt.NbtWrapper;
|
||||||
|
|
||||||
|
public class NbtBinarySerializer {
|
||||||
|
// Used to read and write NBT
|
||||||
|
private static Method methodWrite;
|
||||||
|
private static Method methodLoad;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve a default instance of the NBT binary serializer.
|
||||||
|
*/
|
||||||
|
public static final NbtBinarySerializer DEFAULT = new NbtBinarySerializer();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Write the content of a wrapped NBT tag to a stream.
|
||||||
|
* @param value - the NBT tag to write.
|
||||||
|
* @param destination - the destination stream.
|
||||||
|
*/
|
||||||
|
public <TType> void serialize(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, NbtFactory.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 <TType> NbtWrapper<TType> deserialize(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 NbtFactory.fromNMS(methodLoad.invoke(null, source));
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new FieldAccessException("Unable to read NBT from " + source, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load an NBT compound from a stream.
|
||||||
|
* @param source - the input stream.
|
||||||
|
* @return An NBT compound.
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("rawtypes")
|
||||||
|
public NbtCompound deserializeCompound(DataInput source) {
|
||||||
|
// I always seem to override generics ...
|
||||||
|
return (NbtCompound) (NbtBase) deserialize(source);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load an NBT list from a stream.
|
||||||
|
* @param source - the input stream.
|
||||||
|
* @return An NBT list.
|
||||||
|
*/
|
||||||
|
@SuppressWarnings({"rawtypes", "unchecked"})
|
||||||
|
public <T> NbtList<T> deserializeList(DataInput source) {
|
||||||
|
return (NbtList<T>) (NbtBase) deserialize(source);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,301 @@
|
|||||||
|
package com.comphenix.protocol.wrappers.nbt.io;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Comparator;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
import org.bukkit.configuration.ConfigurationSection;
|
||||||
|
import org.bukkit.configuration.file.YamlConfiguration;
|
||||||
|
|
||||||
|
import com.comphenix.protocol.wrappers.nbt.NbtBase;
|
||||||
|
import com.comphenix.protocol.wrappers.nbt.NbtCompound;
|
||||||
|
import com.comphenix.protocol.wrappers.nbt.NbtFactory;
|
||||||
|
import com.comphenix.protocol.wrappers.nbt.NbtList;
|
||||||
|
import com.comphenix.protocol.wrappers.nbt.NbtType;
|
||||||
|
import com.comphenix.protocol.wrappers.nbt.NbtVisitor;
|
||||||
|
import com.comphenix.protocol.wrappers.nbt.NbtWrapper;
|
||||||
|
import com.google.common.collect.Lists;
|
||||||
|
import com.google.common.collect.Maps;
|
||||||
|
import com.google.common.primitives.Ints;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Serialize and deserialize NBT information from a configuration section.
|
||||||
|
* <p>
|
||||||
|
* Note that data types may be internally preserved by modifying the serialized name. This may
|
||||||
|
* be visible to the end-user.
|
||||||
|
*
|
||||||
|
* @author Kristian
|
||||||
|
*/
|
||||||
|
public class NbtConfigurationSerializer {
|
||||||
|
/**
|
||||||
|
* The default delimiter that is used to store the data type in YAML.
|
||||||
|
*/
|
||||||
|
public static final String TYPE_DELIMITER = "$";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A standard YAML serializer.
|
||||||
|
*/
|
||||||
|
public static final NbtConfigurationSerializer DEFAULT = new NbtConfigurationSerializer();
|
||||||
|
|
||||||
|
private String dataTypeDelimiter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Construct a serializer using {@link #TYPE_DELIMITER} as the default delimiter.
|
||||||
|
*/
|
||||||
|
public NbtConfigurationSerializer() {
|
||||||
|
this.dataTypeDelimiter = TYPE_DELIMITER;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Construct a serializer using the given value as a delimiter.
|
||||||
|
* @param dataTypeDelimiter - the local data type delimiter.
|
||||||
|
*/
|
||||||
|
public NbtConfigurationSerializer(String dataTypeDelimiter) {
|
||||||
|
this.dataTypeDelimiter = dataTypeDelimiter;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve the current data type delimiter.
|
||||||
|
* @return The current data type delimiter.
|
||||||
|
*/
|
||||||
|
public String getDataTypeDelimiter() {
|
||||||
|
return dataTypeDelimiter;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Write the content of a NBT tag to a configuration section.
|
||||||
|
* @param value - the NBT tag to write.
|
||||||
|
* @param destination - the destination section.
|
||||||
|
*/
|
||||||
|
public <TType> void serialize(NbtBase<TType> value, final ConfigurationSection destination) {
|
||||||
|
value.accept(new NbtVisitor() {
|
||||||
|
private ConfigurationSection current = destination;
|
||||||
|
|
||||||
|
// The current list we're working on
|
||||||
|
private List<Object> currentList;
|
||||||
|
|
||||||
|
// Store the index of a configuration section that works like a list
|
||||||
|
private Map<ConfigurationSection, Integer> workingIndex = Maps.newHashMap();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean visitEnter(NbtCompound compound) {
|
||||||
|
current = current.createSection(compound.getName());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean visitEnter(NbtList<?> list) {
|
||||||
|
Integer listIndex = getNextIndex();
|
||||||
|
String name = getEncodedName(list, listIndex);
|
||||||
|
|
||||||
|
if (list.getElementType().isComposite()) {
|
||||||
|
// Use a configuration section to store this list
|
||||||
|
current = current.createSection(name);
|
||||||
|
workingIndex.put(current, 0);
|
||||||
|
} else {
|
||||||
|
currentList = Lists.newArrayList();
|
||||||
|
current.set(name, currentList);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean visitLeave(NbtCompound compound) {
|
||||||
|
current = current.getParent();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean visitLeave(NbtList<?> list) {
|
||||||
|
// Write the list to the configuration section
|
||||||
|
if (currentList != null) {
|
||||||
|
// Save and reset the temporary list
|
||||||
|
currentList = null;
|
||||||
|
} else {
|
||||||
|
// Go up a level
|
||||||
|
workingIndex.remove(current);
|
||||||
|
current = current.getParent();
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean visit(NbtBase<?> node) {
|
||||||
|
// Are we working on a list?
|
||||||
|
if (currentList == null) {
|
||||||
|
Integer listIndex = getNextIndex();
|
||||||
|
String name = getEncodedName(node, listIndex);
|
||||||
|
|
||||||
|
// Save member
|
||||||
|
current.set(name, node.getValue());
|
||||||
|
|
||||||
|
} else {
|
||||||
|
currentList.add(node.getValue());
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Integer getNextIndex() {
|
||||||
|
Integer listIndex = workingIndex.get(current);
|
||||||
|
|
||||||
|
if (listIndex != null)
|
||||||
|
return workingIndex.put(current, listIndex + 1);
|
||||||
|
else
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// We need to store the data type somehow
|
||||||
|
private String getEncodedName(NbtBase<?> node, Integer index) {
|
||||||
|
if (index != null)
|
||||||
|
return index + dataTypeDelimiter + node.getType().getRawID();
|
||||||
|
else
|
||||||
|
return node.getName() + dataTypeDelimiter + node.getType().getRawID();
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getEncodedName(NbtList<?> node, Integer index) {
|
||||||
|
if (index != null)
|
||||||
|
return index + dataTypeDelimiter + node.getElementType().getRawID();
|
||||||
|
else
|
||||||
|
return node.getName() + dataTypeDelimiter + node.getElementType().getRawID();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read a NBT tag from a root configuration.
|
||||||
|
* @param root - configuration that contains the NBT tag.
|
||||||
|
* @param nodeName - name of the NBT tag.
|
||||||
|
* @return The read NBT tag.
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public <TType> NbtWrapper<TType> deserialize(ConfigurationSection root, String nodeName) {
|
||||||
|
return (NbtWrapper<TType>) readNode(root, nodeName);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read a NBT compound from a root configuration.
|
||||||
|
* @param root - configuration that contains the NBT compound.
|
||||||
|
* @param nodeName - name of the NBT compound.
|
||||||
|
* @return The read NBT compound.
|
||||||
|
*/
|
||||||
|
public NbtCompound deserializeCompound(YamlConfiguration root, String nodeName) {
|
||||||
|
return (NbtCompound) readNode(root, nodeName);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read a NBT compound from a root configuration.
|
||||||
|
* @param root - configuration that contains the NBT compound.
|
||||||
|
* @param nodeName - name of the NBT compound.
|
||||||
|
* @return The read NBT compound.
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public <T> NbtList<T> deserializeList(YamlConfiguration root, String nodeName) {
|
||||||
|
return (NbtList<T>) readNode(root, nodeName);
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
private NbtWrapper<?> readNode(ConfigurationSection parent, String name) {
|
||||||
|
String[] decoded = getDecodedName(name);
|
||||||
|
Object node = parent.get(name);
|
||||||
|
NbtType type = NbtType.TAG_END;
|
||||||
|
|
||||||
|
// It's possible that the caller isn't aware of the encoded name itself
|
||||||
|
if (node == null) {
|
||||||
|
for (String key : parent.getKeys(false)) {
|
||||||
|
decoded = getDecodedName(key);
|
||||||
|
|
||||||
|
// Great
|
||||||
|
if (decoded[0].equals(name)) {
|
||||||
|
node = parent.get(decoded[0]);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Inform the caller of the problem
|
||||||
|
if (node == null) {
|
||||||
|
throw new IllegalArgumentException("Unable to find node " + name + " in " + parent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Attempt to decode a NBT type
|
||||||
|
if (decoded.length > 1) {
|
||||||
|
type = NbtType.getTypeFromID(Integer.parseInt(decoded[1]));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Is this a compound?
|
||||||
|
if (node instanceof ConfigurationSection) {
|
||||||
|
// Is this a list of a map?
|
||||||
|
if (type != NbtType.TAG_END) {
|
||||||
|
NbtList<Object> list = NbtFactory.ofList(decoded[0]);
|
||||||
|
ConfigurationSection section = (ConfigurationSection) node;
|
||||||
|
List<String> sorted = sortSet(section.getKeys(false));
|
||||||
|
|
||||||
|
// Read everything in order
|
||||||
|
for (String key : sorted) {
|
||||||
|
NbtBase<Object> base = (NbtBase<Object>) readNode(section, key.toString());
|
||||||
|
base.setName(NbtList.EMPTY_NAME);
|
||||||
|
list.getValue().add(base);
|
||||||
|
}
|
||||||
|
return (NbtWrapper<?>) list;
|
||||||
|
|
||||||
|
} else {
|
||||||
|
NbtCompound compound = NbtFactory.ofCompound(decoded[0]);
|
||||||
|
ConfigurationSection section = (ConfigurationSection) node;
|
||||||
|
|
||||||
|
// As above
|
||||||
|
for (String key : section.getKeys(false))
|
||||||
|
compound.put(readNode(section, key));
|
||||||
|
return (NbtWrapper<?>) compound;
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
// We need to know
|
||||||
|
if (type == NbtType.TAG_END) {
|
||||||
|
throw new IllegalArgumentException("Cannot find encoded type of " + decoded[0] + " in " + name);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (node instanceof List) {
|
||||||
|
NbtList<Object> list = NbtFactory.ofList(decoded[0]);
|
||||||
|
list.setElementType(type);
|
||||||
|
|
||||||
|
for (Object value : (List<Object>) node) {
|
||||||
|
list.addClosest(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add the list
|
||||||
|
return (NbtWrapper<?>) list;
|
||||||
|
|
||||||
|
} else {
|
||||||
|
// Normal node
|
||||||
|
return NbtFactory.ofWrapper(type, decoded[0], node);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<String> sortSet(Set<String> unsorted) {
|
||||||
|
// Convert to integers
|
||||||
|
List<String> sorted = new ArrayList<String>(unsorted);
|
||||||
|
|
||||||
|
Collections.sort(sorted, new Comparator<String>() {
|
||||||
|
@Override
|
||||||
|
public int compare(String o1, String o2) {
|
||||||
|
// Parse the name
|
||||||
|
int index1 = Integer.parseInt(getDecodedName(o1)[0]);
|
||||||
|
int index2 = Integer.parseInt(getDecodedName(o2)[0]);
|
||||||
|
return Ints.compare(index1, index2);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return sorted;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String[] getDecodedName(String nodeName) {
|
||||||
|
int delimiter = nodeName.lastIndexOf('$');
|
||||||
|
|
||||||
|
if (delimiter > 0)
|
||||||
|
return new String[] { nodeName.substring(0, delimiter), nodeName.substring(delimiter + 1) };
|
||||||
|
else
|
||||||
|
return new String[] { nodeName };
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,119 @@
|
|||||||
|
package com.comphenix.protocol.wrappers.nbt.io;
|
||||||
|
|
||||||
|
import java.io.ByteArrayInputStream;
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.io.DataInputStream;
|
||||||
|
import java.io.DataOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.math.BigInteger;
|
||||||
|
|
||||||
|
import com.comphenix.protocol.wrappers.nbt.NbtBase;
|
||||||
|
import com.comphenix.protocol.wrappers.nbt.NbtCompound;
|
||||||
|
import com.comphenix.protocol.wrappers.nbt.NbtList;
|
||||||
|
import com.comphenix.protocol.wrappers.nbt.NbtWrapper;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Serializes NBT to a base N (default 32) encoded string and back.
|
||||||
|
*
|
||||||
|
* @author Kristian
|
||||||
|
*/
|
||||||
|
public class NbtTextSerializer {
|
||||||
|
/**
|
||||||
|
* The default radix to use while converting to text.
|
||||||
|
*/
|
||||||
|
public static final int STANDARD_BASE = 32;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A default instance of this serializer.
|
||||||
|
*/
|
||||||
|
public static final NbtTextSerializer DEFAULT = new NbtTextSerializer();
|
||||||
|
|
||||||
|
private NbtBinarySerializer binarySerializer;
|
||||||
|
private int baseRadix;
|
||||||
|
|
||||||
|
public NbtTextSerializer() {
|
||||||
|
this(new NbtBinarySerializer(), STANDARD_BASE);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Construct a serializer with a custom binary serializer and base radix.
|
||||||
|
* @param binary - binary serializer.
|
||||||
|
* @param baseRadix - base radix in the range 2 - 32.
|
||||||
|
*/
|
||||||
|
public NbtTextSerializer(NbtBinarySerializer binary, int baseRadix) {
|
||||||
|
this.binarySerializer = binary;
|
||||||
|
this.baseRadix = baseRadix;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve the binary serializer that is used.
|
||||||
|
* @return The binary serializer.
|
||||||
|
*/
|
||||||
|
public NbtBinarySerializer getBinarySerializer() {
|
||||||
|
return binarySerializer;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve the base radix.
|
||||||
|
* @return The base radix.
|
||||||
|
*/
|
||||||
|
public int getBaseRadix() {
|
||||||
|
return baseRadix;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Serialize a NBT tag to a String.
|
||||||
|
* @param value - the NBT tag to serialize.
|
||||||
|
* @return The NBT tag in base N form.
|
||||||
|
*/
|
||||||
|
public <TType> String serialize(NbtBase<TType> value) {
|
||||||
|
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
|
||||||
|
DataOutputStream dataOutput = new DataOutputStream(outputStream);
|
||||||
|
|
||||||
|
binarySerializer.serialize(value, dataOutput);
|
||||||
|
|
||||||
|
// Serialize that array
|
||||||
|
return new BigInteger(1, outputStream.toByteArray()).toString(baseRadix);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deserialize a NBT tag from a base N encoded string.
|
||||||
|
* @param input - the base N string.
|
||||||
|
* @return The NBT tag contained in the string.
|
||||||
|
* @throws IOException If we are unable to parse the input.
|
||||||
|
*/
|
||||||
|
public <TType> NbtWrapper<TType> deserialize(String input) throws IOException {
|
||||||
|
try {
|
||||||
|
BigInteger baseN = new BigInteger(input, baseRadix);
|
||||||
|
ByteArrayInputStream inputStream = new ByteArrayInputStream(baseN.toByteArray());
|
||||||
|
|
||||||
|
return binarySerializer.deserialize(new DataInputStream(inputStream));
|
||||||
|
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
throw new IOException("Input is not valid base " + baseRadix + ".", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deserialize a NBT compound from a base N encoded string.
|
||||||
|
* @param input - the base N string.
|
||||||
|
* @return The NBT tag contained in the string.
|
||||||
|
* @throws IOException If we are unable to parse the input.
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("rawtypes")
|
||||||
|
public NbtCompound deserializeCompound(String input) throws IOException {
|
||||||
|
// I always seem to override generics ...
|
||||||
|
return (NbtCompound) (NbtBase) deserialize(input);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deserialize a NBT list from a base N encoded string.
|
||||||
|
* @param input - the base N string.
|
||||||
|
* @return The NBT tag contained in the string.
|
||||||
|
* @throws IOException If we are unable to parse the input.
|
||||||
|
*/
|
||||||
|
@SuppressWarnings({"rawtypes", "unchecked"})
|
||||||
|
public <T> NbtList<T> deserializeList(String input) throws IOException {
|
||||||
|
return (NbtList<T>) (NbtBase) deserialize(input);
|
||||||
|
}
|
||||||
|
}
|
@ -91,5 +91,10 @@ public class NbtCompoundTest {
|
|||||||
public NbtBase<TValue> deepClone() {
|
public NbtBase<TValue> deepClone() {
|
||||||
return new NbtCustomTag<TValue>(name, value);
|
return new NbtCustomTag<TValue>(name, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean accept(NbtVisitor visitor) {
|
||||||
|
return visitor.visit(this);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -30,6 +30,7 @@ import org.junit.BeforeClass;
|
|||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
import com.comphenix.protocol.utility.MinecraftReflection;
|
import com.comphenix.protocol.utility.MinecraftReflection;
|
||||||
|
import com.comphenix.protocol.wrappers.nbt.io.NbtBinarySerializer;
|
||||||
|
|
||||||
public class NbtFactoryTest {
|
public class NbtFactoryTest {
|
||||||
@BeforeClass
|
@BeforeClass
|
||||||
@ -54,7 +55,7 @@ public class NbtFactoryTest {
|
|||||||
ByteArrayInputStream source = new ByteArrayInputStream(buffer.toByteArray());
|
ByteArrayInputStream source = new ByteArrayInputStream(buffer.toByteArray());
|
||||||
DataInput input = new DataInputStream(source);
|
DataInput input = new DataInputStream(source);
|
||||||
|
|
||||||
NbtCompound cloned = (NbtCompound) NbtFactory.fromStream(input);
|
NbtCompound cloned = NbtBinarySerializer.DEFAULT.deserializeCompound(input);
|
||||||
|
|
||||||
assertEquals(compound.getString("name"), cloned.getString("name"));
|
assertEquals(compound.getString("name"), cloned.getString("name"));
|
||||||
assertEquals(compound.getInteger("age"), cloned.getInteger("age"));
|
assertEquals(compound.getInteger("age"), cloned.getInteger("age"));
|
||||||
|
@ -0,0 +1,37 @@
|
|||||||
|
package com.comphenix.protocol.wrappers.nbt.io;
|
||||||
|
|
||||||
|
import static org.junit.Assert.*;
|
||||||
|
|
||||||
|
import org.bukkit.configuration.file.YamlConfiguration;
|
||||||
|
import org.junit.BeforeClass;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import com.comphenix.protocol.utility.MinecraftReflection;
|
||||||
|
import com.comphenix.protocol.wrappers.nbt.NbtCompound;
|
||||||
|
import com.comphenix.protocol.wrappers.nbt.NbtFactory;
|
||||||
|
|
||||||
|
public class NbtConfigurationSerializerTest {
|
||||||
|
@BeforeClass
|
||||||
|
public static void initializeBukkit() {
|
||||||
|
// Initialize reflection
|
||||||
|
MinecraftReflection.setMinecraftPackage("net.minecraft.server.v1_4_6", "org.bukkit.craftbukkit.v1_4_6");
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
@Test
|
||||||
|
public void testSerialization() {
|
||||||
|
NbtCompound compound = NbtFactory.ofCompound("hello");
|
||||||
|
compound.put("age", (short) 30);
|
||||||
|
compound.put("name", "test");
|
||||||
|
compound.put(NbtFactory.ofList("telephone", "12345678", "81549300"));
|
||||||
|
|
||||||
|
compound.put(NbtFactory.ofList("lists", NbtFactory.ofList("", "a", "a", "b", "c")));
|
||||||
|
|
||||||
|
YamlConfiguration yaml = new YamlConfiguration();
|
||||||
|
NbtConfigurationSerializer.DEFAULT.serialize(compound, yaml);
|
||||||
|
|
||||||
|
NbtCompound result = NbtConfigurationSerializer.DEFAULT.deserializeCompound(yaml, "hello");
|
||||||
|
|
||||||
|
assertEquals(compound, result);
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user