1476 lines
43 KiB
Java
1476 lines
43 KiB
Java
package com.hypixel.hytale.codec.util;
|
|
|
|
import ch.randelshofer.fastdoubleparser.JavaDoubleParser;
|
|
import com.hypixel.hytale.codec.Codec;
|
|
import com.hypixel.hytale.codec.ExtraInfo;
|
|
import com.hypixel.hytale.logger.HytaleLogger;
|
|
import com.hypixel.hytale.unsafe.UnsafeUtil;
|
|
import io.sentry.Sentry;
|
|
import java.io.IOException;
|
|
import java.io.InputStreamReader;
|
|
import java.io.Reader;
|
|
import java.nio.charset.StandardCharsets;
|
|
import java.nio.file.Files;
|
|
import java.nio.file.NoSuchFileException;
|
|
import java.nio.file.Path;
|
|
import java.util.Arrays;
|
|
import java.util.logging.Level;
|
|
import javax.annotation.Nonnull;
|
|
import javax.annotation.Nullable;
|
|
import org.bson.BsonArray;
|
|
import org.bson.BsonBoolean;
|
|
import org.bson.BsonDocument;
|
|
import org.bson.BsonDouble;
|
|
import org.bson.BsonNull;
|
|
import org.bson.BsonString;
|
|
import org.bson.BsonValue;
|
|
import sun.misc.Unsafe;
|
|
|
|
public class RawJsonReader implements AutoCloseable {
|
|
public static final ThreadLocal<char[]> READ_BUFFER = ThreadLocal.withInitial(() -> new char[131072]);
|
|
public static final int DEFAULT_CHAR_BUFFER_SIZE = 32768;
|
|
public static final int MIN_CHAR_BUFFER_READ = 16384;
|
|
public static final int BUFFER_GROWTH = 1048576;
|
|
private static final int UNMARKED = -1;
|
|
private int streamIndex;
|
|
@Nullable
|
|
private Reader in;
|
|
@Nullable
|
|
private char[] buffer;
|
|
private int bufferIndex;
|
|
private int bufferSize;
|
|
private int markIndex = -1;
|
|
private int markLine = -1;
|
|
private int markLineStart = -1;
|
|
private StringBuilder tempSb;
|
|
private int line;
|
|
private int lineStart;
|
|
public static final int ERROR_LINES_BUFFER = 10;
|
|
|
|
public RawJsonReader(@Nonnull char[] preFilledBuffer) {
|
|
if (preFilledBuffer == null) {
|
|
throw new IllegalArgumentException("buffer can't be null!");
|
|
} else {
|
|
this.in = null;
|
|
this.buffer = preFilledBuffer;
|
|
this.bufferIndex = 0;
|
|
this.streamIndex = 0;
|
|
this.bufferSize = preFilledBuffer.length;
|
|
}
|
|
}
|
|
|
|
public RawJsonReader(Reader in, @Nonnull char[] buffer) {
|
|
if (buffer == null) {
|
|
throw new IllegalArgumentException("buffer can't be null!");
|
|
} else {
|
|
this.in = in;
|
|
this.buffer = buffer;
|
|
this.bufferIndex = 0;
|
|
this.streamIndex = 0;
|
|
this.bufferSize = 0;
|
|
}
|
|
}
|
|
|
|
public char[] getBuffer() {
|
|
return this.buffer;
|
|
}
|
|
|
|
public int getBufferIndex() {
|
|
return this.bufferIndex;
|
|
}
|
|
|
|
public int getBufferSize() {
|
|
return this.bufferSize;
|
|
}
|
|
|
|
public int getLine() {
|
|
return this.line + 1;
|
|
}
|
|
|
|
public int getColumn() {
|
|
return this.bufferIndex - this.lineStart + 1;
|
|
}
|
|
|
|
private boolean ensure() throws IOException {
|
|
return this.ensure(1);
|
|
}
|
|
|
|
private boolean ensure(int n) throws IOException {
|
|
boolean filled;
|
|
for (filled = false; this.bufferIndex + n > this.bufferSize; filled = true) {
|
|
if (!this.fill()) {
|
|
throw this.unexpectedEOF();
|
|
}
|
|
}
|
|
|
|
return filled;
|
|
}
|
|
|
|
private boolean fill() throws IOException {
|
|
int dst;
|
|
int len;
|
|
if (this.markIndex <= -1) {
|
|
this.streamIndex = this.streamIndex + this.bufferIndex;
|
|
dst = 0;
|
|
len = this.buffer.length;
|
|
} else {
|
|
int spaceInBuffer = this.buffer.length - this.bufferIndex;
|
|
if (spaceInBuffer > 16384) {
|
|
dst = this.bufferIndex;
|
|
len = spaceInBuffer;
|
|
} else {
|
|
int delta = this.bufferIndex - this.markIndex;
|
|
if (this.markIndex > 16384) {
|
|
System.arraycopy(this.buffer, this.markIndex, this.buffer, 0, delta);
|
|
} else {
|
|
int newSize = this.bufferIndex + 1048576;
|
|
System.err.println("Reallocate: " + this.buffer.length + " to " + newSize);
|
|
char[] ncb = new char[newSize];
|
|
System.arraycopy(this.buffer, this.markIndex, ncb, 0, delta);
|
|
this.buffer = ncb;
|
|
}
|
|
|
|
this.streamIndex = this.streamIndex + this.markIndex;
|
|
this.markIndex = 0;
|
|
dst = delta;
|
|
this.bufferIndex = this.bufferSize = delta;
|
|
len = this.buffer.length - delta;
|
|
}
|
|
}
|
|
|
|
if (this.in == null) {
|
|
return false;
|
|
} else {
|
|
int n = this.in.read(this.buffer, dst, len);
|
|
if (n > 0) {
|
|
this.bufferSize = dst + n;
|
|
this.bufferIndex = dst;
|
|
return true;
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
public int peek() throws IOException {
|
|
return this.peek(0);
|
|
}
|
|
|
|
public int peek(int n) throws IOException {
|
|
if (this.bufferIndex + n >= this.bufferSize) {
|
|
this.fill();
|
|
if (this.bufferIndex + n >= this.bufferSize) {
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
return this.buffer[this.bufferIndex + n];
|
|
}
|
|
|
|
public int read() throws IOException {
|
|
if (this.bufferIndex >= this.bufferSize) {
|
|
this.fill();
|
|
if (this.bufferIndex >= this.bufferSize) {
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
char c = this.buffer[this.bufferIndex++];
|
|
if (c == '\n') {
|
|
this.line++;
|
|
this.lineStart = this.bufferIndex;
|
|
}
|
|
|
|
return c;
|
|
}
|
|
|
|
public long skip(long skip) throws IOException {
|
|
if (skip < 0L) {
|
|
int negativeBufferIndex = -this.bufferIndex;
|
|
if (skip < negativeBufferIndex) {
|
|
this.bufferIndex = 0;
|
|
return negativeBufferIndex;
|
|
} else {
|
|
this.bufferIndex = (int)(this.bufferIndex + skip);
|
|
return skip;
|
|
}
|
|
} else {
|
|
long haveSkipped = 0L;
|
|
|
|
while (haveSkipped < skip) {
|
|
long charsInBuffer = this.bufferSize - this.bufferIndex;
|
|
long charsToSkip = skip - haveSkipped;
|
|
if (charsToSkip <= charsInBuffer) {
|
|
this.bufferIndex = (int)(this.bufferIndex + charsToSkip);
|
|
return skip;
|
|
}
|
|
|
|
haveSkipped += charsInBuffer;
|
|
this.bufferIndex = this.bufferSize;
|
|
this.fill();
|
|
if (this.bufferIndex >= this.bufferSize) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
return haveSkipped;
|
|
}
|
|
}
|
|
|
|
public int findOffset(char value) throws IOException {
|
|
return this.findOffset(0, value);
|
|
}
|
|
|
|
public int findOffset(int start, char value) throws IOException {
|
|
while (true) {
|
|
this.ensure();
|
|
char c = this.buffer[this.bufferIndex + start];
|
|
if (c == value) {
|
|
return start;
|
|
}
|
|
|
|
start++;
|
|
}
|
|
}
|
|
|
|
public void skipOrThrow(long n) throws IOException {
|
|
long skipped = this.skip(n);
|
|
if (skipped != n) {
|
|
throw new IOException("Failed to skip " + n + " char's!");
|
|
}
|
|
}
|
|
|
|
public boolean ready() throws IOException {
|
|
return this.buffer != null && this.bufferIndex < this.bufferSize || this.in.ready();
|
|
}
|
|
|
|
public boolean markSupported() {
|
|
return true;
|
|
}
|
|
|
|
public void mark(int readAheadLimit) throws IOException {
|
|
this.mark();
|
|
}
|
|
|
|
public boolean isMarked() {
|
|
return this.markIndex >= 0;
|
|
}
|
|
|
|
public void mark() throws IOException {
|
|
if (this.markIndex >= 0) {
|
|
throw new IOException("mark can't be used while already marked!");
|
|
} else {
|
|
this.markIndex = this.bufferIndex;
|
|
this.markLine = this.line;
|
|
this.markLineStart = this.lineStart;
|
|
}
|
|
}
|
|
|
|
public void unmark() {
|
|
this.markIndex = -1;
|
|
this.markLine = -1;
|
|
this.markLineStart = -1;
|
|
}
|
|
|
|
public int getMarkDistance() {
|
|
return this.bufferIndex - this.markIndex;
|
|
}
|
|
|
|
public char[] cloneMark() {
|
|
return Arrays.copyOfRange(this.buffer, this.markIndex, this.bufferIndex);
|
|
}
|
|
|
|
public void reset() throws IOException {
|
|
if (this.markIndex < 0) {
|
|
throw new IOException("Stream not marked");
|
|
} else {
|
|
this.bufferIndex = this.markIndex;
|
|
this.markIndex = -1;
|
|
this.line = this.markLine;
|
|
this.lineStart = this.markLineStart;
|
|
this.markLine = -1;
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void close() throws IOException {
|
|
if (this.buffer != null) {
|
|
try {
|
|
if (this.in != null) {
|
|
this.in.close();
|
|
}
|
|
} finally {
|
|
this.in = null;
|
|
this.buffer = null;
|
|
}
|
|
}
|
|
}
|
|
|
|
public char[] closeAndTakeBuffer() throws IOException {
|
|
char[] buffer = this.buffer;
|
|
this.close();
|
|
return buffer;
|
|
}
|
|
|
|
public boolean peekFor(char consume) throws IOException {
|
|
this.ensure();
|
|
return this.buffer[this.bufferIndex] == consume;
|
|
}
|
|
|
|
public boolean tryConsume(char consume) throws IOException {
|
|
this.ensure();
|
|
if (this.buffer[this.bufferIndex] == consume) {
|
|
this.bufferIndex++;
|
|
if (consume == '\n') {
|
|
this.line++;
|
|
this.lineStart = this.bufferIndex;
|
|
}
|
|
|
|
return true;
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
public boolean tryConsumeString(@Nonnull String str) throws IOException {
|
|
this.mark();
|
|
if (this.tryConsume('"') && this.tryConsume(str) && this.tryConsume('"')) {
|
|
this.unmark();
|
|
return true;
|
|
} else {
|
|
this.reset();
|
|
return false;
|
|
}
|
|
}
|
|
|
|
public boolean tryConsume(@Nonnull String str) throws IOException {
|
|
return this.tryConsume(str, 0);
|
|
}
|
|
|
|
public boolean tryConsume(@Nonnull String str, int start) throws IOException {
|
|
while (start < str.length()) {
|
|
this.ensure();
|
|
|
|
while (start < str.length() && this.bufferIndex < this.bufferSize) {
|
|
char c = this.buffer[this.bufferIndex];
|
|
if (c != str.charAt(start++)) {
|
|
return false;
|
|
}
|
|
|
|
this.bufferIndex++;
|
|
if (c == '\n') {
|
|
this.line++;
|
|
this.lineStart = this.bufferIndex;
|
|
}
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
public int tryConsumeSome(@Nonnull String str, int start) throws IOException {
|
|
while (start < str.length()) {
|
|
this.ensure();
|
|
|
|
while (start < str.length() && this.bufferIndex < this.bufferSize) {
|
|
char c = this.buffer[this.bufferIndex];
|
|
if (c != str.charAt(start)) {
|
|
return start;
|
|
}
|
|
|
|
start++;
|
|
this.bufferIndex++;
|
|
if (c == '\n') {
|
|
this.line++;
|
|
this.lineStart = this.bufferIndex;
|
|
}
|
|
}
|
|
}
|
|
|
|
return start;
|
|
}
|
|
|
|
public void expect(char expect) throws IOException {
|
|
this.ensure();
|
|
char read = this.buffer[this.bufferIndex++];
|
|
if (read != expect) {
|
|
throw this.expecting(read, expect);
|
|
} else {
|
|
if (expect == '\n') {
|
|
this.line++;
|
|
this.lineStart = this.bufferIndex;
|
|
}
|
|
}
|
|
}
|
|
|
|
public void expect(@Nonnull String str, int start) throws IOException {
|
|
this.ensure(str.length() - start);
|
|
|
|
while (start < str.length()) {
|
|
char c = this.buffer[this.bufferIndex];
|
|
if (c != str.charAt(start++)) {
|
|
throw this.expecting(c, str, start);
|
|
}
|
|
|
|
this.bufferIndex++;
|
|
if (c == '\n') {
|
|
this.line++;
|
|
this.lineStart = this.bufferIndex;
|
|
}
|
|
}
|
|
}
|
|
|
|
public boolean tryConsumeOrExpect(char consume, char expect) throws IOException {
|
|
this.ensure();
|
|
char read = this.buffer[this.bufferIndex];
|
|
if (read == consume) {
|
|
this.bufferIndex++;
|
|
if (consume == '\n') {
|
|
this.line++;
|
|
this.lineStart = this.bufferIndex;
|
|
}
|
|
|
|
return true;
|
|
} else if (read == expect) {
|
|
this.bufferIndex++;
|
|
if (expect == '\n') {
|
|
this.line++;
|
|
this.lineStart = this.bufferIndex;
|
|
}
|
|
|
|
return false;
|
|
} else {
|
|
throw this.expecting(read, expect);
|
|
}
|
|
}
|
|
|
|
public void consumeWhiteSpace() throws IOException {
|
|
while (true) {
|
|
if (this.bufferIndex >= this.bufferSize) {
|
|
this.fill();
|
|
if (this.bufferIndex >= this.bufferSize) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
while (this.bufferIndex < this.bufferSize) {
|
|
char ch = this.buffer[this.bufferIndex];
|
|
switch (ch) {
|
|
case '\t':
|
|
case '\r':
|
|
case ' ':
|
|
this.bufferIndex++;
|
|
break;
|
|
case '\n':
|
|
this.bufferIndex++;
|
|
this.line++;
|
|
this.lineStart = this.bufferIndex;
|
|
break;
|
|
default:
|
|
if (!Character.isWhitespace(ch)) {
|
|
return;
|
|
}
|
|
|
|
this.bufferIndex++;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
public void consumeIgnoreCase(@Nonnull String str, int start) throws IOException {
|
|
this.ensure(str.length() - start);
|
|
|
|
while (start < str.length()) {
|
|
char c = this.buffer[this.bufferIndex];
|
|
if (!equalsIgnoreCase(c, str.charAt(start++))) {
|
|
throw this.expecting(c, str, start);
|
|
}
|
|
|
|
this.bufferIndex++;
|
|
if (c == '\n') {
|
|
this.line++;
|
|
this.lineStart = this.bufferIndex;
|
|
}
|
|
}
|
|
}
|
|
|
|
@Nonnull
|
|
public String readString() throws IOException {
|
|
this.expect('"');
|
|
return this.readRemainingString();
|
|
}
|
|
|
|
@Nonnull
|
|
public String readRemainingString() throws IOException {
|
|
if (this.tempSb == null) {
|
|
this.tempSb = new StringBuilder(1024);
|
|
}
|
|
|
|
while (true) {
|
|
this.ensure();
|
|
|
|
while (this.bufferIndex < this.bufferSize) {
|
|
char read = this.buffer[this.bufferIndex++];
|
|
switch (read) {
|
|
case '"':
|
|
String string = this.tempSb.toString();
|
|
this.tempSb.setLength(0);
|
|
return string;
|
|
case '\\':
|
|
this.ensure();
|
|
read = this.buffer[this.bufferIndex++];
|
|
switch (read) {
|
|
case '"':
|
|
case '/':
|
|
case '\\':
|
|
this.tempSb.append(read);
|
|
continue;
|
|
case 'b':
|
|
this.tempSb.append('\b');
|
|
continue;
|
|
case 'f':
|
|
this.tempSb.append('\f');
|
|
continue;
|
|
case 'n':
|
|
this.tempSb.append('\n');
|
|
continue;
|
|
case 'r':
|
|
this.tempSb.append('\r');
|
|
continue;
|
|
case 't':
|
|
this.tempSb.append('\t');
|
|
continue;
|
|
case 'u':
|
|
this.ensure(4);
|
|
read = this.buffer[this.bufferIndex++];
|
|
int digit = Character.digit(read, 16);
|
|
if (digit == -1) {
|
|
throw this.expectingWhile(read, "HEX Digit 0-F", "reading string");
|
|
}
|
|
|
|
int hex = digit << 12;
|
|
read = this.buffer[this.bufferIndex++];
|
|
digit = Character.digit(read, 16);
|
|
if (digit == -1) {
|
|
throw this.expectingWhile(read, "HEX Digit 0-F", "reading string");
|
|
}
|
|
|
|
hex |= digit << 8;
|
|
read = this.buffer[this.bufferIndex++];
|
|
digit = Character.digit(read, 16);
|
|
if (digit == -1) {
|
|
throw this.expectingWhile(read, "HEX Digit 0-F", "reading string");
|
|
}
|
|
|
|
hex |= digit << 4;
|
|
read = this.buffer[this.bufferIndex++];
|
|
digit = Character.digit(read, 16);
|
|
if (digit == -1) {
|
|
throw this.expectingWhile(read, "HEX Digit 0-F", "reading string");
|
|
}
|
|
|
|
hex |= digit;
|
|
this.tempSb.appendCodePoint(hex);
|
|
continue;
|
|
default:
|
|
throw this.expecting(read, "escape char");
|
|
}
|
|
default:
|
|
if (Character.isISOControl(read)) {
|
|
throw this.unexpectedChar(read);
|
|
}
|
|
|
|
this.tempSb.append(read);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
public void skipString() throws IOException {
|
|
this.expect('"');
|
|
this.skipRemainingString();
|
|
}
|
|
|
|
public void skipRemainingString() throws IOException {
|
|
while (true) {
|
|
this.ensure();
|
|
|
|
while (this.bufferIndex < this.bufferSize) {
|
|
char read = this.buffer[this.bufferIndex++];
|
|
switch (read) {
|
|
case '"':
|
|
return;
|
|
case '\\':
|
|
this.ensure();
|
|
read = this.buffer[this.bufferIndex++];
|
|
switch (read) {
|
|
case '"':
|
|
case '/':
|
|
case '\\':
|
|
case 'b':
|
|
case 'f':
|
|
case 'n':
|
|
case 'r':
|
|
case 't':
|
|
continue;
|
|
case 'u':
|
|
this.ensure(4);
|
|
read = this.buffer[this.bufferIndex++];
|
|
int digit = Character.digit(read, 16);
|
|
if (digit == -1) {
|
|
throw this.expectingWhile(read, "HEX Digit 0-F", "skipping string");
|
|
}
|
|
|
|
read = this.buffer[this.bufferIndex++];
|
|
digit = Character.digit(read, 16);
|
|
if (digit == -1) {
|
|
throw this.expectingWhile(read, "HEX Digit 0-F", "skipping string");
|
|
}
|
|
|
|
read = this.buffer[this.bufferIndex++];
|
|
digit = Character.digit(read, 16);
|
|
if (digit == -1) {
|
|
throw this.expectingWhile(read, "HEX Digit 0-F", "skipping string");
|
|
}
|
|
|
|
read = this.buffer[this.bufferIndex++];
|
|
digit = Character.digit(read, 16);
|
|
if (digit == -1) {
|
|
throw this.expectingWhile(read, "HEX Digit 0-F", "skipping string");
|
|
}
|
|
continue;
|
|
default:
|
|
throw this.expecting(read, "escape char");
|
|
}
|
|
default:
|
|
if (Character.isISOControl(read)) {
|
|
throw this.unexpectedChar(read);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
public long readStringPartAsLong(int count) throws IOException {
|
|
assert count > 0 && count <= 4;
|
|
|
|
return UnsafeUtil.UNSAFE != null && this.bufferIndex + count <= this.bufferSize
|
|
? this.readStringPartAsLongUnsafe(count)
|
|
: this.readStringPartAsLongSlow(count);
|
|
}
|
|
|
|
protected long readStringPartAsLongSlow(int count) throws IOException {
|
|
this.ensure(count);
|
|
char c1 = this.buffer[this.bufferIndex++];
|
|
if (count == 1) {
|
|
return c1;
|
|
} else {
|
|
char c2 = this.buffer[this.bufferIndex++];
|
|
long value = c1 | (long)c2 << 16;
|
|
if (count == 2) {
|
|
return value;
|
|
} else {
|
|
char c3 = this.buffer[this.bufferIndex++];
|
|
value |= (long)c3 << 32;
|
|
if (count == 3) {
|
|
return value;
|
|
} else {
|
|
char c4 = this.buffer[this.bufferIndex++];
|
|
return value | (long)c4 << 48;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
protected long readStringPartAsLongUnsafe(int count) throws IOException {
|
|
this.ensure(count);
|
|
int offset = Unsafe.ARRAY_CHAR_BASE_OFFSET + Unsafe.ARRAY_CHAR_INDEX_SCALE * this.bufferIndex;
|
|
long value = UnsafeUtil.UNSAFE.getLong(this.buffer, offset);
|
|
this.bufferIndex += count;
|
|
long mask = count == 4 ? -1L : (1L << count * 16) - 1L;
|
|
return value & mask;
|
|
}
|
|
|
|
public boolean readBooleanValue() throws IOException {
|
|
this.ensure(4);
|
|
char read = this.buffer[this.bufferIndex++];
|
|
|
|
return switch (read) {
|
|
case 'F', 'f' -> {
|
|
this.consumeIgnoreCase("false", 1);
|
|
yield false;
|
|
}
|
|
case 'T', 't' -> {
|
|
this.consumeIgnoreCase("true", 1);
|
|
yield true;
|
|
}
|
|
default -> throw this.expecting(read, "true' or 'false");
|
|
};
|
|
}
|
|
|
|
public void skipBooleanValue() throws IOException {
|
|
this.readBooleanValue();
|
|
}
|
|
|
|
@Nullable
|
|
public Void readNullValue() throws IOException {
|
|
this.consumeIgnoreCase("null", 0);
|
|
return null;
|
|
}
|
|
|
|
public void skipNullValue() throws IOException {
|
|
this.consumeIgnoreCase("null", 0);
|
|
}
|
|
|
|
public double readDoubleValue() throws IOException {
|
|
int start = this.bufferIndex;
|
|
|
|
while (true) {
|
|
if (this.bufferIndex >= this.bufferSize) {
|
|
this.fill();
|
|
if (this.bufferIndex >= this.bufferSize) {
|
|
return JavaDoubleParser.parseDouble(this.buffer, start, this.bufferIndex - start);
|
|
}
|
|
}
|
|
|
|
while (this.bufferIndex < this.bufferSize) {
|
|
char read = this.buffer[this.bufferIndex];
|
|
switch (read) {
|
|
case '+':
|
|
case '-':
|
|
case '.':
|
|
case 'E':
|
|
case 'e':
|
|
this.bufferIndex++;
|
|
break;
|
|
default:
|
|
if (!Character.isDigit(read)) {
|
|
return JavaDoubleParser.parseDouble(this.buffer, start, this.bufferIndex - start);
|
|
}
|
|
|
|
this.bufferIndex++;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
public void skipDoubleValue() throws IOException {
|
|
while (true) {
|
|
if (this.bufferIndex >= this.bufferSize) {
|
|
this.fill();
|
|
if (this.bufferIndex >= this.bufferSize) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
while (this.bufferIndex < this.bufferSize) {
|
|
char read = this.buffer[this.bufferIndex];
|
|
switch (read) {
|
|
case '+':
|
|
case '-':
|
|
case '.':
|
|
case 'E':
|
|
case 'e':
|
|
this.bufferIndex++;
|
|
break;
|
|
default:
|
|
if (!Character.isDigit(read)) {
|
|
return;
|
|
}
|
|
|
|
this.bufferIndex++;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
public float readFloatValue() throws IOException {
|
|
return (float)this.readDoubleValue();
|
|
}
|
|
|
|
public void skipFloatValue() throws IOException {
|
|
this.skipDoubleValue();
|
|
}
|
|
|
|
public long readLongValue() throws IOException {
|
|
return this.readLongValue(10);
|
|
}
|
|
|
|
public long readLongValue(int radix) throws IOException {
|
|
if (this.tempSb == null) {
|
|
this.tempSb = new StringBuilder(1024);
|
|
}
|
|
|
|
while (true) {
|
|
if (this.bufferIndex >= this.bufferSize) {
|
|
this.fill();
|
|
if (this.bufferIndex >= this.bufferSize) {
|
|
long value = Long.parseLong(this.tempSb, 0, this.tempSb.length(), radix);
|
|
this.tempSb.setLength(0);
|
|
return value;
|
|
}
|
|
}
|
|
|
|
while (this.bufferIndex < this.bufferSize) {
|
|
char read = this.buffer[this.bufferIndex];
|
|
switch (read) {
|
|
case '+':
|
|
case '-':
|
|
case '.':
|
|
case 'E':
|
|
case 'e':
|
|
this.tempSb.append(read);
|
|
this.bufferIndex++;
|
|
break;
|
|
default:
|
|
if (Character.digit(read, radix) < 0) {
|
|
long value = Long.parseLong(this.tempSb, 0, this.tempSb.length(), radix);
|
|
this.tempSb.setLength(0);
|
|
return value;
|
|
}
|
|
|
|
this.tempSb.append(read);
|
|
this.bufferIndex++;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
public void skipLongValue() throws IOException {
|
|
this.skipLongValue(10);
|
|
}
|
|
|
|
public void skipLongValue(int radix) throws IOException {
|
|
while (true) {
|
|
if (this.bufferIndex >= this.bufferSize) {
|
|
this.fill();
|
|
if (this.bufferIndex >= this.bufferSize) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
while (this.bufferIndex < this.bufferSize) {
|
|
char read = this.buffer[this.bufferIndex];
|
|
switch (read) {
|
|
case '+':
|
|
case '-':
|
|
case '.':
|
|
case 'E':
|
|
case 'e':
|
|
this.bufferIndex++;
|
|
break;
|
|
default:
|
|
if (Character.digit(read, radix) < 0) {
|
|
return;
|
|
}
|
|
|
|
this.bufferIndex++;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
public int readIntValue() throws IOException {
|
|
return this.readIntValue(10);
|
|
}
|
|
|
|
public int readIntValue(int radix) throws IOException {
|
|
return (int)this.readLongValue(radix);
|
|
}
|
|
|
|
public byte readByteValue() throws IOException {
|
|
return this.readByteValue(10);
|
|
}
|
|
|
|
public byte readByteValue(int radix) throws IOException {
|
|
return (byte)this.readLongValue(radix);
|
|
}
|
|
|
|
public void skipIntValue() throws IOException {
|
|
this.skipLongValue();
|
|
}
|
|
|
|
public void skipIntValue(int radix) throws IOException {
|
|
this.skipLongValue(radix);
|
|
}
|
|
|
|
public void skipObject() throws IOException {
|
|
this.expect('{');
|
|
this.skipObjectContinued();
|
|
}
|
|
|
|
public void skipObjectContinued() throws IOException {
|
|
int count = 1;
|
|
|
|
while (true) {
|
|
this.ensure();
|
|
|
|
while (this.bufferIndex < this.bufferSize) {
|
|
char read = this.buffer[this.bufferIndex++];
|
|
switch (read) {
|
|
case '\n':
|
|
this.line++;
|
|
this.lineStart = this.bufferIndex;
|
|
break;
|
|
case '{':
|
|
count++;
|
|
break;
|
|
case '}':
|
|
if (--count == 0) {
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
public void skipArray() throws IOException {
|
|
this.expect('[');
|
|
this.skipArrayContinued();
|
|
}
|
|
|
|
public void skipArrayContinued() throws IOException {
|
|
int count = 1;
|
|
|
|
while (true) {
|
|
this.ensure();
|
|
|
|
while (this.bufferIndex < this.bufferSize) {
|
|
char read = this.buffer[this.bufferIndex++];
|
|
switch (read) {
|
|
case '\n':
|
|
this.line++;
|
|
this.lineStart = this.bufferIndex;
|
|
break;
|
|
case '[':
|
|
count++;
|
|
break;
|
|
case ']':
|
|
if (--count == 0) {
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
public void skipValue() throws IOException {
|
|
this.ensure();
|
|
char read = this.buffer[this.bufferIndex];
|
|
switch (read) {
|
|
case '"':
|
|
this.skipString();
|
|
break;
|
|
case '+':
|
|
case '-':
|
|
this.skipDoubleValue();
|
|
break;
|
|
case 'F':
|
|
case 'f':
|
|
this.consumeIgnoreCase("false", 0);
|
|
break;
|
|
case 'N':
|
|
case 'n':
|
|
this.skipNullValue();
|
|
break;
|
|
case 'T':
|
|
case 't':
|
|
this.consumeIgnoreCase("true", 0);
|
|
break;
|
|
case '[':
|
|
this.skipArray();
|
|
break;
|
|
case '{':
|
|
this.skipObject();
|
|
break;
|
|
default:
|
|
if (!Character.isDigit(read)) {
|
|
throw this.unexpectedChar(read);
|
|
}
|
|
|
|
this.skipDoubleValue();
|
|
}
|
|
}
|
|
|
|
@Nonnull
|
|
private IOException unexpectedEOF() {
|
|
return new IOException("Unexpected EOF!");
|
|
}
|
|
|
|
@Nonnull
|
|
private IOException unexpectedChar(char read) {
|
|
return new IOException("Unexpected character: " + Integer.toHexString(read) + ", '" + read + "'!");
|
|
}
|
|
|
|
@Nonnull
|
|
private IOException expecting(char read, char expect) {
|
|
return new IOException("Unexpected character: " + Integer.toHexString(read) + ", '" + read + "' expected '" + expect + "'!");
|
|
}
|
|
|
|
@Nonnull
|
|
private IOException expecting(char read, String expected) {
|
|
return new IOException("Unexpected character: " + Integer.toHexString(read) + ", '" + read + "' expected '" + expected + "'!");
|
|
}
|
|
|
|
@Nonnull
|
|
private IOException expectingWhile(char read, String expected, String reason) {
|
|
return new IOException("Unexpected character: " + Integer.toHexString(read) + ", '" + read + "' expected '" + expected + "' while " + reason + "!");
|
|
}
|
|
|
|
@Nonnull
|
|
private IOException expecting(char read, @Nonnull String expected, int index) {
|
|
return new IOException(
|
|
"Unexpected character: "
|
|
+ Integer.toHexString(read)
|
|
+ ", '"
|
|
+ read
|
|
+ "' when consuming string '"
|
|
+ expected
|
|
+ "' expected '"
|
|
+ expected.substring(index - 1)
|
|
+ "'!"
|
|
);
|
|
}
|
|
|
|
@Nonnull
|
|
@Override
|
|
public String toString() {
|
|
if (this.buffer == null) {
|
|
return "Closed RawJsonReader";
|
|
} else {
|
|
StringBuilder s = new StringBuilder("Index: ")
|
|
.append(this.streamIndex + this.bufferIndex)
|
|
.append(", StreamIndex: ")
|
|
.append(this.streamIndex)
|
|
.append(", BufferIndex: ")
|
|
.append(this.bufferIndex)
|
|
.append(", BufferSize: ")
|
|
.append(this.bufferSize)
|
|
.append(", Line: ")
|
|
.append(this.line)
|
|
.append(", MarkIndex: ")
|
|
.append(this.markIndex)
|
|
.append(", MarkLine: ")
|
|
.append(this.markLine)
|
|
.append('\n');
|
|
int lineStart = this.findLineStart(this.bufferIndex);
|
|
|
|
int lineNumber;
|
|
for (lineNumber = this.line; lineStart > 0 && lineNumber > this.line - 10; lineNumber--) {
|
|
lineStart = this.findLineStart(lineStart);
|
|
}
|
|
|
|
while (lineNumber < this.line) {
|
|
lineStart = this.appendLine(s, lineStart, lineNumber);
|
|
lineNumber++;
|
|
}
|
|
|
|
for (int var4 = this.appendProblemLine(s, lineStart, this.line); var4 < this.bufferSize && lineNumber < this.line + 10; lineNumber++) {
|
|
var4 = this.appendLine(s, var4, lineNumber);
|
|
}
|
|
|
|
return this.in == null ? "Buffer RawJsonReader: " + s : "Streamed RawJsonReader: " + s;
|
|
}
|
|
}
|
|
|
|
private int findLineStart(int index) {
|
|
index--;
|
|
|
|
while (index > 0 && this.buffer[index] != '\n') {
|
|
index--;
|
|
}
|
|
|
|
return index;
|
|
}
|
|
|
|
private int appendLine(@Nonnull StringBuilder sb, int index, int lineNumber) {
|
|
int lineStart = index + 1;
|
|
index++;
|
|
|
|
while (index < this.bufferSize && this.buffer[index] != '\n') {
|
|
index++;
|
|
}
|
|
|
|
sb.append("L").append(String.format("%3s", lineNumber)).append('|').append(this.buffer, lineStart, index - lineStart).append('\n');
|
|
return index;
|
|
}
|
|
|
|
private int appendProblemLine(@Nonnull StringBuilder sb, int index, int lineNumber) {
|
|
int lineStart = ++index;
|
|
|
|
while (index < this.bufferSize && this.buffer[index] != '\n') {
|
|
index++;
|
|
}
|
|
|
|
sb.append("L").append(String.format("%3s", lineNumber)).append('>').append(this.buffer, lineStart, index - lineStart).append('\n');
|
|
sb.append(" |");
|
|
sb.append("-".repeat(Math.max(0, this.bufferIndex - lineStart - 1)));
|
|
sb.append('^').append('\n');
|
|
return index;
|
|
}
|
|
|
|
@Nonnull
|
|
public static RawJsonReader fromRawString(String str) {
|
|
return fromJsonString("\"" + str + "\"");
|
|
}
|
|
|
|
@Nonnull
|
|
public static RawJsonReader fromJsonString(@Nonnull String str) {
|
|
return fromBuffer(str.toCharArray());
|
|
}
|
|
|
|
@Nonnull
|
|
public static RawJsonReader fromPath(@Nonnull Path path, @Nonnull char[] buffer) throws IOException {
|
|
return new RawJsonReader(new InputStreamReader(Files.newInputStream(path), StandardCharsets.UTF_8), buffer);
|
|
}
|
|
|
|
@Nonnull
|
|
public static RawJsonReader fromBuffer(@Nonnull char[] buffer) {
|
|
return new RawJsonReader(buffer);
|
|
}
|
|
|
|
public static boolean equalsIgnoreCase(char c1, char c2) {
|
|
if (c1 == c2) {
|
|
return true;
|
|
} else {
|
|
char u1 = Character.toUpperCase(c1);
|
|
char u2 = Character.toUpperCase(c2);
|
|
return u1 == u2 || Character.toLowerCase(u1) == Character.toLowerCase(u2);
|
|
}
|
|
}
|
|
|
|
@Deprecated
|
|
public static BsonDocument readBsonDocument(@Nonnull RawJsonReader reader) throws IOException {
|
|
reader.expect('{');
|
|
StringBuilder sb = new StringBuilder("{");
|
|
readBsonDocument0(reader, sb);
|
|
return BsonDocument.parse(sb.toString());
|
|
}
|
|
|
|
private static void readBsonDocument0(@Nonnull RawJsonReader reader, @Nonnull StringBuilder sb) throws IOException {
|
|
int count = 1;
|
|
|
|
int read;
|
|
while ((read = reader.read()) != -1) {
|
|
sb.append((char)read);
|
|
switch (read) {
|
|
case 10:
|
|
reader.line++;
|
|
reader.lineStart = reader.bufferIndex;
|
|
break;
|
|
case 91:
|
|
readBsonArray0(reader, sb);
|
|
break;
|
|
case 123:
|
|
count++;
|
|
break;
|
|
case 125:
|
|
if (--count == 0) {
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
throw reader.unexpectedEOF();
|
|
}
|
|
|
|
@Deprecated
|
|
public static BsonArray readBsonArray(@Nonnull RawJsonReader reader) throws IOException {
|
|
reader.expect('[');
|
|
StringBuilder sb = new StringBuilder("[");
|
|
readBsonArray0(reader, sb);
|
|
return BsonArray.parse(sb.toString());
|
|
}
|
|
|
|
private static void readBsonArray0(@Nonnull RawJsonReader reader, @Nonnull StringBuilder sb) throws IOException {
|
|
int count = 1;
|
|
|
|
int read;
|
|
while ((read = reader.read()) != -1) {
|
|
sb.append((char)read);
|
|
switch (read) {
|
|
case 10:
|
|
reader.line++;
|
|
reader.lineStart = reader.bufferIndex;
|
|
break;
|
|
case 91:
|
|
count++;
|
|
break;
|
|
case 93:
|
|
if (--count == 0) {
|
|
return;
|
|
}
|
|
break;
|
|
case 123:
|
|
readBsonDocument0(reader, sb);
|
|
}
|
|
}
|
|
|
|
throw reader.unexpectedEOF();
|
|
}
|
|
|
|
@Deprecated
|
|
public static BsonValue readBsonValue(@Nonnull RawJsonReader reader) throws IOException {
|
|
int read = reader.peek();
|
|
if (read == -1) {
|
|
throw reader.unexpectedEOF();
|
|
} else {
|
|
return (BsonValue)(switch (read) {
|
|
case 34 -> new BsonString(reader.readString());
|
|
case 43, 45 -> new BsonDouble(reader.readDoubleValue());
|
|
case 70, 84, 102, 116 -> reader.readBooleanValue() ? BsonBoolean.TRUE : BsonBoolean.FALSE;
|
|
case 78, 110 -> {
|
|
reader.skipNullValue();
|
|
yield BsonNull.VALUE;
|
|
}
|
|
case 91 -> readBsonArray(reader);
|
|
case 123 -> readBsonDocument(reader);
|
|
default -> {
|
|
if (!Character.isDigit(read)) {
|
|
throw reader.unexpectedChar((char)read);
|
|
}
|
|
|
|
yield new BsonDouble(reader.readDoubleValue());
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
public static boolean seekToKey(@Nonnull RawJsonReader reader, @Nonnull String search) throws IOException {
|
|
reader.consumeWhiteSpace();
|
|
reader.expect('{');
|
|
reader.consumeWhiteSpace();
|
|
if (reader.tryConsume('}')) {
|
|
return false;
|
|
} else {
|
|
while (true) {
|
|
reader.expect('"');
|
|
if (reader.tryConsume(search) && reader.tryConsume('"')) {
|
|
reader.consumeWhiteSpace();
|
|
reader.expect(':');
|
|
reader.consumeWhiteSpace();
|
|
return true;
|
|
}
|
|
|
|
reader.skipRemainingString();
|
|
reader.consumeWhiteSpace();
|
|
reader.expect(':');
|
|
reader.consumeWhiteSpace();
|
|
reader.skipValue();
|
|
reader.consumeWhiteSpace();
|
|
if (reader.tryConsumeOrExpect('}', ',')) {
|
|
return false;
|
|
}
|
|
|
|
reader.consumeWhiteSpace();
|
|
}
|
|
}
|
|
}
|
|
|
|
@Nullable
|
|
public static String seekToKeyFromObjectStart(@Nonnull RawJsonReader reader, @Nonnull String search1, @Nonnull String search2) throws IOException {
|
|
reader.consumeWhiteSpace();
|
|
reader.expect('{');
|
|
reader.consumeWhiteSpace();
|
|
if (reader.tryConsume('}')) {
|
|
return null;
|
|
} else {
|
|
while (true) {
|
|
reader.expect('"');
|
|
int search1Index = reader.tryConsumeSome(search1, 0);
|
|
if (search1Index == search1.length() && reader.tryConsume('"')) {
|
|
reader.consumeWhiteSpace();
|
|
reader.expect(':');
|
|
reader.consumeWhiteSpace();
|
|
return search1;
|
|
}
|
|
|
|
if (reader.tryConsume(search2, search1Index) && reader.tryConsume('"')) {
|
|
reader.consumeWhiteSpace();
|
|
reader.expect(':');
|
|
reader.consumeWhiteSpace();
|
|
return search2;
|
|
}
|
|
|
|
reader.skipRemainingString();
|
|
reader.consumeWhiteSpace();
|
|
reader.expect(':');
|
|
reader.consumeWhiteSpace();
|
|
reader.skipValue();
|
|
reader.consumeWhiteSpace();
|
|
if (reader.tryConsumeOrExpect('}', ',')) {
|
|
return null;
|
|
}
|
|
|
|
reader.consumeWhiteSpace();
|
|
}
|
|
}
|
|
}
|
|
|
|
@Nullable
|
|
public static String seekToKeyFromObjectContinued(@Nonnull RawJsonReader reader, @Nonnull String search1, @Nonnull String search2) throws IOException {
|
|
reader.consumeWhiteSpace();
|
|
if (reader.tryConsumeOrExpect('}', ',')) {
|
|
return null;
|
|
} else {
|
|
reader.consumeWhiteSpace();
|
|
|
|
while (true) {
|
|
reader.expect('"');
|
|
int search1Index = reader.tryConsumeSome(search1, 0);
|
|
if (search1Index == search1.length() && reader.tryConsume('"')) {
|
|
reader.consumeWhiteSpace();
|
|
reader.expect(':');
|
|
reader.consumeWhiteSpace();
|
|
return search1;
|
|
}
|
|
|
|
if (reader.tryConsume(search2, search1Index) && reader.tryConsume('"')) {
|
|
reader.consumeWhiteSpace();
|
|
reader.expect(':');
|
|
reader.consumeWhiteSpace();
|
|
return search2;
|
|
}
|
|
|
|
reader.skipRemainingString();
|
|
reader.consumeWhiteSpace();
|
|
reader.expect(':');
|
|
reader.consumeWhiteSpace();
|
|
reader.skipValue();
|
|
reader.consumeWhiteSpace();
|
|
if (reader.tryConsumeOrExpect('}', ',')) {
|
|
return null;
|
|
}
|
|
|
|
reader.consumeWhiteSpace();
|
|
}
|
|
}
|
|
}
|
|
|
|
public static void validateBsonDocument(@Nonnull RawJsonReader reader) throws IOException {
|
|
reader.expect('{');
|
|
reader.consumeWhiteSpace();
|
|
if (!reader.tryConsume('}')) {
|
|
while (true) {
|
|
reader.skipString();
|
|
reader.consumeWhiteSpace();
|
|
reader.expect(':');
|
|
reader.consumeWhiteSpace();
|
|
validateBsonValue(reader);
|
|
reader.consumeWhiteSpace();
|
|
if (reader.tryConsumeOrExpect('}', ',')) {
|
|
return;
|
|
}
|
|
|
|
reader.consumeWhiteSpace();
|
|
}
|
|
}
|
|
}
|
|
|
|
public static void validateBsonArray(@Nonnull RawJsonReader reader) throws IOException {
|
|
reader.expect('[');
|
|
reader.consumeWhiteSpace();
|
|
if (!reader.tryConsume(']')) {
|
|
while (true) {
|
|
validateBsonValue(reader);
|
|
reader.consumeWhiteSpace();
|
|
if (reader.tryConsumeOrExpect(']', ',')) {
|
|
return;
|
|
}
|
|
|
|
reader.consumeWhiteSpace();
|
|
}
|
|
}
|
|
}
|
|
|
|
public static void validateBsonValue(@Nonnull RawJsonReader reader) throws IOException {
|
|
int read = reader.peek();
|
|
if (read == -1) {
|
|
throw reader.unexpectedEOF();
|
|
} else {
|
|
switch (read) {
|
|
case 34:
|
|
reader.skipString();
|
|
break;
|
|
case 43:
|
|
case 45:
|
|
reader.readDoubleValue();
|
|
break;
|
|
case 70:
|
|
case 84:
|
|
case 102:
|
|
case 116:
|
|
reader.readBooleanValue();
|
|
break;
|
|
case 78:
|
|
case 110:
|
|
reader.skipNullValue();
|
|
break;
|
|
case 91:
|
|
validateBsonArray(reader);
|
|
break;
|
|
case 123:
|
|
validateBsonDocument(reader);
|
|
break;
|
|
default:
|
|
if (Character.isDigit(read)) {
|
|
reader.readDoubleValue();
|
|
return;
|
|
}
|
|
|
|
throw reader.unexpectedChar((char)read);
|
|
}
|
|
}
|
|
}
|
|
|
|
@Nullable
|
|
public static <T> T readSync(@Nonnull Path path, @Nonnull Codec<T> codec, @Nonnull HytaleLogger logger) throws IOException {
|
|
char[] buffer = READ_BUFFER.get();
|
|
RawJsonReader reader = fromPath(path, buffer);
|
|
|
|
Object var7;
|
|
try {
|
|
ExtraInfo extraInfo = ExtraInfo.THREAD_LOCAL.get();
|
|
T value = codec.decodeJson(reader, extraInfo);
|
|
extraInfo.getValidationResults().logOrThrowValidatorExceptions(logger);
|
|
var7 = value;
|
|
} finally {
|
|
char[] newBuffer = reader.closeAndTakeBuffer();
|
|
if (newBuffer.length > buffer.length) {
|
|
READ_BUFFER.set(newBuffer);
|
|
}
|
|
}
|
|
|
|
return (T)var7;
|
|
}
|
|
|
|
@Nullable
|
|
public static <T> T readSyncWithBak(@Nonnull Path path, @Nonnull Codec<T> codec, @Nonnull HytaleLogger logger) {
|
|
try {
|
|
return readSync(path, codec, logger);
|
|
} catch (IOException var8) {
|
|
Path backupPath = path.resolveSibling(path.getFileName() + ".bak");
|
|
if (var8 instanceof NoSuchFileException && !Files.exists(backupPath)) {
|
|
return null;
|
|
} else {
|
|
if (Sentry.isEnabled()) {
|
|
Sentry.captureException(var8);
|
|
}
|
|
|
|
((HytaleLogger.Api)logger.at(Level.SEVERE).withCause(var8)).log("Failed to load from primary file %s, trying backup file", path);
|
|
|
|
try {
|
|
T value = readSync(backupPath, codec, logger);
|
|
logger.at(Level.WARNING).log("Loaded from backup file %s after primary file %s failed to load", backupPath, path);
|
|
return value;
|
|
} catch (NoSuchFileException var6) {
|
|
return null;
|
|
} catch (IOException var7) {
|
|
((HytaleLogger.Api)logger.at(Level.WARNING).withCause(var8)).log("Failed to load from both %s and backup file %s", path, backupPath);
|
|
return null;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|