Created
February 28, 2020 14:16
-
-
Save Waterpicker/a3bf767ea7c07103565fc14ff34bdfde to your computer and use it in GitHub Desktop.
GsonNbt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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(); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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