Add NBT library by Eisenwave

This commit is contained in:
filoghost 2018-11-21 18:35:10 +01:00
parent 88e9b13741
commit 7220e1e4f8
16 changed files with 1872 additions and 0 deletions

View File

@ -0,0 +1,58 @@
package com.gmail.filoghost.chestcommands.util.nbt;
/**
* The {@code TAG_Byte} tag.
*/
public final class NBTByte extends NBTTag implements Cloneable {
private byte value;
public NBTByte(byte value) {
this.value = value;
}
@Override
public Byte getValue() {
return value;
}
public byte getByteValue() {
return value;
}
public void setByteValue(byte value) {
this.value = value;
}
@Override
public NBTType getType() {
return NBTType.BYTE;
}
// MISC
@Override
public boolean equals(Object obj) {
return obj instanceof NBTByte && equals((NBTByte) obj);
}
public boolean equals(NBTByte tag) {
return this.value == tag.value;
}
@Override
public int hashCode() {
return Byte.hashCode(value);
}
@Override
public String toMSONString() {
return Byte.toUnsignedInt(value)+"b";
}
@Override
public NBTByte clone() {
return new NBTByte(value);
}
}

View File

@ -0,0 +1,64 @@
package com.gmail.filoghost.chestcommands.util.nbt;
import java.util.Arrays;
/**
* The {@code TAG_Byte_Array} tag.
*/
public final class NBTByteArray extends NBTTag {
private final byte[] value;
public NBTByteArray(byte[] value) {
this.value = value;
}
public NBTByteArray(Number[] numbers) {
this.value = new byte[numbers.length];
for (int i = 0; i < numbers.length; i++)
value[i] = numbers[i].byteValue();
}
/**
* Returns the length of this array.
*
* @return the length of this array
*/
public int length() {
return value.length;
}
@Override
public byte[] getValue() {
return value;
}
@Override
public NBTType getType() {
return NBTType.BYTE_ARRAY;
}
// MISC
@Override
public boolean equals(Object obj) {
return obj instanceof NBTByteArray && equals((NBTByteArray) obj);
}
public boolean equals(NBTByteArray tag) {
return Arrays.equals(this.value, tag.value);
}
@Override
public String toMSONString() {
StringBuilder stringbuilder = new StringBuilder("[B;");
for (int i = 0; i < this.value.length; i++) {
if (i != 0) {
stringbuilder.append(',');
}
stringbuilder.append(this.value[i]).append('B');
}
return stringbuilder.append(']').toString();
}
}

View File

@ -0,0 +1,436 @@
package com.gmail.filoghost.chestcommands.util.nbt;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Objects;
import java.util.Set;
import java.util.function.BiConsumer;
import java.util.regex.Pattern;
/**
* The {@code TAG_Compound} tag.
*/
public final class NBTCompound extends NBTTag {
private static final Pattern SIMPLE_STRING = Pattern.compile("[A-Za-z0-9._+-]+");
private final Map<String, NBTTag> value;
public NBTCompound(Map<String, NBTTag> value) {
this.value = new LinkedHashMap<String, NBTTag>(value);
}
public NBTCompound() {
this.value = new LinkedHashMap<String, NBTTag>();
}
// GETTERS
/**
* Returns the size of this compound.
*
* @return the size of this compound
*/
public int size() {
return value.size();
}
@Override
public Map<String, NBTTag> getValue() {
return value;
}
@Override
public NBTType getType() {
return NBTType.COMPOUND;
}
/**
* Returns a tag named with the given key.
*
* @param key the key
* @return a byte
* @throws NoSuchElementException if there is no tag with given name
*/
public NBTTag getTag(String key) {
if (!hasKey(key)) throw new NoSuchElementException(key);
return value.get(key);
}
/**
* Returns a byte named with the given key.
*
* @param key the key
* @return a byte
* @throws NoSuchElementException if there is no byte with given name
*/
public byte getByte(String key) {
NBTTag tag = value.get(key);
if (!(tag instanceof NBTByte)) throw new NoSuchElementException(key);
return ((NBTByte) tag).getValue();
}
/**
* Returns an short named with the given key.
*
* @param key the key
* @return an short
* @throws NoSuchElementException if there is no short with given name
*/
public short getShort(String key) {
NBTTag tag = value.get(key);
if (!(tag instanceof NBTShort)) throw new NoSuchElementException(key);
return ((NBTShort) tag).getValue();
}
/**
* Returns an int named with the given key.
*
* @param key the key
* @return an int
* @throws NoSuchElementException if there is no int with given name
*/
public int getInt(String key) {
NBTTag tag = value.get(key);
if (!(tag instanceof NBTInt)) throw new NoSuchElementException(key);
return ((NBTInt) tag).getValue();
}
/**
* Returns an long named with the given key.
*
* @param key the key
* @return an long
* @throws NoSuchElementException if there is no long with given name
*/
public long getLong(String key) {
NBTTag tag = value.get(key);
if (!(tag instanceof NBTLong)) throw new NoSuchElementException(key);
return ((NBTLong) tag).getValue();
}
/**
* Returns float named with the given key.
*
* @param key the key
* @return a float
* @throws NoSuchElementException if there is no float with given name
*/
public float getFloat(String key) {
NBTTag tag = value.get(key);
if (!(tag instanceof NBTFloat)) throw new NoSuchElementException(key);
return ((NBTFloat) tag).getValue();
}
/**
* Returns a double named with the given key.
*
* @param key the key
* @return a double
* @throws NoSuchElementException if there is no int with given name
*/
public double getDouble(String key) {
NBTTag tag = value.get(key);
if (!(tag instanceof NBTDouble)) throw new NoSuchElementException(key);
return ((NBTDouble) tag).getValue();
}
/**
* Returns a byte array named with the given key.
*
* @param key the key
* @return a byte array
* @throws NoSuchElementException if there is no int with given name
*/
public byte[] getByteArray(String key) {
NBTTag tag = value.get(key);
if (!(tag instanceof NBTByteArray)) throw new NoSuchElementException(key);
return ((NBTByteArray) tag).getValue();
}
/**
* Returns a string named with the given key.
*
* @param key the key
* @return a string
* @throws NoSuchElementException if there is no int with given name
*/
public String getString(String key) {
NBTTag tag = value.get(key);
if (!(tag instanceof NBTString)) throw new NoSuchElementException(key);
return ((NBTString) tag).getValue();
}
/**
* Returns a list named with the given key.
*
* @param key the key
* @return a list
* @throws NoSuchElementException if there is no int with given name
*/
public List<NBTTag> getList(String key) {
return getTagList(key).getValue();
}
/**
* Returns a list named with the given key.
*
* @param key the key
* @return a list
* @throws NoSuchElementException if there is no list with given name
*/
public NBTList getTagList(String key) {
NBTTag tag = value.get(key);
if (!(tag instanceof NBTList)) throw new NoSuchElementException(key);
return (NBTList) tag;
}
/**
* Returns a list named with the given key.
*
* @param key the key
* @return a list
* @throws NoSuchElementException if there is no compound with given name
*/
public Map<String, NBTTag> getCompound(String key) {
return getCompoundTag(key).getValue();
}
/**
* Returns a compound named with the given key.
*
* @param key the key
* @return a compound
* @throws NoSuchElementException if there is no compound with given name
*/
public NBTCompound getCompoundTag(String key) {
NBTTag tag = value.get(key);
if (!(tag instanceof NBTCompound)) throw new NoSuchElementException(key);
return (NBTCompound) tag;
}
/**
* Returns an int array named with the given key.
*
* @param key the key
* @return a int array
* @throws NoSuchElementException if there is no int array with given name
*/
public int[] getIntArray(String key) {
NBTTag tag = value.get(key);
if (!(tag instanceof NBTIntArray)) throw new NoSuchElementException(key);
return ((NBTIntArray) tag).getValue();
}
/**
* Returns a long array named with the given key.
*
* @param key the key
* @return a int array
* @throws NoSuchElementException if there is no int array with given name
*/
public long[] getLongArray(String key) {
NBTTag tag = value.get(key);
if (!(tag instanceof NBTLongArray)) throw new NoSuchElementException(key);
return ((NBTLongArray) tag).getValue();
}
/**
* Returns an immutable set containing all the keys in this compound.
*
* @return an immutable set
*/
public Set<String> getKeys() {
return Collections.unmodifiableSet(value.keySet());
}
// PREDICATES
/**
* Returns whether this compound is empty.
*
* @return whether this compound is empty
*/
public boolean isEmpty() {
return value.isEmpty();
}
/**
* Returns whether this compound tag contains the given key.
*
* @param key the given key
* @return true if the tag contains the given key
*/
public boolean hasKey(String key) {
return value.containsKey(key);
}
/**
* Returns whether this compound tag contains the given key and its value is of a given type.
*
* @param key the given key
* @param type the type of the value
* @return true if the tag contains an entry with given key and of given type
*/
public boolean hasKeyOfType(String key, NBTType type) {
Objects.requireNonNull(type);
return value.containsKey(key) && value.get(key).getType() == type;
}
// MUTATORS
/**
* Put the given name and its corresponding tag into the compound tag.
*
* @param name the tag name
* @param tag the tag value
*/
public void put(String name, NBTTag tag) {
this.value.put(name, tag);
}
/**
* Put the given key and value into the compound tag.
*
* @param key they key
* @param value the value
*/
public void putByteArray(String key, byte[] value) {
put(key, new NBTByteArray(value));
}
/**
* Put the given key and value into the compound tag.
*
* @param key they key
* @param value the value
*/
public void putByte(String key, byte value) {
put(key, new NBTByte(value));
}
/**
* Put the given key and value into the compound tag.
*
* @param key they key
* @param value the value
*/
public void putDouble(String key, double value) {
put(key, new NBTDouble(value));
}
/**
* Put the given key and value into the compound tag.
*
* @param key they key
* @param value the value
*/
public void putFloat(String key, float value) {
put(key, new NBTFloat(value));
}
/**
* Put the given key and value into the compound tag.
*
* @param key they key
* @param value the value
*/
public void putIntArray(String key, int[] value) {
put(key, new NBTIntArray(value));
}
/**
* Put the given key and value into the compound tag.
*
* @param key they key
* @param value the value
*/
public void putLongArray(String key, long[] value) {
put(key, new NBTLongArray(value));
}
/**
* Put the given key and value into the compound tag.
*
* @param key they key
* @param value the valu
*/
public void putInt(String key, int value) {
put(key, new NBTInt(value));
}
/**
* Put the given key and value into the compound tag.
*
* @param key they key
* @param value the value
*/
public void putLong(String key, long value) {
put(key, new NBTLong(value));
}
/**
* Put the given key and value into the compound tag.
*
* @param key they key
* @param value the value
*/
public void putShort(String key, short value) {
put(key, new NBTShort(value));
}
/**
* Put the given key and value into the compound tag.
*
* @param key they key
* @param value the value
*/
public void putString(String key, String value) {
put(key, new NBTString(value));
}
// ITERATION
/**
* Performs an action for every pair of keys and tags.
*
* @param action the action
*/
public void forEach(BiConsumer<String, ? super NBTTag> action) {
this.value.forEach(action);
}
// MISC
@Override
public boolean equals(Object obj) {
return obj instanceof NBTCompound && equals((NBTCompound) obj);
}
public boolean equals(NBTCompound tag) {
return this.isEmpty() && tag.isEmpty()
|| this.value.equals(tag.value);
}
@Override
public String toMSONString() {
StringBuilder builder = new StringBuilder("{");
Set<String> keys = this.value.keySet();
for (String key : keys) {
if (builder.length() > 1) {
builder.append(',');
}
builder
.append(SIMPLE_STRING.matcher(key).matches()? key : NBTString.toMSONString(key))
.append(':')
.append(this.value.get(key).toMSONString());
}
return builder.append("}").toString();
}
}

View File

@ -0,0 +1,59 @@
package com.gmail.filoghost.chestcommands.util.nbt;
/**
* The {@code TAG_Double} tag.
*
*/
public final class NBTDouble extends NBTTag implements Cloneable {
private double value;
public NBTDouble(double value) {
this.value = value;
}
@Override
public Double getValue() {
return value;
}
public double getDoubleValue() {
return value;
}
public void setDoubleValue(double value) {
this.value = value;
}
@Override
public NBTType getType() {
return NBTType.DOUBLE;
}
// MISC
@Override
public boolean equals(Object obj) {
return obj instanceof NBTDouble && equals((NBTDouble) obj);
}
public boolean equals(NBTDouble tag) {
return this.value == tag.value;
}
@Override
public int hashCode() {
return Double.hashCode(value);
}
@Override
public String toMSONString() {
return value+"d";
}
@Override
public NBTDouble clone() {
return new NBTDouble(value);
}
}

View File

@ -0,0 +1,58 @@
package com.gmail.filoghost.chestcommands.util.nbt;
/**
* The {@code TAG_Float} tag.
*/
public final class NBTFloat extends NBTTag implements Cloneable {
private float value;
public NBTFloat(float value) {
this.value = value;
}
@Override
public Float getValue() {
return value;
}
public float getFloatValue() {
return value;
}
public void setFloatValue(float value) {
this.value = value;
}
@Override
public NBTType getType() {
return NBTType.FLOAT;
}
// MISC
@Override
public boolean equals(Object obj) {
return obj instanceof NBTFloat && equals((NBTFloat) obj);
}
public boolean equals(NBTFloat tag) {
return this.value == tag.value;
}
@Override
public int hashCode() {
return Float.hashCode(value);
}
@Override
public String toMSONString() {
return value+"f";
}
@Override
public NBTFloat clone() {
return new NBTFloat(value);
}
}

View File

@ -0,0 +1,58 @@
package com.gmail.filoghost.chestcommands.util.nbt;
/**
* The {@code TAG_Int} tag.
*/
public final class NBTInt extends NBTTag implements Cloneable {
private int value;
public NBTInt(int value) {
this.value = value;
}
@Override
public Integer getValue() {
return value;
}
public int getIntValue() {
return value;
}
public void setIntValue(int value) {
this.value = value;
}
@Override
public NBTType getType() {
return NBTType.INT;
}
// MISC
@Override
public boolean equals(Object obj) {
return obj instanceof NBTInt && equals((NBTInt) obj);
}
public boolean equals(NBTInt tag) {
return this.value == tag.value;
}
@Override
public int hashCode() {
return Integer.hashCode(value);
}
@Override
public String toMSONString() {
return Integer.toString(value);
}
@Override
public NBTInt clone() {
return new NBTInt(value);
}
}

View File

@ -0,0 +1,75 @@
package com.gmail.filoghost.chestcommands.util.nbt;
import java.util.Arrays;
import java.util.Objects;
/**
* The {@code TAG_Int_Array} tag.
*/
public final class NBTIntArray extends NBTTag implements Cloneable {
private final int[] value;
/**
* Creates the tag with an empty name.
*
* @param value the value of the tag
*/
public NBTIntArray(int[] value) {
this.value = Objects.requireNonNull(value);
}
public NBTIntArray(Number[] numbers) {
this.value = new int[numbers.length];
for (int i = 0; i < numbers.length; i++)
value[i] = numbers[i].intValue();
}
/**
* Returns the length of this array.
*
* @return the length of this array
*/
public int length() {
return value.length;
}
@Override
public int[] getValue() {
return value;
}
@Override
public NBTType getType() {
return NBTType.INT_ARRAY;
}
// MISC
@Override
public boolean equals(Object obj) {
return obj instanceof NBTIntArray && equals((NBTIntArray) obj);
}
public boolean equals(NBTIntArray tag) {
return Arrays.equals(this.value, tag.value);
}
@Override
public String toMSONString() {
StringBuilder stringbuilder = new StringBuilder("[I;");
for (int i = 0; i < this.value.length; i++) {
if (i != 0) {
stringbuilder.append(',');
}
stringbuilder.append(this.value[i]);
}
return stringbuilder.append(']').toString();
}
@Override
public NBTIntArray clone() {
return new NBTIntArray(value);
}
}

View File

@ -0,0 +1,183 @@
package com.gmail.filoghost.chestcommands.util.nbt;
import java.util.*;
/**
* The {@code TAG_List} tag.
*/
public final class NBTList extends NBTTag implements Iterable<NBTTag>, Cloneable {
private NBTType type;
private final List<NBTTag> list = new ArrayList<NBTTag>();
/**
* Creates the list with a type and a series of elements.
*
* @param type the type of tag
* @param value the value of the tag
*/
public NBTList(NBTType type, List<? extends NBTTag> value) {
this.type = type;
for (NBTTag entry : value) {
this.add(entry);
}
}
/**
* Creates the list with a type and a series of elements.
*
* @param type the type of tag
* @param value the value of the tag
*/
public NBTList(NBTType type, NBTTag... value) {
this(type, Arrays.asList(value));
}
/**
* Creates an empty list with a type.
*
* @param type the type of tag or null if the list has no type yet
*/
public NBTList(NBTType type) {
this.type = type;
}
/**
* Creates an empty list without a type.
*/
public NBTList() {
this(null);
}
// GETTERS
/**
* Returns the size of this list.
*
* @return the size of this list
*/
public int size() {
return list.size();
}
@Override
public List<NBTTag> getValue() {
return list;
}
@Override
public NBTType getType() {
return NBTType.LIST;
}
/**
* Gets the type of elements in this list.
*
* @return The type of elements in this list.
*/
public NBTType getElementType() {
return type;
}
/**
* Returns a tag named with the given index.
*
* @param index the index
* @return a byte
* @throws NoSuchElementException if there is no tag with given index
*/
public NBTTag get(int index) {
return list.get(index);
}
// PREDICATES
/**
* Returns whether this list is empty.
*
* @return whether this list is empty
*/
public boolean isEmpty() {
return list.isEmpty();
}
// MUTATORS
/**
* Add the given tag.
*
* @param value the tag
*/
public void add(NBTTag value) {
if (this.type == null)
this.type = value.getType();
else if (this.type != value.getType())
throw new IllegalArgumentException(value.getType() + " is not of expected type " + type);
list.add(value);
}
/**
* Add the given tag at the given index in the list.
*
* @param value the tag
*/
public void add(int index, NBTTag value) {
if (index < 0 || index >= list.size())
throw new IndexOutOfBoundsException(Integer.toString(index));
if (this.type == null)
this.type = value.getType();
else if (this.type != value.getType())
throw new IllegalArgumentException(value.getType() + " is not of expected type " + type);
list.add(index, value);
}
/**
* Add all the tags in the given list.
*
* @param values a list of tags
*/
public void addAll(Collection<? extends NBTTag> values) {
for (NBTTag entry : values) {
this.add(entry);
}
}
// MISC
@Override
public boolean equals(Object obj) {
return obj instanceof NBTList && equals((NBTList) obj);
}
public boolean equals(NBTList tag) {
return this.isEmpty() && tag.isEmpty()
|| this.type == tag.type && this.list.equals(tag.list);
}
@Override
public Iterator<NBTTag> iterator() {
return list.iterator();
}
@Override
public String toMSONString() {
StringBuilder builder = new StringBuilder("[");
Iterator<NBTTag> iter = iterator();
boolean first = true;
while (iter.hasNext()) {
if (first) first = false;
else builder.append(',');
builder.append(iter.next().toMSONString());
}
return builder.append("]").toString();
}
@Override
public NBTList clone() {
return new NBTList(type, list);
}
}

View File

@ -0,0 +1,58 @@
package com.gmail.filoghost.chestcommands.util.nbt;
/**
* The {@code TAG_Long} tag.
*/
public final class NBTLong extends NBTTag implements Cloneable {
private long value;
public NBTLong(long value) {
this.value = value;
}
@Override
public Long getValue() {
return value;
}
public long getLongValue() {
return value;
}
public void setLongValue(long value) {
this.value = value;
}
@Override
public NBTType getType() {
return NBTType.LONG;
}
// MISC
@Override
public boolean equals(Object obj) {
return obj instanceof NBTLong && equals((NBTLong) obj);
}
public boolean equals(NBTLong tag) {
return this.value == tag.value;
}
@Override
public int hashCode() {
return Long.hashCode(value);
}
@Override
public String toMSONString() {
return value+"L";
}
@Override
public NBTLong clone() {
return new NBTLong(value);
}
}

View File

@ -0,0 +1,69 @@
package com.gmail.filoghost.chestcommands.util.nbt;
import java.util.Arrays;
/**
* The {@code TAG_Long_Array} tag.
*/
public final class NBTLongArray extends NBTTag {
private final long[] value;
/**
* Creates the tag with an empty name.
*
* @param value the value of the tag
*/
public NBTLongArray(long... value) {
this.value = value;
}
public NBTLongArray(Number[] numbers) {
this.value = new long[numbers.length];
for (int i = 0; i < numbers.length; i++)
value[i] = numbers[i].longValue();
}
/**
* Returns the length of this array.
*
* @return the length of this array
*/
public int length() {
return value.length;
}
@Override
public long[] getValue() {
return value;
}
@Override
public NBTType getType() {
return NBTType.LONG_ARRAY;
}
// MISC
@Override
public boolean equals(Object obj) {
return obj instanceof NBTLongArray && equals((NBTLongArray) obj);
}
public boolean equals(NBTLongArray tag) {
return Arrays.equals(this.value, tag.value);
}
@Override
public String toMSONString() {
StringBuilder stringbuilder = new StringBuilder("[I;");
for (int i = 0; i < this.value.length; i++) {
if (i != 0) {
stringbuilder.append(',');
}
stringbuilder.append(this.value[i]);
}
return stringbuilder.append(']').toString();
}
}

View File

@ -0,0 +1,58 @@
package com.gmail.filoghost.chestcommands.util.nbt;
/**
* The {@code TAG_Short} tag.
*/
public final class NBTShort extends NBTTag implements Cloneable {
private short value;
public NBTShort(short value) {
this.value = value;
}
@Override
public Short getValue() {
return value;
}
public short getShortValue() {
return value;
}
public void setShortValue(short value) {
this.value = value;
}
@Override
public NBTType getType() {
return NBTType.SHORT;
}
// MISC
@Override
public boolean equals(Object obj) {
return obj instanceof NBTShort && equals((NBTShort) obj);
}
public boolean equals(NBTShort tag) {
return this.value == tag.value;
}
@Override
public int hashCode() {
return Short.hashCode(value);
}
@Override
public String toMSONString() {
return value+"s";
}
@Override
public NBTShort clone() {
return new NBTShort(value);
}
}

View File

@ -0,0 +1,66 @@
package com.gmail.filoghost.chestcommands.util.nbt;
/**
* The {@code TAG_String} tag.
*/
public final class NBTString extends NBTTag implements Cloneable {
private String value;
public NBTString(String value) {
setValue(value);
}
@Override
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
@Override
public NBTType getType() {
return NBTType.STRING;
}
// MISC
@Override
public int hashCode() {
return value.hashCode();
}
@Override
public String toMSONString() {
return toMSONString(value);
}
@Override
public NBTString clone() {
return new NBTString(value);
}
// UTIL
/**
* Converts a regular string into a Mojangson string by surrounding it with quotes and escaping backslashes and
* quotes inside it.
*
* @param str the string
* @return the Mojangson string
*/
public static String toMSONString(String str) {
StringBuilder builder = new StringBuilder("\"");
char[] chars = str.toCharArray();
for (char c : chars) {
if ((c == '\\') || (c == '"')) {
builder.append('\\');
}
builder.append(c);
}
return builder.append('\"').toString();
}
}

View File

@ -0,0 +1,60 @@
package com.gmail.filoghost.chestcommands.util.nbt;
/**
* An abstract NBT-Tag.
*/
public abstract class NBTTag {
/**
* Gets the value of this tag.
*
* @return the value of this tag
*/
public abstract Object getValue();
/**
* Returns the type of this tag.
*
* @return the type of this tag
*/
public abstract NBTType getType();
/**
* Convenience method for getting the id of this tag's type.
*
* @return the type id
*/
public byte getTypeId() {
return getType().getId();
}
/**
* Returns a Mojangson string depicting this NBT tag.
*
* @return a Mojangson string depicting this NBT tag
*/
public abstract String toMSONString();
// MISC
@Override
public boolean equals(Object obj) {
if (obj instanceof NBTTag) {
NBTTag tag = (NBTTag) obj;
return this.getType() == tag.getType()
&& this.getValue().equals(tag.getValue());
}
return false;
}
@Override
public int hashCode() {
return getValue().hashCode();
}
@Override
public String toString() {
return toMSONString();
}
}

View File

@ -0,0 +1,179 @@
package com.gmail.filoghost.chestcommands.util.nbt;
/**
* <p>
* The type of an NBTTag.
* </p>
* <p>
* This enum may be prone to further additions, such as the {@link #LONG_ARRAY} which has been added by Mojang
* in NBT Version 19133. (second NBT version)
* </p>
* <p>
* For a community maintained documentation of the NBT format and its types, visit the
* <a href=https://minecraft.gamepedia.com/NBT_format>Minecraft Wiki</a>
* </p>
*/
public enum NBTType {
/**
* Used to mark the end of compounds tags. May also be the type of empty list tags.
* @since NBT Version 19132
*/
END("TAG_End", false, false, false),
/**
* A signed integer (8 bits). Sometimes used for booleans. (-128 to 127)
* @since NBT Version 19132
*/
BYTE("TAG_Byte", true, true, false),
/**
* A signed integer (16 bits). (-2<sup>15</sup> to 2<sup>15</sup>-1)
* @since NBT Version 19132
*/
SHORT("TAG_Short", true, true, false),
/**
* A signed integer (32 bits). (-2<sup>31</sup> to 2<sup>31</sup>-1)
* @since NBT Version 19132
*/
INT("TAG_Int", true, true, false),
/**
* A signed integer (64 bits). (-2<sup>63</sup> to 2<sup>63</sup>-1)
* @since NBT Version 19132
*/
LONG("TAG_Long", true, true, false),
/**
* A signed (IEEE 754-2008) floating point number (32 bits).
* @since NBT Version 19132
*/
FLOAT("TAG_Float", true, true, false),
/**
* A signed (IEEE 754-2008) floating point number (64 bits).
* @since NBT Version 19132
*/
DOUBLE("TAG_Double", true, true, false),
/**
* An array of {@link #BYTE} with maximum length of {@link Integer#MAX_VALUE}.
* @since NBT Version 19132
*/
BYTE_ARRAY("TAG_Byte_Array", false, false, true),
/**
* UTF-8 encoded string.
* @since NBT Version 19132
*/
STRING("TAG_String", true, false, false),
/**
* A list of unnamed tags of equal type.
* @since NBT Version 19132
*/
LIST("TAG_List", false, false, false),
/**
* Compound of named tags followed by {@link #END}.
* @since NBT Version 19132
*/
COMPOUND("TAG_Compound", false, false, false),
/**
* An array of {@link #BYTE} with maximum length of {@link Integer#MAX_VALUE}.
* @since NBT Version 19132
*/
INT_ARRAY("TAG_Int_Array", false, false, true),
/**
* An array of {@link #LONG} with maximum length of {@link Integer#MAX_VALUE}.
* @since NBT Version 19133
*/
LONG_ARRAY("TAG_Long_Array", false, false, true);
private final String name;
private final boolean numeric, primitive, array;
private final byte id;
NBTType(String name, boolean primitive, boolean numeric, boolean array) {
this.name = name;
this.id = (byte) ordinal();
this.numeric = numeric;
this.primitive = primitive;
this.array = array;
}
/**
* Returns the type with the given id.
*
* @param id the id
* @return the type
*/
public static NBTType getById(byte id) {
return values()[id];
}
/**
* <p>
* Returns the id of this tag type.
* </p>
* <p>
* Although this method is currently equivalent to {@link #ordinal()}, it should always be used in its stead,
* since it is not guaranteed that this behavior will remain consistent.
* </p>
*
* @return the id
*/
public byte getId() {
return id;
}
/**
* Returns the name of this type.
*
* @return the name
*/
public String getName() {
return name;
}
/**
* <p>
* Returns whether this tag type is numeric.
* </p>
* <p>
* All tag types with payloads that are representable as a {@link Number} are compliant with this definition.
* </p>
*
* @return whether this type is numeric
*/
public boolean isNumeric() {
return numeric;
}
/**
* Returns whether this tag type is primitive, meaning that it is not a {@link NBTByteArray}, {@link NBTIntArray},
* {@link NBTList}, {@link NBTCompound} or {@link NBTEnd}.
*
* @return whether this type is numeric
*/
public boolean isPrimitive() {
return primitive;
}
/**
* Returns whether this tag type is is an array type such as {@link NBTByteArray} or {@link NBTIntArray}.
*
* @return whether this type is an array type
*/
public boolean isArray() {
return array;
}
@Override
public String toString() {
return getName();
}
}

View File

@ -0,0 +1,27 @@
package com.gmail.filoghost.chestcommands.util.nbt.parser;
import java.io.IOException;
import org.bukkit.ChatColor;
public class MojangsonParseException extends IOException {
private static final long serialVersionUID = 1L;
public MojangsonParseException(String msg, String content, int index) {
super(msg + " at character " + index + ": " + printErrorLoc(content, index));
}
private static String printErrorLoc(String content, int index) {
StringBuilder builder = new StringBuilder();
int i = Math.min(content.length(), index);
if (i > 35) {
builder.append("...");
}
builder.append(content.substring(Math.max(0, i - 35), i));
builder.append(ChatColor.GOLD + "<--[HERE]");
return builder.toString();
}
}

View File

