
com.dyuproject.protostuff.JsonXByteArrayInput Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of protostuff-json Show documentation
Show all versions of protostuff-json Show documentation
protostuff serialization using json format
The newest version!
//========================================================================
//Copyright 2022 David Yu
//------------------------------------------------------------------------
//Licensed under the Apache License, Version 2.0 (the "License");
//you may not use this file except in compliance with the License.
//You may obtain a copy of the License at
//http://www.apache.org/licenses/LICENSE-2.0
//Unless required by applicable law or agreed to in writing, software
//distributed under the License is distributed on an "AS IS" BASIS,
//WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//See the License for the specific language governing permissions and
//limitations under the License.
//========================================================================
package com.dyuproject.protostuff;
import java.io.IOException;
/**
* An byte array based input used for reading data with json format.
*
* @author David Yu
* @created Mar 24, 2022
*/
public final class JsonXByteArrayInput implements Input
{
static final byte
VALUE_NUM = 0,
VALUE_NULL = 'n',
START_OBJECT = '{',
END_OBJECT = '}',
START_ARRAY = '[',
END_ARRAY = ']',
COMMA = ',',
COLON = ':',
QUOTE = '"';
private static final int[] intDigits = new int[127];
private static final int[] floatDigits = new int[127];
static final int END_OF_NUMBER = -2;
static final int DOT_IN_NUMBER = -3;
static final int INVALID_CHAR_FOR_NUMBER = -1;
// for string
private static final int[] hexDigits = new int['f' + 1];
// for skip
private static final byte[] breaks = new byte[256];
static
{
for (int i = 0; i < floatDigits.length; i++)
{
floatDigits[i] = INVALID_CHAR_FOR_NUMBER;
intDigits[i] = INVALID_CHAR_FOR_NUMBER;
}
for (int i = '0'; i <= '9'; ++i)
{
floatDigits[i] = (i - '0');
intDigits[i] = (i - '0');
}
floatDigits[','] = END_OF_NUMBER;
floatDigits[']'] = END_OF_NUMBER;
floatDigits['}'] = END_OF_NUMBER;
floatDigits[' '] = END_OF_NUMBER;
floatDigits['.'] = DOT_IN_NUMBER;
for (int i = 0; i < hexDigits.length; i++)
hexDigits[i] = -1;
for (int i = '0'; i <= '9'; ++i)
hexDigits[i] = (i - '0');
for (int i = 'a'; i <= 'f'; ++i)
hexDigits[i] = ((i - 'a') + 10);
for (int i = 'A'; i <= 'F'; ++i)
hexDigits[i] = ((i - 'A') + 10);
breaks[' '] = 1;
breaks['\t'] = 1;
breaks['\n'] = 1;
breaks['\r'] = 1;
breaks[','] = 2;
breaks['}'] = 2;
breaks[']'] = 2;
}
private byte[] buf;
private int start, offset, limit;
final boolean allowQuotedInt64;
final byte alphaNumericPrefixChar;
final char[] charBuf;
final int charOffset, charLimit;
final boolean charBufAsLimit;
final int previewMaxLen;
private byte token = 0;
// resettable
private boolean lastRepeated;
private int lastNumber;
public JsonXByteArrayInput(byte[] buf, int offset, int len, boolean allowQuotedInt64,
byte alphaNumericPrefixChar,
char[] charBuf, int charOffset, int charLen, boolean charBufAsLimit,
int previewMaxLen)
{
this.buf = buf;
this.start = offset;
this.offset = offset;
this.limit = offset + len;
this.allowQuotedInt64 = allowQuotedInt64;
this.alphaNumericPrefixChar = alphaNumericPrefixChar;
this.charBuf = charBuf;
this.charOffset = charOffset;
this.charLimit = charOffset + charLen;
// whether we should error when the limit is exceeded
this.charBufAsLimit = charBufAsLimit;
this.previewMaxLen = previewMaxLen;
}
/**
* Returns the current offset;
*/
int currentOffset()
{
return offset;
}
/**
* Gets the last field number read.
*/
public int getLastNumber()
{
return lastNumber;
}
/**
* Returns true if the last read field was a repeated field.
*/
public boolean isLastRepeated()
{
return lastRepeated;
}
/**
* Resets this input.
*/
public JsonXByteArrayInput reset()
{
//depth = 0;
token = 0;
lastRepeated = false;
lastNumber = 0;
return this;
}
/**
* Resets this input.
*/
public JsonXByteArrayInput reset(int offset)
{
this.start = offset;
this.offset = offset;
return reset();
}
/**
* Resets this input.
*/
public JsonXByteArrayInput reset(int offset, int len)
{
this.start = offset;
this.offset = offset;
this.limit = offset + len;
return reset();
}
/**
* Sets the offset and limit (which effectively re-uses this input).
*/
public JsonXByteArrayInput setBounds(int offset, int limit)
{
this.start = offset;
this.offset = offset;
this.limit = limit;
return reset();
}
/**
* Resets this input.
*/
public JsonXByteArrayInput reset(byte[] buf, int offset, int len)
{
this.buf = buf;
return reset(offset, len);
}
// ==================================================
private static int findTokenOrEnd(byte token, int offset, int max,
byte[] buf, int limit)
{
if (offset == limit)
return limit;
for (int i = 0, remaining = Math.min(limit - offset, max);
i < remaining && token != buf[offset];
i++, offset++);
return offset;
}
private static int rfindTokenOrStart(byte token, int offset, int max, int maxCount, int diff,
byte[] buf, final int start, char[] out, int outOffset)
{
out[outOffset] = 0;
final int remaining = Math.min(offset - start, max);
if (remaining == 0)
return 0;
int i = 0, seenCount = 0;
for (int end = offset; i < remaining; i++, offset--)
{
if (token == buf[offset])
{
if (1 == ++seenCount)
out[outOffset] = (char)(end - offset + diff);
if (seenCount == maxCount)
break;
}
}
return i == remaining ? 0 : offset + diff;
}
private final JsonInputException reportError(String op, String msg) {
final StringBuilder sb = new StringBuilder()
.append(op)
.append(':')
.append(' ')
.append(msg)
.append(" at offset: ")
.append(offset - 1)
.append('\n');
if (previewMaxLen == 0)
return new JsonInputException(sb.toString());
final int diff = 1;
int end = findTokenOrEnd((byte)'\n', offset, 16, buf, limit);
int start = rfindTokenOrStart((byte)'\n', offset - 1, 250, 5, diff, buf, this.start,
charBuf, charOffset);
int len = end - start;
if (len > previewMaxLen)
return new JsonInputException(sb.toString());
String preview;
try
{
preview = new String(buf, start, len, "UTF-8");
}
catch (Exception e)
{
preview = "";
}
if (!preview.isEmpty())
{
sb.append(preview).append('\n');
len = 0xFFFF & charBuf[charOffset];
if (len == 0)
{
// no newlines
end = offset;
while (end != ++start) sb.append('-');
}
else
{
len -= diff;
while (0 < --len) sb.append('-');
}
sb.append('^');
}
return new JsonInputException(sb.toString());
}
private String tokenToString()
{
return new String(new char[]{ (char)token });
}
/*
private String tokenToString(byte b)
{
return new String(new char[]{ (char)b });
}
*/
byte currentToken()
{
return token;
}
byte nextToken() throws IOException
{
int i = offset;
byte c;
for (;;)
{
c = buf[i++];
switch (c)
{
case ' ':
case '\n':
case '\r':
case '\t':
if (i == limit)
throw reportError("nextToken", "Missing \"}\"");
continue;
default:
offset = i;
token = c;
return c;
}
}
}
private byte skip() throws IOException
{
switch (token) {
case '"':
//*IterImpl.*/skipString(offset + 1);
return skipUntilBreakToken(skipString(offset), true);
case '-':
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
//*IterImpl.*/skipUntilBreak(offset);
return skipUntilBreakToken(offset, true);
case 't':
case 'n':
//*IterImpl.*/skipFixedBytes(iter, 3); // true or null
return skipUntilBreakToken(offset + 3, true);
case 'f':
///*IterImpl.*/skipFixedBytes(iter, 4); // false
return skipUntilBreakToken(offset + 4, true);
case '[':
return skipUntilBreakToken(skipArray(offset), true);
case '{':
return skipUntilBreakToken(skipObject(offset), true);
default:
throw reportError("skip", "could not skip: " + tokenToString());
}
}
private int skipArray(int offset) throws IOException
{
int level = 1;
int i = offset;
while (i < limit)
{
switch (buf[i++])
{
case '"': // If inside string, skip it
i = skipString(i);
break;
case '[': // If open symbol, increase level
level++;
break;
case ']': // If close symbol, decrease level
// If we have returned to the original level, we're done
if (0 == --level)
return i;
break;
}
}
throw reportError("skipArray", "incomplete array");
}
private int skipObject(int offset) throws IOException
{
int level = 1;
int i = offset;
while (i < limit)
{
switch (buf[i++])
{
case '"': // If inside string, skip it
i = skipString(i);
break;
case '{': // If open symbol, increase level
level++;
break;
case '}': // If close symbol, decrease level
// If we have returned to the original level, we're done
if (0 == --level)
return i;
break;
}
}
throw reportError("skipObject", "Incomplete object");
}
/**
* Does not end with shitespace.
* @throws IOException
*/
private byte skipUntilBreakToken(int offset, boolean nextTokenOnComma) throws IOException
{
// true, false, null, number
while (offset < limit && 2 != breaks[0xFF & buf[offset]]) offset++;
if (offset >= limit)
throw reportError("skipUntilBreakToken", "Truncated");
byte c = buf[offset];
this.offset = ++offset;
if (!nextTokenOnComma || COMMA != c)
token = c;
else if (offset != limit)
c = nextToken();
else
throw reportError("skipUntilBreakToken", "Truncated");
return c;
}
private int skipString(int start) throws IOException
{
int end = findStringEnd(buf, start, limit);
if (end == -1)
throw reportError("skipString", "Incomplete string");
return end + 1;
}
static int findStringEnd(byte[] input, int offset, int limit)
{
byte c;
boolean escaped = false;
for (int i = offset, j = 0; i < limit; i++)
{
if ('"' != (c = input[i]))
{
escaped = c == '\\';
continue;
}
if (!escaped)
return i;
for (j = i - 1;;)
{
if (j < offset || '\\' != input[j--])
{
// even number of backslashes
// either end of buffer, or " found
return i;
}
if (j < offset || '\\' != input[j--])
{
// odd number of backslashes
// it is \" or \\\"
break;
}
}
}
return -1;
}
int readNumericField() throws IOException
{
if (offset == limit)
throw reportError("readField", "Truncated");
if (alphaNumericPrefixChar != 0)
{
if (alphaNumericPrefixChar != buf[offset])
{
throw reportError("readField", "Invalid field prefix char: " +
new String(new char[]{(char)buf[offset]}));
}
if (limit == ++offset)
throw reportError("readField", "Truncated");
}
final int value = parseInt(buf[offset++]);
if (QUOTE != token)
throw reportError("readField", "Invalid field number");
if (COLON != nextToken() || offset == limit)
throw reportError("readField", "Invalid field value for the field: " + value);
for (int b, c = 0xFF & nextToken(); 0 != (b = breaks[c]); c = 0xFF & nextToken())
{
if (b == 2 || offset == limit)
throw reportError("readField", "Invalid field value for the field: " + value);
}
return value;
}
// ==================================================
public void handleUnknownField(int fieldNumber, Schema schema) throws IOException
{
if (lastRepeated)
{
lastRepeated = false;
skipUntilBreakToken(skipArray(offset - 1), true);
}
else
{
skip();
}
}
public int readFieldNumber(final Schema schema) throws IOException
{
byte c = token;
if (lastRepeated)
{
if (VALUE_NULL != c)
return lastNumber;
// ignore null fields
while (VALUE_NULL == (c = skipUntilBreakToken(offset, true)));
if (END_ARRAY != c)
return lastNumber;
lastNumber = 0;
lastRepeated = false;
}
for (int fieldNumber;;)
{
if (END_OBJECT == c)
return 0;
if (QUOTE != c)
{
throw reportError("readFieldNumber", "Expected token: $field: but was " +
tokenToString() + " on message " + schema.messageFullName());
}
fieldNumber = readNumericField();
c = token;
if (VALUE_NULL == c)
{
// ignore null field
c = skipUntilBreakToken(offset + 3, true);
continue;
}
if (fieldNumber < 1)
{
c = skip();
continue;
}
if (START_ARRAY != c)
{
lastRepeated = false;
}
else if (END_ARRAY != nextToken())
{
lastRepeated = true;
}
else
{
// ignore empty array field
c = skipUntilBreakToken(offset, true);
continue;
}
lastNumber = fieldNumber;
return fieldNumber;
}
}
private int parseInt(byte b) throws IOException
{
boolean isNeg = b == '-';
if (isNeg)
{
if (offset == limit)
throw reportError("readInt32", "Truncated");
token = b = buf[offset++];
}
if (b < '0' || b > '9')
throw reportError("readInt32", "Expected 0~9");//numberError()
int x = '0' - b;
if (x == 0)
{
if (offset == limit)
throw reportError("readInt32", "Truncated");
token = b = buf[offset++];
if (b == '.')
throw reportError("readInt32", "Invalid integer");
if (b >= '0' && b <= '9')
throw reportError("readInt32", "Leading zero invalid");
return x;
}
int pos = offset;
while (pos < limit && '0' <= (b = buf[pos]) && b <= '9')
{
if (x < -214748364 || 0 < (x = x * 10 + ('0' - b)))
throw reportError("readInt32", "Invalid number (overflow)");
pos++;
}
if (pos == limit)
throw reportError("readInt32", "Truncated");
token = b;
offset = pos + 1;
if ((b | 0x20) == 'e' || b == '.')
throw reportError("readInt32", "Invalid integer");
if (!isNeg)
{
if (x == -2147483648) throw reportError("readInt32", "Invalid integer (overflow)");
x = -x;
}
return x;
}
private long parseLong(byte b) throws IOException
{
boolean isNeg = b == '-';
if (isNeg)
{
if (offset == limit)
throw reportError("readInt64", "Truncated");
token = b = buf[offset++];
}
if (b < '0' || b > '9')
throw reportError("readInt64", "Expected 0~9");//numberError()
long x = '0' - b;
if (x == 0)
{
if (offset == limit)
throw reportError("readInt64", "Truncated");
token = b = buf[offset++];
if (b == '.')
throw reportError("readInt64", "Invalid integer");
if (b >= '0' && b <= '9')
throw reportError("readInt64", "Leading zero invalid");
return x;
}
int pos = offset;
while (pos < limit && '0' <= (b = buf[pos]) && b <= '9')
{
if (x < -922337203685477580L || 0 < (x = x * 10 + ('0' - b)))
throw reportError("readInt64", "Invalid integer (overflow)");
pos++;
}
if (pos == limit)
throw reportError("readInt64", "Truncated");
token = b;
offset = pos + 1;
if ((b | 0x20) == 'e' || b == '.')
throw reportError("readInt64", "Invalid integer");//numberError(pos)
if (!isNeg)
{
if (x == -9223372036854775808L) throw reportError("readInt64", "Invalid integer (overflow)");
x = -x;
}
return x;
}
public boolean readBool() throws IOException
{
final boolean value;
byte c;
switch (token)
{
case 'f':
c = skipUntilBreakToken(offset + 4, true);
value = false;
break;
case 't':
c = skipUntilBreakToken(offset + 3, true);
value = true;
break;
default:
throw reportError("readBool", "Expected token: true/false but was " + tokenToString());
}
if (lastRepeated && c == END_ARRAY)
{
skipUntilBreakToken(offset, true);
lastRepeated = false;
}
return value;
}
public double readDouble() throws IOException
{
final int start = offset - 1;
byte c = skipUntilBreakToken(offset, false);
int lastIdx = offset - 1 - 1; // exclude the break char
// skip trailing whitespace
while (Character.isWhitespace((char)buf[lastIdx])) lastIdx--;
final String str;
if (QUOTE != buf[start])
{
str = new String(buf, start, lastIdx + 1 - start);
}
else if (QUOTE == buf[lastIdx])
{
// exclude quotes
str = new String(buf, start + 1, lastIdx - 1 - start);
}
else
{
throw reportError("readDouble", "Invalid number/string");
}
final double value;
if ("infinity".equals(str))
value = Double.POSITIVE_INFINITY;
else if ("-infinity".equals(str))
value = Double.NEGATIVE_INFINITY;
else
value = Double.parseDouble(str);
if (c == COMMA)
{
if (offset == limit)
throw reportError("readDouble", "Truncated");
c = nextToken();
}
if (lastRepeated && c == END_ARRAY)
{
skipUntilBreakToken(offset, true);
lastRepeated = false;
}
return value;
}
public int readEnum() throws IOException
{
return readInt32();
}
public int readEnumIdx(EnumMapping mapping) throws IOException
{
final int start = offset - 1;
byte c = skipUntilBreakToken(offset, false);
int lastIdx = offset - 1 - 1; // exclude the break char
// skip trailing whitespace
while (Character.isWhitespace((char)buf[lastIdx])) lastIdx--;
final Integer idx;
if (QUOTE != buf[start])
{
idx = mapping.numberIdxMap.get(NumberParser.parseInt(buf, start, lastIdx + 1 - start, 10));
}
else if (QUOTE == buf[lastIdx])
{
// exclude quotes
idx = mapping.nameIdxMap.get(new String(buf, start + 1, lastIdx - 1 - start));
}
else
{
throw reportError("readEnumIdx", "Invalid number/string");
}
if (c == COMMA)
{
if (offset == limit)
throw reportError("readEnumIdx", "Truncated");
c = nextToken();
}
if (lastRepeated && c == END_ARRAY)
{
skipUntilBreakToken(offset, true);
lastRepeated = false;
}
return idx == null ? -1 : idx.intValue();
}
public int readFixed32() throws IOException
{
return readInt32();
}
public long readFixed64() throws IOException
{
return readInt64();
}
public float readFloat() throws IOException
{
return (float)readDouble();
}
public int readInt32() throws IOException
{
final int value = parseInt(token);
byte c = token;
if (0 == breaks[0xFF & c])
throw reportError("readInt32", "Expected ',]}' but was " + tokenToString());
if (c == COMMA)
{
if (offset == limit)
throw reportError("readInt32", "Truncated");
c = nextToken();
}
if (lastRepeated && c == END_ARRAY)
{
skipUntilBreakToken(offset, true);
lastRepeated = false;
}
return value;
}
public long readInt64() throws IOException
{
if (allowQuotedInt64)
return flexReadInt64();
final long value = parseLong(token);
byte c = token;
if (0 == breaks[0xFF & c])
throw reportError("readInt64", "Expected ',]}' but was " + tokenToString());
if (c == COMMA)
{
if (offset == limit)
throw reportError("readInt64", "Truncated");
c = nextToken();
}
if (lastRepeated && c == END_ARRAY)
{
skipUntilBreakToken(offset, true);
lastRepeated = false;
}
return value;
}
private long flexReadInt64() throws IOException
{
final int start = offset - 1;
byte c = skipUntilBreakToken(offset, false);
int lastIdx = offset - 1 - 1; // exclude the break char
// skip trailing whitespace
while (Character.isWhitespace((char)buf[lastIdx])) lastIdx--;
final long value;
if (QUOTE != buf[start])
{
value = NumberParser.parseLong(buf, start, lastIdx + 1 - start, 10);
}
else if (QUOTE == buf[lastIdx])
{
// exclude quotes
value = Double.doubleToRawLongBits(Double.parseDouble(
new String(buf, start + 1, lastIdx - 1 - start)));
}
else
{
throw reportError("readLong", "Invalid number/string");
}
if (c == COMMA)
{
if (offset == limit)
throw reportError("readLong", "Truncated");
c = nextToken();
}
if (lastRepeated && c == END_ARRAY)
{
skipUntilBreakToken(offset, true);
lastRepeated = false;
}
return value;
}
public int readSFixed32() throws IOException
{
return readInt32();
}
public long readSFixed64() throws IOException
{
return readInt64();
}
public int readSInt32() throws IOException
{
return readInt32();
}
public long readSInt64() throws IOException
{
return readInt64();
}
public int readUInt32() throws IOException
{
return readInt32();
}
public long readUInt64() throws IOException
{
return readInt64();
}
public ByteString readBytes() throws IOException
{
return ByteString.wrap(readByteArray());
}
public byte[] readByteArray() throws IOException
{
if (QUOTE != token)
throw reportError("readByteArray", "Expected base64 string");
final int start = offset;
final int len = (offset = skipString(start)) - 1 - start; // exclude trailing quote
token = QUOTE;
final byte[] value = B64Code.decode(buf, start, len);
byte b = skipUntilBreakToken(offset, true);
if (lastRepeated && END_ARRAY == b)
{
skipUntilBreakToken(offset, true);
lastRepeated = false;
}
return value;
}
public String readString() throws IOException
{
if (QUOTE != token)
throw reportError("readString", "Expected string");
final String value = parseString();
byte b = skipUntilBreakToken(offset, true);
if (lastRepeated && END_ARRAY == b)
{
skipUntilBreakToken(offset, true);
lastRepeated = false;
}
return value;
}
public T mergeObject(T value, final Schema schema) throws IOException
{
if (START_OBJECT != token)
{
throw reportError("mergeObject", "Expected token: { but was " +
tokenToString() + " on " + lastNumber + " of message " +
schema.messageFullName());
}
nextToken();
final int lastNumber = this.lastNumber;
final boolean lastRepeated = this.lastRepeated;
// reset
this.lastRepeated = false;
if (value == null)
value = schema.newMessage();
schema.mergeFrom(this, value);
if (END_OBJECT != token)
{
throw reportError("mergeObject", "Expected token: } but was " +
tokenToString() + " on " + lastNumber + " of message " +
schema.messageFullName());
}
if (!schema.isInitialized(value))
throw new UninitializedMessageException(value, schema);
// restore state
this.lastNumber = lastNumber;
this.lastRepeated = lastRepeated;
if (offset == limit)
throw reportError("readObject", "Truncated");
byte c = skipUntilBreakToken(offset, true);
if (lastRepeated && END_ARRAY == c)
{
skipUntilBreakToken(offset, true);
this.lastRepeated = false;
}
return value;
}
public void transferByteRangeTo(Output output, boolean utf8String, int fieldNumber,
boolean repeated) throws IOException
{
if (utf8String)
output.writeString(fieldNumber, readString(), repeated);
else
output.writeByteArray(fieldNumber, readByteArray(), repeated);
}
public void transferEnumTo(Output output, EnumMapping mapping,
int fieldNumber, boolean repeated) throws IOException
{
final int start = offset - 1;
byte c = skipUntilBreakToken(offset, false);
int lastIdx = offset - 1 - 1; // exclude the break char
// skip trailing whitespace
while (Character.isWhitespace((char)buf[lastIdx])) lastIdx--;
int number = 0;
String name = null;
if (QUOTE != buf[start])
{
number = NumberParser.parseInt(buf, start, lastIdx + 1 - start, 10);
}
else if (QUOTE == buf[lastIdx])
{
// TODO could use transferByteRangeTo since this is ascii
// exclude quotes
name = new String(buf, start + 1, lastIdx - 1 - start);
}
else
{
throw reportError("readEnumIdx", "Invalid number/string");
}
if (c == COMMA)
{
if (offset == limit)
throw reportError("readEnumIdx", "Truncated");
c = nextToken();
}
if (lastRepeated && c == END_ARRAY)
{
skipUntilBreakToken(offset, true);
lastRepeated = false;
}
Integer idx;
if (name == null)
{
if (!output.isEnumsByName())
output.writeEnum(fieldNumber, number, repeated);
else if (null != (idx = mapping.numberIdxMap.get(number)))
output.writeEnumFromIdx(fieldNumber, idx.intValue(), mapping, repeated);
}
else if (output.isEnumsByName())
{
output.writeString(fieldNumber, name, repeated);
}
else if (null != (idx = mapping.nameIdxMap.get(name)))
{
output.writeEnumFromIdx(fieldNumber, idx.intValue(), mapping, repeated);
}
}
private String parseString() throws IOException
{
final byte[] buffer = buf;
final char[] chars = charBuf;
final int startIndex = offset;
if (offset == limit)
throw reportError("readString", "Truncated");
byte bb;
int ci = startIndex;
char[] _tmp = chars;
final int remaining = limit - offset;
final int maxSize = charLimit - charOffset;
int tmpSize = maxSize < remaining ? maxSize : remaining;
int i = 0;
while (i < tmpSize)
{
if ('"' == (bb = buffer[ci++]))
{
token = bb;
offset = ci;
return new String(chars, charOffset, i);
}
// If we encounter a backslash, which is a beginning of an escape
// sequence
// or a high bit was set - indicating an UTF-8 encoded multibyte
// character,
// there is no chance that we can decode the string without
// instantiating
// a temporary buffer, so quit this loop
if ((bb ^ '\\') < 1)
break;
_tmp[charOffset + i] = (char)bb;
i++;
}
if (i == maxSize)
throw reportError("readString", "Maximum string buffer limit exceeded");
/*
if (i == _tmp.length)
{
final int newSize = chars.length * 2;
if (newSize > maxStringBuffer)
{
throw reportError("readString", "Maximum string buffer limit exceeded");
}
_tmp = chars = Arrays.copyOf(chars, newSize);
}
*/
tmpSize = maxSize;
int soFar = (offset = ci - 1) - startIndex;
int bc = 0;
while (offset < limit)
{
if ('"' == (bb = buffer[offset++]))
{
token = bb;
return new String(chars, charOffset, soFar);
}
if ('\\' == (bc = bb))
{
if (soFar >= tmpSize - 6)
{
/*
final int newSize = chars.length * 2;
if (newSize > maxStringBuffer)
{
throw newParseErrorWith("Maximum string buffer limit exceeded",
maxStringBuffer);
}
_tmp = chars = Arrays.copyOf(chars, newSize);
tmpSize = _tmp.length;
*/
throw reportError("readString", "Maximum string buffer limit exceeded");
}
bc = buffer[offset++];
switch (bc)
{
case 'b':
bc = '\b';
break;
case 't':
bc = '\t';
break;
case 'n':
bc = '\n';
break;
case 'f':
bc = '\f';
break;
case 'r':
bc = '\r';
break;
case '"':
case '/':
case '\\':
break;
case 'u':
bc = (hexToInt(buffer[offset++]) << 12)
+ (hexToInt(buffer[offset++]) << 8)
+ (hexToInt(buffer[offset++]) << 4)
+ hexToInt(buffer[offset++]);
break;
default:
throw reportError("readString", "Invalid escape combination detected");//, bc);
}
}
else if ((bc & 0x80) != 0)
{
if (soFar >= tmpSize - 4)
{
/*
final int newSize = chars.length * 2;
if (newSize > maxStringBuffer)
{
throw newParseErrorWith("Maximum string buffer limit exceeded",
maxStringBuffer);
}
_tmp = chars = Arrays.copyOf(chars, newSize);
tmpSize = _tmp.length;
*/
throw reportError("readString", "Maximum string buffer limit exceeded");
}
final int u2 = buffer[offset++];
if ((bc & 0xE0) == 0xC0)
{
bc = ((bc & 0x1F) << 6) + (u2 & 0x3F);
}
else
{
final int u3 = buffer[offset++];
if ((bc & 0xF0) == 0xE0)
{
bc = ((bc & 0x0F) << 12) + ((u2 & 0x3F) << 6) + (u3 & 0x3F);
}
else
{
final int u4 = buffer[offset++];
if ((bc & 0xF8) == 0xF0)
{
bc = ((bc & 0x07) << 18) + ((u2 & 0x3F) << 12) + ((u3 & 0x3F) << 6)
+ (u4 & 0x3F);
}
else
{
// there are legal 5 & 6 byte combinations, but none
// are _valid_
throw reportError("readString", "Invalid unicode character detected");//, 0);
}
if (bc >= 0x10000)
{
// check if valid unicode
if (bc >= 0x110000)
throw reportError("readString", "Invalid unicode character detected");//, 0);
// split surrogates
final int sup = bc - 0x10000;
_tmp[charOffset + soFar] = (char)((sup >>> 10) + 0xd800);
_tmp[charOffset + soFar + 1] = (char)((sup & 0x3ff) + 0xdc00);
soFar += 2;
continue;
}
}
}
}
else if (soFar >= tmpSize)
{
/*
final int newSize = chars.length * 2;
if (newSize > maxStringBuffer)
{
throw reportError("readString", "Maximum string buffer limit exceeded");
}
_tmp = chars = Arrays.copyOf(chars, newSize);
tmpSize = _tmp.length;
*/
throw reportError("readString", "Maximum string buffer limit exceeded");
}
_tmp[charOffset + soFar] = (char)bc;
soFar++;
}
throw reportError("readString", "JSON string was not closed with a double quote");
}
private int hexToInt(final byte value) throws IOException
{
if (value >= '0' && value <= '9') return value - 0x30;
if (value >= 'A' && value <= 'F') return value - 0x37;
if (value >= 'a' && value <= 'f') return value - 0x57;
throw reportError("readString", "Could not parse unicode escape, expected a hexadecimal digit");
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy