258 lines
9.4 KiB
Java
258 lines
9.4 KiB
Java
/*
|
|
* This file is part of adventure, licensed under the MIT License.
|
|
*
|
|
* Copyright (c) 2017-2020 KyoriPowered
|
|
*
|
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
* of this software and associated documentation files (the "Software"), to deal
|
|
* in the Software without restriction, including without limitation the rights
|
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
* copies of the Software, and to permit persons to whom the Software is
|
|
* furnished to do so, subject to the following conditions:
|
|
*
|
|
* The above copyright notice and this permission notice shall be included in all
|
|
* copies or substantial portions of the Software.
|
|
*
|
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
* SOFTWARE.
|
|
*/
|
|
package us.myles.ViaVersion.api.minecraft.nbt;
|
|
|
|
import com.github.steveice10.opennbt.tag.builtin.ByteArrayTag;
|
|
import com.github.steveice10.opennbt.tag.builtin.ByteTag;
|
|
import com.github.steveice10.opennbt.tag.builtin.CompoundTag;
|
|
import com.github.steveice10.opennbt.tag.builtin.DoubleTag;
|
|
import com.github.steveice10.opennbt.tag.builtin.FloatTag;
|
|
import com.github.steveice10.opennbt.tag.builtin.IntArrayTag;
|
|
import com.github.steveice10.opennbt.tag.builtin.IntTag;
|
|
import com.github.steveice10.opennbt.tag.builtin.ListTag;
|
|
import com.github.steveice10.opennbt.tag.builtin.LongArrayTag;
|
|
import com.github.steveice10.opennbt.tag.builtin.LongTag;
|
|
import com.github.steveice10.opennbt.tag.builtin.ShortTag;
|
|
import com.github.steveice10.opennbt.tag.builtin.StringTag;
|
|
import com.github.steveice10.opennbt.tag.builtin.Tag;
|
|
|
|
import java.io.IOException;
|
|
import java.io.Writer;
|
|
import java.util.Map;
|
|
|
|
/**
|
|
* See https://github.com/KyoriPowered/adventure.
|
|
*/
|
|
/* package */ final class TagStringWriter implements AutoCloseable {
|
|
private final Appendable out;
|
|
private final String indent = " "; // TODO: pretty-printing
|
|
private int level;
|
|
/**
|
|
* Whether a {@link Tokens#VALUE_SEPARATOR} needs to be printed before the beginning of the next object.
|
|
*/
|
|
private boolean needsSeparator;
|
|
|
|
public TagStringWriter(final Appendable out) {
|
|
this.out = out;
|
|
}
|
|
|
|
// NBT-specific
|
|
|
|
public TagStringWriter writeTag(final Tag tag) throws IOException {
|
|
if (tag instanceof CompoundTag) {
|
|
return this.writeCompound((CompoundTag) tag);
|
|
} else if (tag instanceof ListTag) {
|
|
return this.writeList((ListTag) tag);
|
|
} else if (tag instanceof ByteArrayTag) {
|
|
return this.writeByteArray((ByteArrayTag) tag);
|
|
} else if (tag instanceof IntArrayTag) {
|
|
return this.writeIntArray((IntArrayTag) tag);
|
|
} else if (tag instanceof LongArrayTag) {
|
|
return this.writeLongArray((LongArrayTag) tag);
|
|
} else if (tag instanceof StringTag) {
|
|
return this.value(((StringTag) tag).getValue(), Tokens.EOF);
|
|
} else if (tag instanceof ByteTag) {
|
|
return this.value(Byte.toString(((ByteTag) tag).asByte()), Tokens.TYPE_BYTE);
|
|
} else if (tag instanceof ShortTag) {
|
|
return this.value(Short.toString(((ShortTag) tag).asShort()), Tokens.TYPE_SHORT);
|
|
} else if (tag instanceof IntTag) {
|
|
return this.value(Integer.toString(((IntTag) tag).asInt()), Tokens.TYPE_INT);
|
|
} else if (tag instanceof LongTag) {
|
|
return this.value(Long.toString(((LongTag) tag).asLong()), Tokens.TYPE_LONG);
|
|
} else if (tag instanceof FloatTag) {
|
|
return this.value(Float.toString(((FloatTag) tag).asFloat()), Tokens.TYPE_FLOAT);
|
|
} else if (tag instanceof DoubleTag) {
|
|
return this.value(Double.toString(((DoubleTag) tag).asDouble()), Tokens.TYPE_DOUBLE);
|
|
} else {
|
|
throw new IOException("Unknown tag type: " + tag.getClass().getSimpleName());
|
|
// unknown!
|
|
}
|
|
}
|
|
|
|
private TagStringWriter writeCompound(final CompoundTag tag) throws IOException {
|
|
this.beginCompound();
|
|
for (Map.Entry<String, Tag> entry : tag.entrySet()) {
|
|
this.key(entry.getKey());
|
|
this.writeTag(entry.getValue());
|
|
}
|
|
this.endCompound();
|
|
return this;
|
|
}
|
|
|
|
private TagStringWriter writeList(final ListTag tag) throws IOException {
|
|
this.beginList();
|
|
for (final Tag el : tag) {
|
|
this.printAndResetSeparator();
|
|
this.writeTag(el);
|
|
}
|
|
this.endList();
|
|
return this;
|
|
}
|
|
|
|
private TagStringWriter writeByteArray(final ByteArrayTag tag) throws IOException {
|
|
this.beginArray(Tokens.TYPE_BYTE);
|
|
|
|
final byte[] value = tag.getValue();
|
|
for (int i = 0, length = value.length; i < length; i++) {
|
|
this.printAndResetSeparator();
|
|
this.value(Byte.toString(value[i]), Tokens.TYPE_BYTE);
|
|
}
|
|
this.endArray();
|
|
return this;
|
|
}
|
|
|
|
private TagStringWriter writeIntArray(final IntArrayTag tag) throws IOException {
|
|
this.beginArray(Tokens.TYPE_INT);
|
|
|
|
final int[] value = tag.getValue();
|
|
for (int i = 0, length = value.length; i < length; i++) {
|
|
this.printAndResetSeparator();
|
|
this.value(Integer.toString(value[i]), Tokens.TYPE_INT);
|
|
}
|
|
this.endArray();
|
|
return this;
|
|
}
|
|
|
|
private TagStringWriter writeLongArray(final LongArrayTag tag) throws IOException {
|
|
this.beginArray(Tokens.TYPE_LONG);
|
|
|
|
final long[] value = tag.getValue();
|
|
for (int i = 0, length = value.length; i < length; i++) {
|
|
this.printAndResetSeparator();
|
|
this.value(Long.toString(value[i]), Tokens.TYPE_LONG);
|
|
}
|
|
this.endArray();
|
|
return this;
|
|
}
|
|
|
|
// Value types
|
|
|
|
public TagStringWriter beginCompound() throws IOException {
|
|
this.printAndResetSeparator();
|
|
this.level++;
|
|
this.out.append(Tokens.COMPOUND_BEGIN);
|
|
return this;
|
|
}
|
|
|
|
public TagStringWriter endCompound() throws IOException {
|
|
this.out.append(Tokens.COMPOUND_END);
|
|
this.level--;
|
|
this.needsSeparator = true;
|
|
return this;
|
|
}
|
|
|
|
public TagStringWriter key(final String key) throws IOException {
|
|
this.printAndResetSeparator();
|
|
this.writeMaybeQuoted(key, false);
|
|
this.out.append(Tokens.COMPOUND_KEY_TERMINATOR); // TODO: spacing/pretty-printing
|
|
return this;
|
|
}
|
|
|
|
public TagStringWriter value(final String value, final char valueType) throws IOException {
|
|
if (valueType == Tokens.EOF) { // string doesn't have its type
|
|
this.writeMaybeQuoted(value, true);
|
|
} else {
|
|
this.out.append(value);
|
|
if (valueType != Tokens.TYPE_INT) {
|
|
this.out.append(valueType);
|
|
}
|
|
}
|
|
this.needsSeparator = true;
|
|
return this;
|
|
}
|
|
|
|
public TagStringWriter beginList() throws IOException {
|
|
this.printAndResetSeparator();
|
|
this.level++;
|
|
this.out.append(Tokens.ARRAY_BEGIN);
|
|
return this;
|
|
}
|
|
|
|
public TagStringWriter endList() throws IOException {
|
|
this.out.append(Tokens.ARRAY_END);
|
|
this.level--;
|
|
this.needsSeparator = true;
|
|
return this;
|
|
}
|
|
|
|
private TagStringWriter beginArray(final char type) throws IOException {
|
|
this.beginList()
|
|
.out.append(type)
|
|
.append(Tokens.ARRAY_SIGNATURE_SEPARATOR);
|
|
return this;
|
|
}
|
|
|
|
private TagStringWriter endArray() throws IOException {
|
|
return this.endList();
|
|
}
|
|
|
|
private void writeMaybeQuoted(final String content, boolean requireQuotes) throws IOException {
|
|
if (!requireQuotes) {
|
|
for (int i = 0; i < content.length(); ++i) {
|
|
if (!Tokens.id(content.charAt(i))) {
|
|
requireQuotes = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if (requireQuotes) { // TODO: single quotes
|
|
this.out.append(Tokens.DOUBLE_QUOTE);
|
|
this.out.append(escape(content, Tokens.DOUBLE_QUOTE));
|
|
this.out.append(Tokens.DOUBLE_QUOTE);
|
|
} else {
|
|
this.out.append(content);
|
|
}
|
|
}
|
|
|
|
private static String escape(final String content, final char quoteChar) {
|
|
final StringBuilder output = new StringBuilder(content.length());
|
|
for (int i = 0; i < content.length(); ++i) {
|
|
final char c = content.charAt(i);
|
|
if (c == quoteChar || c == '\\') {
|
|
output.append(Tokens.ESCAPE_MARKER);
|
|
}
|
|
output.append(c);
|
|
}
|
|
return output.toString();
|
|
}
|
|
|
|
private void printAndResetSeparator() throws IOException {
|
|
if (this.needsSeparator) {
|
|
this.out.append(Tokens.VALUE_SEPARATOR);
|
|
this.needsSeparator = false;
|
|
}
|
|
}
|
|
|
|
|
|
@Override
|
|
public void close() throws IOException {
|
|
if (this.level != 0) {
|
|
throw new IllegalStateException("Document finished with unbalanced start and end objects");
|
|
}
|
|
if (this.out instanceof Writer) {
|
|
((Writer) this.out).flush();
|
|
}
|
|
}
|
|
}
|