@ -0,0 +1,364 @@
package com.gmail.filoghost.chestcommands.util.nbt.parser;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Pattern;
import com.gmail.filoghost.chestcommands.util.nbt.*;
public final class MojangsonParser {
private static final Pattern
DOUBLE_NS = Pattern.compile("[-+]?(?:[0-9]+[.]|[0-9]*[.][0-9]+)(?:e[-+]?[0-9]+)?", Pattern.CASE_INSENSITIVE),
DOUBLE_S = Pattern.compile("[-+]?(?:[0-9]+[.]?|[0-9]*[.][0-9]+)(?:e[-+]?[0-9]+)?d", Pattern.CASE_INSENSITIVE),
FLOAT = Pattern.compile("[-+]?(?:[0-9]+[.]?|[0-9]*[.][0-9]+)(?:e[-+]?[0-9]+)?f", Pattern.CASE_INSENSITIVE),
BYTE = Pattern.compile("[-+]?(?:0|[1-9][0-9]*)b", Pattern.CASE_INSENSITIVE),
LONG = Pattern.compile("[-+]?(?:0|[1-9][0-9]*)l", Pattern.CASE_INSENSITIVE),
SHORT = Pattern.compile("[-+]?(?:0|[1-9][0-9]*)s", Pattern.CASE_INSENSITIVE),
INT = Pattern.compile("[-+]?(?:0|[1-9][0-9]*)");
private final String str;
private int index;
public static NBTCompound parse(String mson) throws MojangsonParseException {
return new MojangsonParser(mson).parseRootCompound();
}
private MojangsonParser(String str) {
this.str = str;
}
// PARSE
private NBTCompound parseRootCompound() throws MojangsonParseException {
skipWhitespace();
NBTCompound result = parseCompound();
expectNoTrail();
return result;
}
private String parseCompoundKey() throws MojangsonParseException {
skipWhitespace();
if (!hasNext()) {
throw parseException("Expected key");
}
return currentChar() == '"' ? parseQuotedString() : parseSimpleString();
}
private NBTTag parseStringOrLiteral() throws MojangsonParseException {
skipWhitespace();
if (currentChar() == '"')
return new NBTString(parseQuotedString());
String str = parseSimpleString();
if (str.isEmpty())
throw parseException("Expected value");
return parseLiteral(str);
}
private NBTTag parseLiteral(String str) {
try {
if (FLOAT.matcher(str).matches()) {
return new NBTFloat(Float.parseFloat(str.substring(0, str.length() - 1)));
}
if (BYTE.matcher(str).matches()) {
return new NBTByte(Byte.parseByte(str.substring(0, str.length() - 1)));
}
if (LONG.matcher(str).matches()) {
return new NBTLong(Long.parseLong(str.substring(0, str.length() - 1)));
}
if (SHORT.matcher(str).matches()) {
return new NBTShort(Short.parseShort(str.substring(0, str.length() - 1)));
}
if (INT.matcher(str).matches()) {
return new NBTInt(Integer.parseInt(str));
}
if (DOUBLE_S.matcher(str).matches()) {
return new NBTDouble(Double.parseDouble(str.substring(0, str.length() - 1)));
}
if (DOUBLE_NS.matcher(str).matches()) {
return new NBTDouble(Double.parseDouble(str));
}
if ("true".equalsIgnoreCase(str)) {
return new NBTByte((byte)1);
}
if ("false".equalsIgnoreCase(str)) {
return new NBTByte((byte)0);
}
}
catch (NumberFormatException ex) {
return new NBTString(str);
}
return new NBTString(str);
}
private String parseQuotedString() throws MojangsonParseException {
int j = ++this.index;
StringBuilder builder = null;
boolean escape = false;
while (hasNext()) {
char c = nextChar();
if (escape) {
if ((c != '\\') && (c != '"')) {
throw parseException("Invalid escape of '" + c + "'");
}
escape = false;
}
else {
if (c == '\\') {
escape = true;
if (builder != null) {
continue;
}
builder = new StringBuilder(this.str.substring(j, this.index - 1)); continue;
}
if (c == '"') {
return builder == null ? this.str.substring(j, this.index - 1) : builder.toString();
}
}
if (builder != null) {
builder.append(c);
}
}
throw parseException("Missing termination quote");
}
private String parseSimpleString() {
int j = this.index;
while (hasNext() && isSimpleChar(currentChar())) {
this.index += 1;
}
return this.str.substring(j, this.index);
}
private NBTTag parseAnything() throws MojangsonParseException {
skipWhitespace();
if (!hasNext())
throw parseException("Expected value");
int c = currentChar();
if (c == '{')
return parseCompound();
else if (c == '[')
return parseDetectedArray();
else
return parseStringOrLiteral();
}
private NBTTag parseDetectedArray() throws MojangsonParseException {
if (hasCharsLeft(2) && getChar(1) != '"' && getChar(2) == ';') {
return parseNumArray();
}
return parseList();
}
private NBTCompound parseCompound() throws MojangsonParseException {
expectChar('{');
NBTCompound compound = new NBTCompound();
skipWhitespace();
while ((hasNext()) && (currentChar() != '}'))
{
String str = parseCompoundKey();
if (str.isEmpty()) {
throw parseException("Expected non-empty key");
}
expectChar(':');
compound.put(str, parseAnything());
if (!advanceToNextArrayElement()) {
break;
}
if (!hasNext()) {
throw parseException("Expected key");
}
}
expectChar('}');
return compound;
}
private NBTList parseList() throws MojangsonParseException {
expectChar('[');
skipWhitespace();
if (!hasNext()) {
throw parseException("Expected value");
}
NBTList list = new NBTList();
NBTType listType = null;
while (currentChar() != ']') {
NBTTag element = parseAnything();
NBTType elementType = element.getType();
if (listType == null) {
listType = elementType;
} else if (elementType != listType) {
throw parseException("Unable to insert " + elementType + " into ListTag of type " + listType);
}
list.add(element);
if (!advanceToNextArrayElement()) {
break;
}
if (!hasNext()) {
throw parseException("Expected value");
}
}
expectChar(']');
return list;
}
private NBTTag parseNumArray() throws MojangsonParseException {
expectChar('[');
char arrayType = nextChar();
expectChar(';');
//nextChar(); semicolon ignored by Mojang
skipWhitespace();
if (!hasNext()) {
throw parseException("Expected value");
}
if (arrayType == 'B')
return new NBTByteArray(parseNumArray(NBTType.BYTE_ARRAY, NBTType.BYTE));
else if (arrayType == 'L')
return new NBTLongArray(parseNumArray(NBTType.LONG_ARRAY, NBTType.LONG));
else if (arrayType == 'I')
return new NBTIntArray(parseNumArray(NBTType.INT_ARRAY, NBTType.INT));
throw parseException("Invalid array type '" + arrayType + "' found");
}
private Number[] parseNumArray(NBTType arrayType, NBTType primType) throws MojangsonParseException {
List<Number> result = new ArrayList<Number>();
while (currentChar() != ']') {
NBTTag element = parseAnything();
NBTType elementType = element.getType();
if (elementType != primType) {
throw parseException("Unable to insert " + elementType + " into " + arrayType);
}
if (primType == NBTType.BYTE) {
result.add(((NBTByte) element).getValue());
} else if (primType == NBTType.LONG) {
result.add(((NBTLong) element).getValue());
} else {
result.add(((NBTInt) element).getValue());
}
if (!advanceToNextArrayElement()) {
break;
}
if (!hasNext()) {
throw parseException("Expected value");
}
}
expectChar(']');
return result.toArray(new Number[result.size()]);
}
// CHARACTER NAVIGATION
private boolean advanceToNextArrayElement() {
skipWhitespace();
if (hasNext() && currentChar() == ',') {
this.index += 1;
skipWhitespace();
return true;
}
return false;
}
private void skipWhitespace() {
while (hasNext() && Character.isWhitespace(currentChar())) {
this.index += 1;
}
}
private boolean hasCharsLeft(int paramInt) {
return this.index + paramInt < this.str.length();
}
private boolean hasNext() {
return hasCharsLeft(0);
}
/**
* Returns the character in the string at the current index plus a given offset.
*
* @param offset the offset
* @return the character at the offset
*/
private char getChar(int offset) {
return this.str.charAt(this.index + offset);
}
/**
* Returns the current character.
*
* @return the current character
*/
private char currentChar() {
return getChar(0);
}
/**
* Returns the current character and increments the index.
*
* @return the current character
*/
private char nextChar() {
return this.str.charAt(this.index++);
}
// UTIL
/**
* Verifies whether the current character is of given value and whether the parser can advance. If these conditions
* are met, the parser advances by one. If these conditions are not met, an exception is thrown.
*
* @param c the expected character
* @throws MojangsonParseException if {@link #currentChar()} does not equal {@code c} or if {@link #hasNext()}
* returns false
*/
private void expectChar(char c) throws MojangsonParseException {
skipWhitespace();
boolean hasNext = hasNext();
if (hasNext && currentChar() == c) {
this.index += 1;
return;
}
throw new MojangsonParseException("Expected '" + c + "' but got '" + (hasNext ? Character.valueOf(currentChar()) : "<End of string>") + "'", this.str, this.index + 1);
}
/**
* Verifies that the string has ended or that all characters from the next character on only consists of whitespace.
*
* @throws MojangsonParseException if the following characters contain a non-whitespace character
*/
private void expectNoTrail() throws MojangsonParseException {
skipWhitespace();
if (hasNext()) {
this.index++;
throw parseException("Trailing data found");
}
}
private MojangsonParseException parseException(String paramString) {
return new MojangsonParseException(paramString, this.str, this.index);
}
private static boolean isSimpleChar(char paramChar) {
return (paramChar >= '0' && paramChar <= '9')
|| (paramChar >= 'A' && paramChar <= 'Z')
|| (paramChar >= 'a' && paramChar <= 'z')
|| paramChar == '_'
|| paramChar == '-'
|| paramChar == '.'
|| paramChar == '+';
}
}