| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you 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 org.apache.johnzon.core; |
| |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.io.InputStreamReader; |
| import java.io.Reader; |
| import java.math.BigDecimal; |
| import java.nio.charset.Charset; |
| import java.util.Map.Entry; |
| import java.util.AbstractMap; |
| import java.util.NoSuchElementException; |
| import java.util.Spliterator; |
| import java.util.Spliterators; |
| import java.util.function.Consumer; |
| import java.util.stream.Stream; |
| import java.util.stream.StreamSupport; |
| |
| import javax.json.Json; |
| import javax.json.JsonArray; |
| import javax.json.JsonException; |
| import javax.json.JsonObject; |
| import javax.json.JsonReaderFactory; |
| import javax.json.JsonStructure; |
| import javax.json.JsonValue; |
| import javax.json.stream.JsonLocation; |
| import javax.json.stream.JsonParser; |
| import javax.json.stream.JsonParsingException; |
| import javax.json.stream.JsonParser.Event; |
| |
| //This class represents either the Json tokenizer and the Json parser. |
| public class JsonStreamParserImpl implements JsonChars, JsonParser{ |
| |
| //the main buffer where the stream will be buffered |
| private final char[] buffer; |
| |
| //current parser position within the buffer |
| //Initial MIN_VALUE will trigger buffer refill, normally bufferPos is >= -1 |
| //-1 would cause a re-read of the first character in the buffer (which is at zero index) |
| private int bufferPos = Integer.MIN_VALUE; |
| |
| //available character in the buffer. It might be <= "buffer.length". |
| private int availableCharsInBuffer; |
| |
| //start and end position of values in the buffer |
| //may cross boundaries, then value is in fallBackCopyBuffer |
| private int startOfValueInBuffer = -1; |
| private int endOfValueInBuffer = -1; |
| |
| private final Reader in; |
| |
| //do we read from a character stream or a byte stream |
| //not used at the moment but maybe relevant in future to calculate the JsonLocation offset |
| @SuppressWarnings("unused") |
| private final boolean readBytes; |
| private final BufferStrategy.BufferProvider<char[]> bufferProvider; |
| private final BufferStrategy.BufferProvider<char[]> valueProvider; |
| |
| //max length for strings and numbers (max count of characters) |
| private final int maxValueLength; |
| |
| //we use a byte here, because comparing bytes |
| //is more efficient than comparing enums |
| //Additionally we handle internally two more event: COMMA_EVENT and KEY_SEPARATOR_EVENT |
| private byte previousEvent = -1; |
| |
| //this buffer is used to store current String or Number value in case that |
| //within the value a buffer boundary is crossed or the string contains escaped characters |
| private final char[] fallBackCopyBuffer; |
| private int fallBackCopyBufferLength; |
| |
| // location (line, column, offset) |
| // We try to calculate this efficiently so we do not just increment the values per char read |
| // Instead we calculate the column and offset relative to the pastBufferReadCount and/or lastLineBreakPosition. |
| private long currentLine = 1; |
| private long lastLineBreakPosition; |
| private long pastBufferReadCount; |
| |
| //cache (if current value is a number) integral state and the number itself if its only one digit |
| private boolean isCurrentNumberIntegral = true; |
| private int currentIntegralNumber = Integer.MIN_VALUE; //for number from 0 - 9 |
| |
| //maybe we want also cache BigDecimals |
| //private BigDecimal currentBigDecimalNumber = null; |
| |
| //We need a stack if we want detect bad formatted Json do determine if we are within an array or not |
| //example |
| // Streamparser sees: ],1 <-- we look from here |
| //the 1 is only allowed if we are within an array |
| //This can only be determined by build up a stack which tracks the trail of Json objects and arrays |
| //This stack here is only needed for validating the above mentioned case, if we want to be lenient we can skip suing the stack. |
| //Stack can cause out of memory issues when the nesting depth of a Json stream is too deep. |
| private StructureElement currentStructureElement = null; |
| |
| //minimal stack implementation |
| private static final class StructureElement { |
| final StructureElement previous; |
| final boolean isArray; |
| |
| StructureElement(final StructureElement previous, final boolean isArray) { |
| super(); |
| this.previous = previous; |
| this.isArray = isArray; |
| } |
| |
| int getDepth() { |
| if(previous == null) { |
| return 1; |
| } else { |
| return previous.getDepth()+1; |
| } |
| } |
| } |
| |
| //detect charset according to RFC 4627 |
| public JsonStreamParserImpl(final InputStream inputStream, final int maxStringLength, |
| final BufferStrategy.BufferProvider<char[]> bufferProvider, final BufferStrategy.BufferProvider<char[]> valueBuffer) { |
| |
| this(inputStream, null, null, maxStringLength, bufferProvider, valueBuffer); |
| } |
| |
| //use charset provided |
| public JsonStreamParserImpl(final InputStream inputStream, final Charset encoding, final int maxStringLength, |
| final BufferStrategy.BufferProvider<char[]> bufferProvider, final BufferStrategy.BufferProvider<char[]> valueBuffer) { |
| |
| this(inputStream, null, encoding, maxStringLength, bufferProvider, valueBuffer); |
| } |
| |
| public JsonStreamParserImpl(final Reader reader, final int maxStringLength, final BufferStrategy.BufferProvider<char[]> bufferProvider, |
| final BufferStrategy.BufferProvider<char[]> valueBuffer) { |
| |
| this(null, reader, null, maxStringLength, bufferProvider, valueBuffer); |
| } |
| |
| private JsonStreamParserImpl(final InputStream inputStream, final Reader reader, final Charset encoding, final int maxStringLength, |
| final BufferStrategy.BufferProvider<char[]> bufferProvider, final BufferStrategy.BufferProvider<char[]> valueBuffer) { |
| |
| this.maxValueLength = maxStringLength <= 0 ? 8192 : maxStringLength; |
| this.fallBackCopyBuffer = valueBuffer.newBuffer(); |
| this.buffer = bufferProvider.newBuffer(); |
| this.bufferProvider = bufferProvider; |
| this.valueProvider = valueBuffer; |
| |
| if (fallBackCopyBuffer.length < maxStringLength) { |
| throw cust("Size of value buffer cannot be smaller than maximum string length"); |
| } |
| |
| if(reader == null && inputStream == null) { |
| throw new NullPointerException("input must not be null"); |
| } |
| |
| if (reader != null) { |
| this.in = reader; |
| readBytes = false; |
| } else if (encoding == null) { |
| this.in = new RFC4627AwareInputStreamReader(inputStream); |
| readBytes = true; |
| |
| } else { |
| this.in = new InputStreamReader(inputStream, encoding.newDecoder()); |
| readBytes = true; |
| } |
| |
| } |
| |
| //append a single char to the value buffer |
| private void appendToCopyBuffer(final char c) { |
| fallBackCopyBuffer[fallBackCopyBufferLength++] = c; |
| } |
| |
| //copy content between "start" and "end" from buffer to value buffer |
| private void copyCurrentValue() { |
| |
| if ((endOfValueInBuffer - startOfValueInBuffer) > 0) { |
| |
| if ((endOfValueInBuffer - startOfValueInBuffer) > maxValueLength) { |
| throw tmc(); |
| } |
| //System.out.println("copy from "+startOfValueInBuffer+" with length "+(endOfValueInBuffer - startOfValueInBuffer)); |
| //System.out.println("endOfValueInBuffer "+endOfValueInBuffer); |
| System.arraycopy(buffer, startOfValueInBuffer, fallBackCopyBuffer, fallBackCopyBufferLength, |
| (endOfValueInBuffer - startOfValueInBuffer)); |
| fallBackCopyBufferLength += (endOfValueInBuffer - startOfValueInBuffer); |
| |
| } |
| |
| startOfValueInBuffer = endOfValueInBuffer = -1; |
| } |
| |
| private void scanForTrailingGarbage(int offset) { |
| //detect garbage at the end of the file after last object or array is closed |
| if (bufferPos < availableCharsInBuffer - offset) { |
| |
| final char c = readNextNonWhitespaceChar(readNextChar()); |
| |
| if (c == EOF) { |
| return; |
| } |
| |
| if (bufferPos < availableCharsInBuffer) { |
| throw uexc("EOF expected"); |
| } |
| |
| } |
| } |
| |
| @Override |
| public final boolean hasNext() { |
| |
| if(currentStructureElement == null && (previousEvent == VALUE_FALSE |
| || previousEvent == VALUE_TRUE |
| || previousEvent == VALUE_NULL |
| || previousEvent == VALUE_NUMBER |
| || previousEvent == VALUE_STRING)) { |
| scanForTrailingGarbage(0); |
| return false; |
| } |
| |
| if (currentStructureElement != null || previousEvent == -1) { |
| return true; |
| } |
| |
| scanForTrailingGarbage(2); |
| |
| return false; |
| |
| } |
| |
| private static boolean isAsciiDigit(final char value) { |
| return value <= NINE && value >= ZERO; |
| } |
| |
| //check if value is a valid hex digit and return the numeric value |
| private int parseHexDigit(final char value) { |
| |
| if (isAsciiDigit(value)) { |
| return value - 48; |
| } else if (value <= 'f' && value >= 'a') { |
| return (value) - 87; |
| } else if ((value <= 'F' && value >= 'A')) { |
| return (value) - 55; |
| } else { |
| throw uexc("Invalid hex character"); |
| } |
| } |
| |
| private JsonLocation createLocation() { |
| |
| //we start with column = 1, so column is always >= 1 |
| //APi is not clear in this, but starting column with 1 is convenient |
| long column = 1; |
| long charOffset = 0; |
| |
| if (bufferPos >= -1) { |
| |
| charOffset = pastBufferReadCount + bufferPos + 1; |
| column = lastLineBreakPosition == 0 ? charOffset + 1 : charOffset - lastLineBreakPosition; |
| } |
| |
| //For now its unclear how to calculate offset for (byte) inputsream. |
| //API says count bytes but thats dependent on encoding and not efficient |
| //skip this for now, count always bytes and defer this until the JSR TCK arrives. |
| |
| return new JsonLocationImpl(currentLine, column, charOffset); |
| } |
| |
| //read the next char from the stream and set/increment the bufferPos |
| //will also refill buffer if necessary |
| //if we are currently processing a value (string or number) and buffer |
| //refill is necessary copy the already read value part into the value buffer |
| protected final char readNextChar() { |
| |
| if ((availableCharsInBuffer - bufferPos) <= 1) { |
| //fillbuffer |
| |
| //copy content from old buffer to valuebuffer |
| //correct start end mark |
| if (startOfValueInBuffer > -1 && endOfValueInBuffer == -1) { |
| endOfValueInBuffer = availableCharsInBuffer; |
| copyCurrentValue(); |
| |
| startOfValueInBuffer = 0; |
| } |
| |
| if (bufferPos >= -1) { |
| pastBufferReadCount += availableCharsInBuffer; |
| } |
| |
| try { |
| availableCharsInBuffer = in.read(buffer, 0, buffer.length); |
| if (availableCharsInBuffer <= 0) { |
| return EOF; |
| } |
| |
| } catch (final IOException e) { |
| close(); |
| throw uexio(e); |
| } |
| |
| bufferPos = 0; |
| //end fillbuffer |
| } else { |
| |
| //since JOHNZON-18 not longer necessary |
| //prevent "bufferoverflow |
| //if(bufferPos + 1 >= availableCharsInBuffer) { |
| // return EOF; |
| //} |
| |
| bufferPos++; |
| } |
| |
| return buffer[bufferPos]; |
| } |
| |
| //skip whitespaces |
| //tracks location informations (line, column) |
| //returns the first non whitespace character |
| protected final char readNextNonWhitespaceChar(char c) { |
| |
| int dosCount = 0; |
| |
| while (c == SPACE || c == TAB || c == CR || c == EOL) { |
| |
| if (c == EOL) { |
| currentLine++; |
| lastLineBreakPosition = pastBufferReadCount + bufferPos; |
| } |
| |
| //prevent DOS (denial of service) attack |
| if (dosCount >= maxValueLength) { |
| throw tmc(); |
| } |
| dosCount++; |
| |
| //read next character |
| c = readNextChar(); |
| |
| } |
| |
| return c; |
| } |
| |
| @Override |
| public final Event next() { |
| //main entry, make decision how to handle the current character in the stream |
| |
| if (!hasNext()) { |
| throw new NoSuchElementException(); |
| } |
| |
| final char c = readNextNonWhitespaceChar(readNextChar()); |
| |
| if (c == COMMA_CHAR) { |
| |
| //last event must one of the following-> " ] } LITERAL |
| if (previousEvent == START_ARRAY || previousEvent == START_OBJECT || previousEvent == COMMA_EVENT || previousEvent == KEY_NAME) { |
| throw uexc("Expected \" ] } LITERAL"); |
| } |
| |
| previousEvent = COMMA_EVENT; |
| return next(); |
| |
| } |
| |
| if (c == KEY_SEPARATOR) { |
| |
| if (previousEvent != KEY_NAME) { |
| throw uexc("A : can only follow a key name"); |
| } |
| |
| previousEvent = KEY_SEPARATOR_EVENT; |
| return next(); |
| |
| } |
| |
| if (!isCurrentNumberIntegral) { |
| isCurrentNumberIntegral = true; |
| } |
| // if (currentBigDecimalNumber != null) { |
| // currentBigDecimalNumber = null; |
| // } |
| if (currentIntegralNumber != Integer.MIN_VALUE) { |
| currentIntegralNumber = Integer.MIN_VALUE; |
| } |
| |
| if (fallBackCopyBufferLength != 0) { |
| fallBackCopyBufferLength = 0; |
| } |
| |
| startOfValueInBuffer = endOfValueInBuffer = -1; |
| |
| switch (c) { |
| |
| case START_OBJECT_CHAR: |
| |
| return handleStartObject(); |
| |
| case END_OBJECT_CHAR: |
| |
| return handleEndObject(); |
| |
| case START_ARRAY_CHAR: |
| |
| return handleStartArray(); |
| |
| case END_ARRAY_CHAR: |
| |
| return handleEndArray(); |
| |
| case QUOTE_CHAR: |
| |
| return handleQuote(); |
| |
| case '0': |
| case '1': |
| case '2': |
| case '3': |
| case '4': |
| case '5': |
| case '6': |
| case '7': |
| case '8': |
| case '9': |
| case MINUS: |
| case FALSE_F: // false |
| case TRUE_T: // true |
| case NULL_N: // null |
| |
| return handleLiteral(); |
| |
| default: |
| |
| return defaultHandling(c); |
| } |
| } |
| |
| protected Event defaultHandling(char c) { |
| if (c == EOF) { |
| throw uexc("End of file hit too early"); |
| } |
| throw uexc("Expected structural character or digit or 't' or 'n' or 'f' or '-'"); |
| } |
| |
| private Event handleStartObject() { |
| |
| //last event must one of the following-> : , [ |
| if (previousEvent != -1 && previousEvent != KEY_SEPARATOR_EVENT && previousEvent != START_ARRAY && previousEvent != COMMA_EVENT) { |
| throw uexc("Expected : , ["); |
| } |
| |
| //push upon the stack |
| if (currentStructureElement == null) { |
| currentStructureElement = new StructureElement(null, false); |
| } else { |
| final StructureElement localStructureElement = new StructureElement(currentStructureElement, false); |
| currentStructureElement = localStructureElement; |
| } |
| |
| return EVT_MAP[previousEvent = START_OBJECT]; |
| |
| } |
| |
| private Event handleEndObject() { |
| |
| //last event must one of the following-> " ] { } LITERAL |
| if (previousEvent == START_ARRAY || previousEvent == COMMA_EVENT || previousEvent == KEY_NAME |
| || previousEvent == KEY_SEPARATOR_EVENT || currentStructureElement == null) { |
| throw uexc("Expected \" ] { } LITERAL"); |
| } |
| |
| if (currentStructureElement.isArray) { |
| throw uexc("Expected : ]"); |
| } |
| |
| //pop from stack |
| currentStructureElement = currentStructureElement.previous; |
| |
| return EVT_MAP[previousEvent = END_OBJECT]; |
| } |
| |
| private Event handleStartArray() { |
| |
| //last event must one of the following-> : , [ |
| if (previousEvent != -1 && previousEvent != KEY_SEPARATOR_EVENT && previousEvent != START_ARRAY && previousEvent != COMMA_EVENT) { |
| throw uexc("Expected : , ["); |
| } |
| |
| //push upon the stack |
| if (currentStructureElement == null) { |
| currentStructureElement = new StructureElement(null, true); |
| } else { |
| if(!currentStructureElement.isArray && previousEvent != KEY_SEPARATOR_EVENT) { |
| throw uexc("Expected \""); |
| } |
| |
| final StructureElement localStructureElement = new StructureElement(currentStructureElement, true); |
| currentStructureElement = localStructureElement; |
| } |
| |
| return EVT_MAP[previousEvent = START_ARRAY]; |
| } |
| |
| private Event handleEndArray() { |
| |
| //last event must one of the following-> [ ] } " LITERAL |
| if (previousEvent == START_OBJECT || previousEvent == COMMA_EVENT || previousEvent == KEY_SEPARATOR_EVENT |
| || currentStructureElement == null) { |
| throw uexc("Expected [ ] } \" LITERAL"); |
| } |
| |
| if (!currentStructureElement.isArray) { |
| throw uexc("Expected : }"); |
| } |
| |
| //pop from stack |
| currentStructureElement = currentStructureElement.previous; |
| |
| return EVT_MAP[previousEvent = END_ARRAY]; |
| } |
| |
| //read a string, gets called recursively |
| //Handles escape/d characters |
| //if string contains escape chars and/or cross buffer boundary then copy in the value buffer |
| //if not then denote string start and end in startOfValueInBuffer and endOfValueInBuffer and read directly from buffer |
| private void readString() { |
| |
| do { |
| char n = readNextChar(); |
| //when first called n its first char after the starting quote |
| //after that its the next character after the while loop below |
| |
| if (n == QUOTE_CHAR) { |
| endOfValueInBuffer = startOfValueInBuffer = bufferPos; //->"" case |
| return; |
| } else if (n == EOL) { |
| throw uexc("Unexpected linebreak"); |
| |
| } else if (/* n >= '\u0000' && */ n <= '\u001F') { |
| throw uexc("Unescaped control character"); |
| |
| } else if (n == ESCAPE_CHAR) { |
| |
| n = readNextChar(); |
| |
| // \ u XXXX -> unicode char |
| if (n == 'u') { |
| n = parseUnicodeHexChars(); |
| appendToCopyBuffer(n); |
| |
| // \\ -> \ |
| } else if (n == ESCAPE_CHAR) { |
| appendToCopyBuffer(n); |
| |
| //another escape chars, for example \t |
| } else { |
| appendToCopyBuffer(Strings.asEscapedChar(n)); |
| |
| } |
| |
| } else { |
| |
| startOfValueInBuffer = bufferPos; |
| endOfValueInBuffer = -1; |
| |
| while ((n = readNextChar()) > '\u001F' && n != ESCAPE_CHAR && n != EOL && n != QUOTE_CHAR) { |
| //read fast |
| } |
| |
| endOfValueInBuffer = bufferPos; |
| |
| if (n == QUOTE_CHAR) { |
| |
| if (fallBackCopyBufferLength > 0) { |
| copyCurrentValue(); |
| } else { |
| if ((endOfValueInBuffer - startOfValueInBuffer) > maxValueLength) { |
| throw tmc(); |
| } |
| |
| } |
| |
| return; |
| } else if (n == EOL) { |
| throw uexc("Unexpected linebreak"); |
| |
| } else if (n >= '\u0000' && n <= '\u001F') { |
| throw uexc("Unescaped control character"); |
| } |
| |
| copyCurrentValue(); |
| |
| //current n is one of < '\u001F' -OR- ESCAPE_CHAR -OR- EOL -OR- QUOTE |
| |
| bufferPos--; //unread one char |
| |
| } |
| } while (true); |
| |
| // before this do while(true) it was: |
| // |
| //recurse until string is terminated by a non escaped quote |
| //readString(); |
| // |
| // |
| // but recursive = can't read big strings |
| |
| } |
| |
| //maybe we want to check invalid utf encoding |
| //not clear yet if the InputStreamReader is doing that |
| |
| /* |
| private char checkSurrogates(char n, char highSurrogate) { |
| //check for invalid surrogates |
| //high followed by low |
| if (Character.isHighSurrogate(n)) { |
| |
| if (highSurrogate != 0) { |
| throw uexc("Unexpected high surrogate"); |
| } |
| return n; |
| } else if (Character.isLowSurrogate(n)) { |
| |
| if (highSurrogate == 0) { |
| throw uexc("Unexpected low surrogate"); |
| } else if (!Character.isSurrogatePair(highSurrogate, n)) { |
| throw uexc("Invalid surrogate pair"); |
| } |
| return 0; |
| } else if (highSurrogate != 0 && !Character.isLowSurrogate(n)) { |
| throw uexc("Expected low surrogate"); |
| } |
| |
| return highSurrogate; |
| }*/ |
| |
| //read the next four chars, check them and treat them as an single unicode char |
| private char parseUnicodeHexChars() { |
| // \u08Ac etc |
| return (char) (((parseHexDigit(readNextChar())) * 4096) + ((parseHexDigit(readNextChar())) * 256) |
| + ((parseHexDigit(readNextChar())) * 16) + ((parseHexDigit(readNextChar())))); |
| |
| } |
| |
| private Event handleQuote() { |
| |
| //always the beginning quote of a key or value |
| |
| //last event must one of the following-> : { [ , |
| if (previousEvent != -1 && previousEvent != KEY_SEPARATOR_EVENT && previousEvent != START_OBJECT && previousEvent != START_ARRAY |
| && previousEvent != COMMA_EVENT) { |
| throw uexc("Expected : { [ ,"); |
| } |
| //starting quote already consumed |
| readString(); |
| //end quote already consumed |
| |
| //make the decision if its an key or value |
| if (previousEvent == KEY_SEPARATOR_EVENT || previousEvent == -1) { |
| //must be value |
| |
| if (currentStructureElement != null && currentStructureElement.isArray) { |
| //not in array, only allowed within array |
| throw uexc("Key value pair not allowed in an array"); |
| } |
| |
| return EVT_MAP[previousEvent = VALUE_STRING]; |
| |
| } else { //Event is START_OBJECT OR START_ARRAY OR COMMA_EVENT |
| //must be a key if we are in an object, if not its a value |
| |
| if (currentStructureElement != null && currentStructureElement.isArray) { |
| return EVT_MAP[previousEvent = VALUE_STRING]; |
| } |
| |
| return EVT_MAP[previousEvent = KEY_NAME]; |
| } |
| |
| } |
| |
| //read a number |
| //if a number cross buffer boundary then copy in the value buffer |
| //if not then denote string start and end in startOfValueInBuffer and endOfValueInBuffer and read directly from buffer |
| private void readNumber() { |
| |
| char c = buffer[bufferPos]; |
| |
| //start can change on any read() if we cross buffer boundary |
| startOfValueInBuffer = bufferPos; |
| endOfValueInBuffer = -1; |
| |
| char y = EOF; |
| |
| //sum up the digit values |
| int cumulatedDigitValue = 0; |
| while (isAsciiDigit(y = readNextChar())) { |
| |
| if (c == ZERO) { |
| throw uexc("Leading zeros not allowed"); |
| } |
| |
| if (c == MINUS && cumulatedDigitValue == 48) { |
| throw uexc("Leading zeros after minus not allowed"); |
| } |
| |
| cumulatedDigitValue += y; |
| |
| } |
| |
| if (c == MINUS && cumulatedDigitValue == 0) { |
| |
| throw uexc("Unexpected premature end of number"); |
| } |
| |
| if (y == DOT) { |
| isCurrentNumberIntegral = false; |
| cumulatedDigitValue = 0; |
| while (isAsciiDigit(y = readNextChar())) { |
| cumulatedDigitValue++; |
| } |
| |
| if (cumulatedDigitValue == 0) { |
| |
| throw uexc("Unexpected premature end of number"); |
| } |
| |
| } |
| |
| if (y == EXP_LOWERCASE || y == EXP_UPPERCASE) { |
| isCurrentNumberIntegral = false; |
| |
| y = readNextChar(); //+ or - or digit |
| |
| if (!isAsciiDigit(y) && y != MINUS && y != PLUS) { |
| throw uexc("Expected DIGIT or + or -"); |
| } |
| |
| if (y == MINUS || y == PLUS) { |
| y = readNextChar(); |
| if (!isAsciiDigit(y)) { |
| throw uexc("Unexpected premature end of number"); |
| } |
| |
| } |
| |
| while (isAsciiDigit(y = readNextChar())) { |
| //no-op |
| } |
| |
| } |
| |
| endOfValueInBuffer = bufferPos; |
| |
| if (y == COMMA_CHAR || y == END_ARRAY_CHAR || y == END_OBJECT_CHAR || y == EOL || y == SPACE || y == TAB || y == CR || y == EOF) { |
| |
| bufferPos--;//unread one char |
| |
| //['-', DIGIT] |
| if (isCurrentNumberIntegral && c == MINUS && cumulatedDigitValue >= 48 && cumulatedDigitValue <= 57) { |
| |
| currentIntegralNumber = -(cumulatedDigitValue - 48); //optimize -0 till -9 |
| return; |
| } |
| |
| //[DIGIT] |
| if (isCurrentNumberIntegral && c != MINUS && cumulatedDigitValue == 0) { |
| |
| currentIntegralNumber = (c - 48); //optimize 0 till 9 |
| return; |
| } |
| |
| if (fallBackCopyBufferLength > 0) { |
| |
| //we crossed a buffer boundary, use value buffer |
| //copyCurrentValue(); |
| |
| if (fallBackCopyBufferLength >= maxValueLength) { |
| throw tmc(); |
| } |
| |
| } else { |
| if ((endOfValueInBuffer - startOfValueInBuffer) >= maxValueLength) { |
| throw tmc(); |
| } |
| } |
| |
| return; |
| |
| } |
| |
| throw uexc("Unexpected premature end of number"); |
| |
| } |
| |
| //handles false, true, null and numbers |
| private Event handleLiteral() { |
| |
| //last event must one of the following-> : , [ |
| if (previousEvent != -1 && previousEvent != KEY_SEPARATOR_EVENT && previousEvent != START_ARRAY && previousEvent != COMMA_EVENT) { |
| throw uexc("Expected : , ["); |
| } |
| |
| if (previousEvent == COMMA_EVENT && !currentStructureElement.isArray) { |
| //only allowed within array |
| throw uexc("Not in an array context"); |
| } |
| |
| char c = buffer[bufferPos]; |
| |
| // probe literals |
| switch (c) { |
| case TRUE_T: |
| |
| if (readNextChar() != TRUE_R || readNextChar() != TRUE_U || readNextChar() != TRUE_E) { |
| throw uexc("Expected LITERAL: true"); |
| } |
| return EVT_MAP[previousEvent = VALUE_TRUE]; |
| case FALSE_F: |
| |
| if (readNextChar() != FALSE_A || readNextChar() != FALSE_L || readNextChar() != FALSE_S || readNextChar() != FALSE_E) { |
| throw uexc("Expected LITERAL: false"); |
| } |
| |
| return EVT_MAP[previousEvent = VALUE_FALSE]; |
| |
| case NULL_N: |
| |
| if (readNextChar() != NULL_U || readNextChar() != NULL_L || readNextChar() != NULL_L) { |
| throw uexc("Expected LITERAL: null"); |
| } |
| return EVT_MAP[previousEvent = VALUE_NULL]; |
| |
| default: |
| readNumber(); |
| return EVT_MAP[previousEvent = VALUE_NUMBER]; |
| } |
| |
| } |
| |
| @Override |
| public String getString() { |
| if (previousEvent == KEY_NAME || previousEvent == VALUE_STRING || previousEvent == VALUE_NUMBER) { |
| |
| //if there a content in the value buffer read from them, if not use main buffer |
| return fallBackCopyBufferLength > 0 ? new String(fallBackCopyBuffer, 0, fallBackCopyBufferLength) : new String(buffer, |
| startOfValueInBuffer, endOfValueInBuffer - startOfValueInBuffer); |
| } else { |
| throw new IllegalStateException(EVT_MAP[previousEvent] + " doesn't support getString()"); |
| } |
| } |
| |
| @Override |
| public boolean isIntegralNumber() { |
| |
| if (previousEvent != VALUE_NUMBER) { |
| throw new IllegalStateException(EVT_MAP[previousEvent] + " doesn't support isIntegralNumber()"); |
| } else { |
| return isCurrentNumberIntegral; |
| } |
| } |
| |
| @Override |
| public int getInt() { |
| if (previousEvent != VALUE_NUMBER) { |
| throw new IllegalStateException(EVT_MAP[previousEvent] + " doesn't support getInt()"); |
| } else if (isCurrentNumberIntegral && currentIntegralNumber != Integer.MIN_VALUE) { |
| return currentIntegralNumber; |
| } else if (isCurrentNumberIntegral) { |
| //if there a content in the value buffer read from them, if not use main buffer |
| final Integer retVal = fallBackCopyBufferLength > 0 ? parseIntegerFromChars(fallBackCopyBuffer, 0, fallBackCopyBufferLength) |
| : parseIntegerFromChars(buffer, startOfValueInBuffer, endOfValueInBuffer); |
| if (retVal == null) { |
| return getBigDecimal().intValue(); |
| } else { |
| return retVal.intValue(); |
| } |
| } else { |
| return getBigDecimal().intValue(); |
| } |
| } |
| |
| @Override |
| public long getLong() { |
| if (previousEvent != VALUE_NUMBER) { |
| throw new IllegalStateException(EVT_MAP[previousEvent] + " doesn't support getLong()"); |
| } else if (isCurrentNumberIntegral && currentIntegralNumber != Integer.MIN_VALUE) { |
| return currentIntegralNumber; |
| } else if (isCurrentNumberIntegral) { |
| //if there a content in the value buffer read from them, if not use main buffer |
| final Long retVal = fallBackCopyBufferLength > 0 ? parseLongFromChars(fallBackCopyBuffer, 0, fallBackCopyBufferLength) |
| : parseLongFromChars(buffer, startOfValueInBuffer, endOfValueInBuffer); |
| if (retVal == null) { |
| return getBigDecimal().longValue(); |
| } else { |
| return retVal.longValue(); |
| } |
| } else { |
| return getBigDecimal().longValue(); |
| } |
| |
| } |
| |
| @Override |
| public BigDecimal getBigDecimal() { |
| if (previousEvent != VALUE_NUMBER) { |
| throw new IllegalStateException(EVT_MAP[previousEvent] + " doesn't support getBigDecimal()"); |
| // } else if (currentBigDecimalNumber != null) { |
| // return currentBigDecimalNumber; |
| } else if (isCurrentNumberIntegral && currentIntegralNumber != Integer.MIN_VALUE) { |
| return new BigDecimal(currentIntegralNumber); |
| } else if (isCurrentNumberIntegral) { |
| //if there a content in the value buffer read from them, if not use main buffer |
| final Long retVal = fallBackCopyBufferLength > 0 ? parseLongFromChars(fallBackCopyBuffer, 0, fallBackCopyBufferLength) |
| : parseLongFromChars(buffer, startOfValueInBuffer, endOfValueInBuffer); |
| if (retVal == null) { |
| return (/*currentBigDecimalNumber = */fallBackCopyBufferLength > 0 ? new BigDecimal(fallBackCopyBuffer, 0, |
| fallBackCopyBufferLength) : new BigDecimal(buffer, startOfValueInBuffer, |
| (endOfValueInBuffer - startOfValueInBuffer))); |
| } else { |
| return (/*currentBigDecimalNumber = */new BigDecimal(retVal.longValue())); |
| } |
| } else { |
| //if there a content in the value buffer read from them, if not use main buffer |
| //System.out.println(new String(fallBackCopyBuffer, 0, |
| // fallBackCopyBufferLength)); |
| return (/*currentBigDecimalNumber = */fallBackCopyBufferLength > 0 ? new BigDecimal(fallBackCopyBuffer, 0, |
| fallBackCopyBufferLength) : new BigDecimal(buffer, startOfValueInBuffer, (endOfValueInBuffer - startOfValueInBuffer))); |
| } |
| |
| } |
| |
| @Override |
| public JsonLocation getLocation() { |
| return createLocation(); |
| } |
| |
| @Override |
| public void close() { |
| bufferProvider.release(buffer); |
| valueProvider.release(fallBackCopyBuffer); |
| |
| try { |
| in.close(); |
| } catch (final IOException e) { |
| throw new JsonException("Unexpected IO exception " + e.getMessage(), e); |
| } |
| } |
| |
| //parse a char[] to long while checking overflow |
| //if overflowed return null |
| //no additional checks since we are sure here that there are no non digits in the array |
| private static Long parseLongFromChars(final char[] chars, final int start, final int end) { |
| |
| long retVal = 0; |
| final boolean negative = chars[start] == MINUS; |
| for (int i = negative ? start + 1 : start; i < end; i++) { |
| final long tmp = retVal * 10 + (chars[i] - ZERO); |
| if (tmp < retVal) { //check overflow |
| return null; |
| } else { |
| retVal = tmp; |
| } |
| } |
| |
| return negative ? -retVal : retVal; |
| } |
| |
| //parse a char[] to int while checking overflow |
| //if overflowed return null |
| //no additional checks since we are sure here that there are no non digits in the array |
| private static Integer parseIntegerFromChars(final char[] chars, final int start, final int end) { |
| |
| int retVal = 0; |
| final boolean negative = chars[start] == MINUS; |
| for (int i = negative ? start + 1 : start; i < end; i++) { |
| final int tmp = retVal * 10 + (chars[i] - ZERO); |
| if (tmp < retVal) { //check overflow |
| return null; |
| } else { |
| retVal = tmp; |
| } |
| } |
| |
| return negative ? -retVal : retVal; |
| } |
| |
| private JsonParsingException uexc(final char c, final String message) { |
| final JsonLocation location = createLocation(); |
| return new JsonParsingException("Unexpected character '" + c + "' (Codepoint: " + String.valueOf(c).codePointAt(0) + ") on " |
| + location + ". Reason is [[" + message + "]]", location); |
| } |
| |
| private JsonParsingException uexc(final String message) { |
| final char c = bufferPos < 0 ? 0 : buffer[bufferPos]; |
| return uexc(c, message); |
| } |
| |
| private JsonParsingException tmc() { |
| final JsonLocation location = createLocation(); |
| return new JsonParsingException("Too many characters. Maximum string/number length of " + maxValueLength + " exceeded on " |
| + location, location); |
| } |
| |
| private JsonParsingException uexio(final IOException e) { |
| final JsonLocation location = createLocation(); |
| return new JsonParsingException("Unexpected IO exception on " + location, e, location); |
| } |
| |
| private JsonParsingException cust(final String message) { |
| final JsonLocation location = createLocation(); |
| return new JsonParsingException("General exception on " + location + ". Reason is [[" + message + "]]", location); |
| } |
| |
| private IllegalStateException is(final String message) { |
| final JsonLocation location = createLocation(); |
| return new IllegalStateException("Illegal parser state exception on " + location + ". Reason is [[" + message + "]]"); |
| } |
| |
| |
| private JsonStructure getStructure(Event start) { |
| JsonReaderImpl reader = new JsonReaderImpl(this, false, start); |
| JsonStructure obj; |
| try { |
| obj = reader.read(); |
| } catch (JsonParsingException e) { |
| throw is(e.getMessage()); |
| } finally { |
| reader.close(); |
| } |
| |
| return obj; |
| } |
| |
| @Override |
| public JsonObject getObject() { |
| if(previousEvent != START_OBJECT) { |
| throw is("Parser state must be START_OBJECT"); |
| } |
| return getStructure(Event.START_OBJECT).asJsonObject(); |
| } |
| |
| @Override |
| public JsonArray getArray() { |
| if(previousEvent != START_ARRAY) { |
| throw is("Parser state must be START_ARRAY"); |
| } |
| return getStructure(Event.START_ARRAY).asJsonArray(); |
| } |
| |
| @Override |
| public JsonValue getValue() { |
| if(previousEvent == VALUE_TRUE) { |
| return JsonValue.TRUE; |
| } |
| |
| if(previousEvent == VALUE_FALSE) { |
| return JsonValue.FALSE; |
| } |
| |
| if(previousEvent == VALUE_NULL) { |
| return JsonValue.NULL; |
| } |
| |
| if(previousEvent == VALUE_NUMBER) { |
| if (isCurrentNumberIntegral && currentIntegralNumber != Integer.MIN_VALUE) { |
| return Json.createValue(currentIntegralNumber); |
| } else if (isCurrentNumberIntegral) { |
| return Json.createValue(getLong()); |
| } else { |
| return Json.createValue(getBigDecimal()); |
| } |
| } |
| |
| if(previousEvent == VALUE_STRING) { |
| return Json.createValue(getString()); |
| } |
| |
| // |
| if(previousEvent == START_OBJECT) { |
| return getObject(); |
| } |
| |
| if(previousEvent == END_OBJECT) { |
| next(); |
| return getValue(); |
| } |
| |
| if(previousEvent == START_ARRAY) { |
| return getArray(); |
| } |
| |
| if(previousEvent == END_ARRAY) { |
| next(); |
| return getValue(); |
| } |
| |
| if(previousEvent == KEY_NAME) { |
| return Json.createValue(getString()); |
| } |
| |
| /* if(previousEvent == KEY_SEPARATOR_EVENT) { |
| next(); |
| return getValue(); |
| } |
| |
| if(previousEvent == COMMA_EVENT) { |
| next(); |
| return getValue(); |
| }*/ |
| |
| /*if(previousEvent == -1) { |
| return getStructure(); |
| }*/ |
| |
| throw cust("Internal parsing error: Unknown state"); |
| |
| } |
| |
| |
| |
| @Override |
| public Stream<JsonValue> getArrayStream() { |
| //TODO check for beginning of an array |
| Spliterator<JsonValue> spliterator = new Spliterators.AbstractSpliterator<JsonValue>(Long.MAX_VALUE, Spliterator.ORDERED) { |
| @Override |
| public Spliterator<JsonValue> trySplit() { |
| return null; |
| } |
| @Override |
| public boolean tryAdvance(Consumer<? super JsonValue> action) { |
| if (action == null) { |
| throw new NullPointerException(); |
| } |
| if (!hasNext()) { |
| return false; |
| } |
| if (next() == Event.END_ARRAY) { |
| return false; |
| } |
| action.accept(getValue()); |
| return true; |
| } |
| }; |
| |
| return StreamSupport.stream(spliterator, false); |
| } |
| |
| @Override |
| public Stream<Entry<String, JsonValue>> getObjectStream() { |
| //TODO check for beginning of an object |
| Spliterator<Entry<String, JsonValue>> spliterator = new Spliterators.AbstractSpliterator<Entry<String, JsonValue>>(Long.MAX_VALUE, Spliterator.ORDERED) { |
| @Override |
| public Spliterator<Entry<String, JsonValue>> trySplit() { |
| return null; |
| } |
| @Override |
| public boolean tryAdvance(Consumer<? super Entry<String, JsonValue>> action) { |
| if (action == null) { |
| throw new NullPointerException(); |
| } |
| if (!hasNext()) { |
| return false; |
| } |
| Event event = next(); |
| if (event == Event.END_OBJECT) { |
| return false; |
| } |
| if (event != Event.KEY_NAME) { |
| throw new JsonException("error generating stream, key name token expected (was: "+event+")"); |
| } |
| String key = getString(); |
| if (!hasNext()) { |
| throw new JsonException("error generating stream, more tokens expected"); |
| } |
| next(); |
| JsonValue value = getValue(); |
| action.accept(new AbstractMap.SimpleImmutableEntry<String, JsonValue>(key, value)); |
| return true; |
| } |
| }; |
| |
| return StreamSupport.stream(spliterator, false); |
| } |
| |
| @Override |
| public Stream<JsonValue> getValueStream() { |
| //TODO check for value |
| Spliterator<JsonValue> spliterator = new Spliterators.AbstractSpliterator<JsonValue>(Long.MAX_VALUE, Spliterator.ORDERED) { |
| @Override |
| public Spliterator<JsonValue> trySplit() { |
| return null; |
| } |
| @Override |
| public boolean tryAdvance(Consumer<? super JsonValue> action) { |
| if (action == null) { |
| throw new NullPointerException(); |
| } |
| if (!hasNext()) { |
| return false; |
| } |
| next(); |
| action.accept(getValue()); |
| return true; |
| } |
| }; |
| |
| return StreamSupport.stream(spliterator, false); |
| } |
| |
| @Override |
| public void skipArray() { |
| if(currentStructureElement != null && currentStructureElement.isArray) { |
| int depth = currentStructureElement.getDepth(); |
| while(next() != Event.END_ARRAY || currentStructureElement.getDepth() != depth); |
| } |
| } |
| |
| @Override |
| public void skipObject() { |
| if(currentStructureElement != null && !currentStructureElement.isArray) { |
| int depth = currentStructureElement.getDepth(); |
| while(next() != Event.END_OBJECT || currentStructureElement.getDepth() != depth); |
| } |
| } |
| |
| //1.1 |
| |
| |
| |
| } |