Skip to content

Instantly share code, notes, and snippets.

@Waterpicker
Created February 28, 2020 14:16
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Waterpicker/a3bf767ea7c07103565fc14ff34bdfde to your computer and use it in GitHub Desktop.
Save Waterpicker/a3bf767ea7c07103565fc14ff34bdfde to your computer and use it in GitHub Desktop.
GsonNbt
package org.dimdev.gsonnbt;
import com.google.gson.*;
import com.google.gson.internal.bind.JsonTreeReader;
import com.google.gson.internal.bind.JsonTreeWriter;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonToken;
import net.minecraft.nbt.*;
import java.io.IOException;
import java.io.Reader;
import java.util.Iterator;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public class NbtReader extends JsonReader {
private static final Reader UNREADABLE_READER = new Reader() {
@Override public int read(char[] buffer, int offset, int count) throws IOException {
throw new AssertionError();
}
@Override public void close() throws IOException {
throw new AssertionError();
}
};
private static final Object SENTINEL_CLOSED = new Object();
/*
* The nesting stack. Using a manual array rather than an ArrayList saves 20%.
*/
private Object[] stack = new Object[32];
private int stackSize = 0;
/*
* The path members. It corresponds directly to stack: At indices where the
* stack contains an object (EMPTY_OBJECT, DANGLING_NAME or NONEMPTY_OBJECT),
* pathNames contains the name at this scope. Where it contains an array
* (EMPTY_ARRAY, NONEMPTY_ARRAY) pathIndices contains the current index in
* that array. Otherwise the value is undefined, and we take advantage of that
* by incrementing pathIndices when doing so isn't useful.
*/
private String[] pathNames = new String[32];
private int[] pathIndices = new int[32];
public NbtReader(NBTBase element) {
super(UNREADABLE_READER);
push(element);
}
@Override public void beginArray() throws IOException {
expect(JsonToken.BEGIN_ARRAY);
NBTTagList array = (NBTTagList) peekStack();
push(array.iterator());
pathIndices[stackSize - 1] = 0;
}
@Override public void endArray() throws IOException {
expect(JsonToken.END_ARRAY);
popStack(); // empty iterator
popStack(); // array
if (stackSize > 0) {
pathIndices[stackSize - 1]++;
}
}
@Override public void beginObject() throws IOException {
expect(JsonToken.BEGIN_OBJECT);
NBTTagCompound object = (NBTTagCompound) peekStack();
push(object.getKeySet().stream().collect(Collectors.toMap(Function.identity(), object::getTag)).entrySet().iterator());
}
@Override public void endObject() throws IOException {
expect(JsonToken.END_OBJECT);
popStack(); // empty iterator
popStack(); // object
if (stackSize > 0) {
pathIndices[stackSize - 1]++;
}
}
@Override public boolean hasNext() throws IOException {
JsonToken token = peek();
return token != JsonToken.END_OBJECT && token != JsonToken.END_ARRAY;
}
@Override public JsonToken peek() throws IOException {
if (stackSize == 0) {
return JsonToken.END_DOCUMENT;
}
Object o = peekStack();
if (o instanceof Iterator) {
boolean isObject = stack[stackSize - 2] instanceof NBTTagCompound;
Iterator<?> iterator = (Iterator<?>) o;
if (iterator.hasNext()) {
if (isObject) {
return JsonToken.NAME;
} else {
push(iterator.next());
return peek();
}
} else {
return isObject ? JsonToken.END_OBJECT : JsonToken.END_ARRAY;
}
} else if (o instanceof NBTTagCompound) {
return JsonToken.BEGIN_OBJECT;
} else if (o instanceof NBTTagList) {
return JsonToken.BEGIN_ARRAY;
} else if (o instanceof NBTPrimitive) {
if (o instanceof NBTTagByte) {
if (((NBTTagByte) o).getByte() == 0 || ((NBTTagByte) o).getByte() == 1) return JsonToken.BOOLEAN;
}
return JsonToken.NUMBER;
} else if (o instanceof NBTTagString) {
return JsonToken.STRING;
} else if (o instanceof NBTTagEnd) {
return JsonToken.NULL;
} else if (o == SENTINEL_CLOSED) {
throw new IllegalStateException("JsonReader is closed");
} else {
System.out.println(o.getClass());
throw new AssertionError();
}
}
private Object peekStack() {
return stack[stackSize - 1];
}
private Object popStack() {
Object result = stack[--stackSize];
stack[stackSize] = null;
return result;
}
private void expect(JsonToken expected) throws IOException {
if (peek() != expected) {
throw new IllegalStateException(
"Expected " + expected + " but was " + peek() + locationString());
}
}
@Override public String nextName() throws IOException {
expect(JsonToken.NAME);
Iterator<?> i = (Iterator<?>) peekStack();
Map.Entry<?, ?> entry = (Map.Entry<?, ?>) i.next();
String result = (String) entry.getKey();
pathNames[stackSize - 1] = result;
push(entry.getValue());
return result;
}
@Override public String nextString() throws IOException {
JsonToken token = peek();
if (token != JsonToken.STRING) {
throw new IllegalStateException(
"Expected " + JsonToken.STRING + " but was " + token + locationString());
}
String result = ((NBTTagString) popStack()).getString();
if (stackSize > 0) {
pathIndices[stackSize - 1]++;
}
return result;
}
@Override public boolean nextBoolean() throws IOException {
expect(JsonToken.BOOLEAN);
boolean result = ((NBTTagByte) popStack()).getByte() != 0;
if (stackSize > 0) {
pathIndices[stackSize - 1]++;
}
return result;
}
@Override public void nextNull() throws IOException {
expect(JsonToken.NULL);
popStack();
if (stackSize > 0) {
pathIndices[stackSize - 1]++;
}
}
@Override public double nextDouble() throws IOException {
JsonToken token = peek();
if (token != JsonToken.NUMBER) {
throw new IllegalStateException(
"Expected " + JsonToken.NUMBER + " but was " + token + locationString());
}
double result = ((NBTPrimitive) peekStack()).getDouble();
if (!isLenient() && (Double.isNaN(result) || Double.isInfinite(result))) {
throw new NumberFormatException("JSON forbids NaN and infinities: " + result);
}
popStack();
if (stackSize > 0) {
pathIndices[stackSize - 1]++;
}
return result;
}
@Override public long nextLong() throws IOException {
JsonToken token = peek();
if (token != JsonToken.NUMBER) {
throw new IllegalStateException(
"Expected " + JsonToken.NUMBER + " but was " + token + locationString());
}
long result = ((NBTPrimitive) peekStack()).getLong();
popStack();
if (stackSize > 0) {
pathIndices[stackSize - 1]++;
}
return result;
}
@Override public int nextInt() throws IOException {
JsonToken token = peek();
if (token != JsonToken.NUMBER) {
throw new IllegalStateException(
"Expected " + JsonToken.NUMBER + " but was " + token + locationString());
}
int result = ((NBTPrimitive) peekStack()).getInt();
popStack();
if (stackSize > 0) {
pathIndices[stackSize - 1]++;
}
return result;
}
@Override public void close() throws IOException {
stack = new Object[] { SENTINEL_CLOSED };
stackSize = 1;
}
@Override public void skipValue() throws IOException {
if (peek() == JsonToken.NAME) {
nextName();
pathNames[stackSize - 2] = "null";
} else {
popStack();
pathNames[stackSize - 1] = "null";
}
pathIndices[stackSize - 1]++;
}
@Override public String toString() {
return getClass().getSimpleName();
}
private void push(Object newTop) {
if (stackSize == stack.length) {
Object[] newStack = new Object[stackSize * 2];
int[] newPathIndices = new int[stackSize * 2];
String[] newPathNames = new String[stackSize * 2];
System.arraycopy(stack, 0, newStack, 0, stackSize);
System.arraycopy(pathIndices, 0, newPathIndices, 0, stackSize);
System.arraycopy(pathNames, 0, newPathNames, 0, stackSize);
stack = newStack;
pathIndices = newPathIndices;
pathNames = newPathNames;
}
stack[stackSize++] = newTop;
}
@Override public String getPath() {
StringBuilder result = new StringBuilder().append('$');
for (int i = 0; i < stackSize; i++) {
if (stack[i] instanceof NBTTagCompound) {
if (stack[++i] instanceof Iterator) {
result.append('[').append(pathIndices[i]).append(']');
}
} else if (stack[i] instanceof NBTTagList) {
if (stack[++i] instanceof Iterator) {
result.append('.');
if (pathNames[i] != null) {
result.append(pathNames[i]);
}
}
}
}
return result.toString();
}
private String locationString() {
return " at path " + getPath();
}
}
package org.dimdev.gsonnbt;
import com.google.gson.*;
import com.google.gson.internal.bind.JsonTreeWriter;
import com.google.gson.stream.JsonWriter;
import net.minecraft.nbt.*;
import scala.math.BigInt;
import java.io.IOException;
import java.io.Writer;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.List;
/**
* This writer creates a JsonElement.
*/
public final class NbtWriter extends JsonWriter {
private static final Writer UNWRITABLE_WRITER = new Writer() {
@Override public void write(char[] buffer, int offset, int counter) {
throw new AssertionError();
}
@Override public void flush() throws IOException {
throw new AssertionError();
}
@Override public void close() throws IOException {
throw new AssertionError();
}
};
/** Added to the top of the stack when this writer is closed to cause following ops to fail. */
private static final JsonPrimitive SENTINEL_CLOSED = new JsonPrimitive("closed");
/** The JsonElements and JsonArrays under modification, outermost to innermost. */
private final List<NBTBase> stack = new ArrayList<>();
/** The name for the next JSON object value. If non-null, the top of the stack is a JsonObject. */
private String pendingName;
/** the JSON element constructed by this writer. */
private NBTBase product = new NBTTagEnd(); // TODO: is this really what we want?;
public NbtWriter() {
super(UNWRITABLE_WRITER);
}
/**
* Returns the top level object produced by this writer.
*/
public NBTBase get() {
if (!stack.isEmpty()) {
throw new IllegalStateException("Expected one JSON element but was " + stack);
}
return product;
}
private NBTBase peek() {
return stack.get(stack.size() - 1);
}
private void put(NBTBase value) {
if (pendingName != null) {
if (!(value instanceof NBTTagEnd) || getSerializeNulls()) {
NBTBase element = peek();
if (element instanceof NBTTagCompound) {
((NBTTagCompound) element).setTag(pendingName, value);
} else {
throw new IllegalStateException();
}
}
pendingName = null;
} else if (stack.isEmpty()) {
product = value;
} else {
NBTBase element = peek();
if (element instanceof NBTTagList) {
((NBTTagList) element).appendTag(value);
} else {
throw new IllegalStateException();
}
}
}
@Override public JsonWriter beginArray() throws IOException {
NBTTagList array = new NBTTagList();
put(array);
stack.add(array);
return this;
}
@Override public JsonWriter endArray() throws IOException {
if (stack.isEmpty() || pendingName != null) {
throw new IllegalStateException();
}
NBTBase element = peek();
if (element instanceof NBTTagList) {
stack.remove(stack.size() - 1);
return this;
}
throw new IllegalStateException();
}
@Override public JsonWriter beginObject() throws IOException {
NBTTagCompound object = new NBTTagCompound();
put(object);
stack.add(object);
return this;
}
@Override public JsonWriter endObject() throws IOException {
if (stack.isEmpty() || pendingName != null) {
throw new IllegalStateException();
}
NBTBase element = peek();
if (element instanceof NBTTagCompound) {
stack.remove(stack.size() - 1);
return this;
}
throw new IllegalStateException();
}
@Override public JsonWriter name(String name) throws IOException {
if (stack.isEmpty() || pendingName != null) {
throw new IllegalStateException();
}
NBTBase element = peek();
if (element instanceof NBTTagCompound) {
pendingName = name;
return this;
}
throw new IllegalStateException();
}
@Override public JsonWriter value(String value) throws IOException {
if (value == null) {
return nullValue();
}
put(new NBTTagString(value));
return this;
}
@Override public JsonWriter nullValue() throws IOException {
put(new NBTTagEnd());
return this;
}
@Override public JsonWriter value(boolean value) throws IOException {
put(new NBTTagByte((byte)(value ? 1 : 0)));
return this;
}
@Override public JsonWriter value(Boolean value) throws IOException {
value(new Byte((byte)(value ? 1 : 0)));
return this;
}
@Override public JsonWriter value(double value) throws IOException {
if (!isLenient() && (Double.isNaN(value) || Double.isInfinite(value))) {
throw new IllegalArgumentException("NBT forbids NaN and infinities: " + value);
}
put(new NBTTagDouble(value));
return this;
}
@Override public JsonWriter value(long value) throws IOException {
put(new NBTTagLong(value));
return this;
}
@Override public JsonWriter value(Number value) throws IOException {
if (value == null) {
return nullValue();
}
if (!isLenient()) {
double d = value.doubleValue();
if (Double.isNaN(d) || Double.isInfinite(d)) {
throw new IllegalArgumentException("NBT forbids NaN and infinities: " + value);
}
}
if(value instanceof Byte) put(new NBTTagByte(value.byteValue()));
else if(value instanceof Double) put(new NBTTagDouble(value.doubleValue()));
else if(value instanceof Float) put(new NBTTagFloat(value.floatValue()));
else if(value instanceof Integer) put(new NBTTagInt(value.intValue()));
else if(value instanceof Short) put(new NBTTagShort(value.shortValue()));
else throw new IllegalArgumentException("NBT does not support " + value.getClass().getName());
return this;
}
@Override public void flush() throws IOException {
}
@Override public void close() throws IOException {
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment