You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@johnzon.apache.org by sa...@apache.org on 2014/09/01 18:50:33 UTC
[09/20] renamed fleece to johnzon
http://git-wip-us.apache.org/repos/asf/incubator-fleece/blob/6e86a53e/johnzon-core/src/main/java/org/apache/johnzon/core/JsonReaderImpl.java
----------------------------------------------------------------------
diff --git a/johnzon-core/src/main/java/org/apache/johnzon/core/JsonReaderImpl.java b/johnzon-core/src/main/java/org/apache/johnzon/core/JsonReaderImpl.java
new file mode 100644
index 0000000..86d7708
--- /dev/null
+++ b/johnzon-core/src/main/java/org/apache/johnzon/core/JsonReaderImpl.java
@@ -0,0 +1,208 @@
+/*
+ * 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 javax.json.JsonArray;
+import javax.json.JsonArrayBuilder;
+import javax.json.JsonObject;
+import javax.json.JsonObjectBuilder;
+import javax.json.JsonReader;
+import javax.json.JsonStructure;
+import javax.json.stream.JsonParser;
+import javax.json.stream.JsonParsingException;
+
+class JsonReaderImpl implements JsonReader {
+ private final JsonParser parser;
+ private boolean closed = false;
+
+ JsonReaderImpl(final JsonParser parser) {
+ this.parser = parser;
+ }
+
+ @Override
+ public JsonStructure read() {
+
+ checkClosed();
+
+ if (!parser.hasNext()) {
+ throw new IllegalStateException("Nothing to read");
+ }
+ switch (parser.next()) {
+ case START_OBJECT:
+ final JsonObjectBuilder objectBuilder = new JsonObjectBuilderImpl();
+ parseObject(objectBuilder);
+ if (parser.hasNext()) {
+ throw new JsonParsingException("Expected end of file", parser.getLocation());
+ }
+ close();
+ return objectBuilder.build();
+ case START_ARRAY:
+ final JsonArrayBuilder arrayBuilder = new JsonArrayBuilderImpl();
+ parseArray(arrayBuilder);
+ if (parser.hasNext()) {
+ throw new JsonParsingException("Expected end of file", parser.getLocation());
+ }
+ close();
+ return arrayBuilder.build();
+ default:
+ close();
+ throw new JsonParsingException("Unknown structure: " + parser.next(), parser.getLocation());
+ }
+
+ }
+
+ @Override
+ public JsonObject readObject() {
+ return JsonObject.class.cast(read());
+ }
+
+ @Override
+ public JsonArray readArray() {
+ return JsonArray.class.cast(read());
+ }
+
+ @Override
+ public void close() {
+
+ if (!closed) {
+ closed = true;
+ parser.close();
+ }
+
+ }
+
+ private void parseObject(final JsonObjectBuilder builder) {
+ String key = null;
+ while (parser.hasNext()) {
+ final JsonParser.Event next = parser.next();
+ switch (next) {
+ case KEY_NAME:
+ key = parser.getString();
+ break;
+
+ case VALUE_STRING:
+ builder.add(key, new JsonStringImpl(parser.getString()));
+ break;
+
+ case START_OBJECT:
+ JsonObjectBuilder subObject = null;
+ parseObject(subObject = new JsonObjectBuilderImpl());
+ builder.add(key, subObject);
+ break;
+
+ case START_ARRAY:
+ JsonArrayBuilder subArray = null;
+ parseArray(subArray = new JsonArrayBuilderImpl());
+ builder.add(key, subArray);
+ break;
+
+ case VALUE_NUMBER:
+ if (parser.isIntegralNumber()) {
+ builder.add(key, new JsonLongImpl(parser.getLong()));
+ } else {
+ builder.add(key, new JsonNumberImpl(parser.getBigDecimal()));
+ }
+ break;
+
+ case VALUE_NULL:
+ builder.addNull(key);
+ break;
+
+ case VALUE_TRUE:
+ builder.add(key, true);
+ break;
+
+ case VALUE_FALSE:
+ builder.add(key, false);
+ break;
+
+ case END_OBJECT:
+ return;
+
+ case END_ARRAY:
+ throw new JsonParsingException("']', shouldn't occur", parser.getLocation());
+
+ default:
+ throw new JsonParsingException(next.name() + ", shouldn't occur", parser.getLocation());
+ }
+ }
+ }
+
+ private void parseArray(final JsonArrayBuilder builder) {
+ while (parser.hasNext()) {
+ final JsonParser.Event next = parser.next();
+ switch (next) {
+ case VALUE_STRING:
+ builder.add(new JsonStringImpl(parser.getString()));
+ break;
+
+ case VALUE_NUMBER:
+ if (parser.isIntegralNumber()) {
+ builder.add(new JsonLongImpl(parser.getLong()));
+ } else {
+ builder.add(new JsonNumberImpl(parser.getBigDecimal()));
+ }
+ break;
+
+ case START_OBJECT:
+ JsonObjectBuilder subObject = null;
+ parseObject(subObject = new JsonObjectBuilderImpl());
+ builder.add(subObject);
+ break;
+
+ case START_ARRAY:
+ JsonArrayBuilder subArray = null;
+ parseArray(subArray = new JsonArrayBuilderImpl());
+ builder.add(subArray);
+ break;
+
+ case END_ARRAY:
+ return;
+
+ case VALUE_NULL:
+ builder.addNull();
+ break;
+
+ case VALUE_TRUE:
+ builder.add(true);
+ break;
+
+ case VALUE_FALSE:
+ builder.add(false);
+ break;
+
+ case KEY_NAME:
+ throw new JsonParsingException("array doesn't have keys", parser.getLocation());
+
+ case END_OBJECT:
+ throw new JsonParsingException("'}', shouldn't occur", parser.getLocation());
+
+ default:
+ throw new JsonParsingException(next.name() + ", shouldn't occur", parser.getLocation());
+ }
+ }
+ }
+
+ private void checkClosed() {
+ if (closed) {
+ throw new IllegalStateException("read(), readObject(), readArray() or close() method was already called");
+ }
+
+ }
+}
http://git-wip-us.apache.org/repos/asf/incubator-fleece/blob/6e86a53e/johnzon-core/src/main/java/org/apache/johnzon/core/JsonStreamParserImpl.java
----------------------------------------------------------------------
diff --git a/johnzon-core/src/main/java/org/apache/johnzon/core/JsonStreamParserImpl.java b/johnzon-core/src/main/java/org/apache/johnzon/core/JsonStreamParserImpl.java
new file mode 100644
index 0000000..931f55f
--- /dev/null
+++ b/johnzon-core/src/main/java/org/apache/johnzon/core/JsonStreamParserImpl.java
@@ -0,0 +1,964 @@
+/*
+ * 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.NoSuchElementException;
+
+import javax.json.JsonException;
+import javax.json.stream.JsonLocation;
+import javax.json.stream.JsonParser;
+import javax.json.stream.JsonParsingException;
+
+//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
+ //normally this is buffer.length, except for the last buffer page 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;
+
+ //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 Integer currentIntegralNumber; //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;
+ }
+ }
+
+ //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) {
+ 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;
+ fallBackCopyBufferLength++;
+ }
+
+ //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.arraycopy(buffer, startOfValueInBuffer, fallBackCopyBuffer, fallBackCopyBufferLength,
+ (endOfValueInBuffer - startOfValueInBuffer));
+ fallBackCopyBufferLength += (endOfValueInBuffer - startOfValueInBuffer);
+
+ }
+
+ startOfValueInBuffer = endOfValueInBuffer = -1;
+ }
+
+ @Override
+ public final boolean hasNext() {
+
+ if (currentStructureElement != null || (previousEvent != END_ARRAY && previousEvent != END_OBJECT) || previousEvent == 0) {
+ return true;
+ }
+
+ //detect garbage at the end of the file after last object or array is closed
+ if (bufferPos < availableCharsInBuffer - 2) {
+
+ final char c = readNextNonWhitespaceChar();
+
+ if (c == EOF) {
+ return false;
+ }
+
+ if (bufferPos < availableCharsInBuffer) {
+ throw uexc("EOF expected");
+ }
+
+ }
+
+ 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 neccessary
+ //if we are currently processing a value (string or number) and buffer
+ //refill is neccessary copy the already readed value part into the value buffer
+ private char readNextChar() {
+
+ if ((buffer.length - 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 {
+ bufferPos++;
+ }
+
+ return buffer[bufferPos];
+ }
+
+ //skip whitespaces
+ //tracks location informations (line, column)
+ //returns the first non whitespace character
+ private char readNextNonWhitespaceChar() {
+
+ int dosCount = 0;
+ char c = readNextChar();
+
+ 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();
+ }
+
+ if (previousEvent != 0 && currentStructureElement == null) {
+ throw uexc("Unexpected end of structure");
+ }
+
+ final char c = readNextNonWhitespaceChar();
+
+ 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 != null) {
+ currentIntegralNumber = null;
+ }
+
+ 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(c);
+ default:
+ throw uexc("Excpected structural character or digit or 't' or 'n' or 'f' or '-'");
+
+ }
+
+ }
+
+ private Event handleStartObject() {
+
+ //last event must one of the following-> : , [
+ if (previousEvent != 0 && previousEvent != KEY_SEPARATOR_EVENT && previousEvent != START_ARRAY && previousEvent != COMMA_EVENT) {
+ throw uexc("Excpected : , [");
+ }
+
+ //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 != 0 && 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 {
+ 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() {
+
+ 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
+
+ }
+
+ //recurse until string is terminated by a non escaped quote
+ readString();
+
+ }
+
+ //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 != 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) {
+ //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(final char c) {
+
+ //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) {
+
+ 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();
+
+ } else {
+ if ((endOfValueInBuffer - startOfValueInBuffer) >= maxValueLength) {
+ throw tmc();
+ }
+ }
+
+ return;
+
+ }
+
+ throw uexc("Unexpected premature end of number");
+
+ }
+
+ //handles false, true, null and numbers
+ private Event handleLiteral(final char c) {
+
+ //last event must one of the following-> : , [
+ if (previousEvent != KEY_SEPARATOR_EVENT && previousEvent != START_ARRAY && previousEvent != COMMA_EVENT) {
+ throw uexc("Excpected : , [");
+ }
+
+ if (previousEvent == COMMA_EVENT && !currentStructureElement.isArray) {
+ //only allowed within array
+ throw uexc("Not in an array context");
+ }
+
+ // 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(c);
+ 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 != null) {
+ 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 != null) {
+ 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 != null) {
+ 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
+ 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);
+ }
+
+}
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/incubator-fleece/blob/6e86a53e/johnzon-core/src/main/java/org/apache/johnzon/core/JsonStringImpl.java
----------------------------------------------------------------------
diff --git a/johnzon-core/src/main/java/org/apache/johnzon/core/JsonStringImpl.java b/johnzon-core/src/main/java/org/apache/johnzon/core/JsonStringImpl.java
new file mode 100644
index 0000000..a7e8f29
--- /dev/null
+++ b/johnzon-core/src/main/java/org/apache/johnzon/core/JsonStringImpl.java
@@ -0,0 +1,76 @@
+/*
+ * 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 javax.json.JsonString;
+
+final class JsonStringImpl implements JsonString {
+ private final String value;
+ private String escape;
+ private Integer hashCode = null;
+
+
+ JsonStringImpl(final String value) {
+ if(value == null) {
+ throw new NullPointerException("value must not be null");
+ }
+
+ this.value = value;
+ }
+
+ @Override
+ public String getString() {
+ return value;
+ }
+
+ @Override
+ public CharSequence getChars() {
+ return value;
+ }
+
+ @Override
+ public ValueType getValueType() {
+ return ValueType.STRING;
+ }
+
+ @Override
+ public String toString() {
+ String s = escape;
+ if (s == null) {
+ s = JsonChars.QUOTE_CHAR+Strings.escape(value)+JsonChars.QUOTE_CHAR;
+ escape=s;
+ }
+ return s;
+ }
+
+ @Override
+ public int hashCode() {
+ Integer h = hashCode;
+ if (h == null) {
+ h = value.hashCode();
+ hashCode=h;
+ }
+ return h;
+ }
+
+ @Override
+ public boolean equals(final Object obj) {
+ return JsonString.class.isInstance(obj) && JsonString.class.cast(obj).getString().equals(value);
+ }
+}
http://git-wip-us.apache.org/repos/asf/incubator-fleece/blob/6e86a53e/johnzon-core/src/main/java/org/apache/johnzon/core/JsonWriterFactoryImpl.java
----------------------------------------------------------------------
diff --git a/johnzon-core/src/main/java/org/apache/johnzon/core/JsonWriterFactoryImpl.java b/johnzon-core/src/main/java/org/apache/johnzon/core/JsonWriterFactoryImpl.java
new file mode 100644
index 0000000..e7d30b8
--- /dev/null
+++ b/johnzon-core/src/main/java/org/apache/johnzon/core/JsonWriterFactoryImpl.java
@@ -0,0 +1,77 @@
+/*
+ * 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.OutputStream;
+import java.io.OutputStreamWriter;
+import java.io.Serializable;
+import java.io.Writer;
+import java.nio.charset.Charset;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.json.JsonWriter;
+import javax.json.JsonWriterFactory;
+import javax.json.stream.JsonGenerator;
+import javax.json.stream.JsonGeneratorFactory;
+
+class JsonWriterFactoryImpl implements JsonWriterFactory, Serializable {
+ private static final Charset UTF8_CHARSET = Charset.forName("UTF-8");
+ private final Map<String, Object> internalConfig = new HashMap<String, Object>();
+ private static final String[] SUPPORTED_CONFIG_KEYS = new String[] {
+
+ JsonGenerator.PRETTY_PRINTING
+
+ };
+ private final JsonGeneratorFactory factory;
+
+ JsonWriterFactoryImpl(final Map<String, ?> config) {
+ if (config != null) {
+
+ for (final String configKey : SUPPORTED_CONFIG_KEYS) {
+ if (config.containsKey(configKey)) {
+ internalConfig.put(configKey, config.get(configKey));
+ }
+ }
+ }
+
+ this.factory = new JsonGeneratorFactoryImpl(internalConfig);
+ }
+
+ @Override
+ public JsonWriter createWriter(final Writer writer) {
+ return new JsonWriterImpl(factory.createGenerator(writer));
+ }
+
+ @Override
+ public JsonWriter createWriter(final OutputStream out) {
+ return createWriter(new OutputStreamWriter(out, UTF8_CHARSET));
+ }
+
+ @Override
+ public JsonWriter createWriter(final OutputStream out, final Charset charset) {
+ return createWriter(new OutputStreamWriter(out, charset));
+ }
+
+ @Override
+ public Map<String, ?> getConfigInUse() {
+ return Collections.unmodifiableMap(internalConfig);
+ }
+}
http://git-wip-us.apache.org/repos/asf/incubator-fleece/blob/6e86a53e/johnzon-core/src/main/java/org/apache/johnzon/core/JsonWriterImpl.java
----------------------------------------------------------------------
diff --git a/johnzon-core/src/main/java/org/apache/johnzon/core/JsonWriterImpl.java b/johnzon-core/src/main/java/org/apache/johnzon/core/JsonWriterImpl.java
new file mode 100644
index 0000000..e012ae1
--- /dev/null
+++ b/johnzon-core/src/main/java/org/apache/johnzon/core/JsonWriterImpl.java
@@ -0,0 +1,73 @@
+/*
+ * 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.Serializable;
+
+import javax.json.JsonArray;
+import javax.json.JsonObject;
+import javax.json.JsonStructure;
+import javax.json.JsonWriter;
+import javax.json.stream.JsonGenerator;
+
+class JsonWriterImpl implements JsonWriter, Serializable{
+ private final JsonGenerator generator;
+ private boolean closed = false;
+
+ JsonWriterImpl(final JsonGenerator generator) {
+ this.generator = generator;
+ }
+
+ @Override
+ public void writeArray(final JsonArray array) {
+ checkClosed();
+ generator.write(array);
+ close();
+ }
+
+ @Override
+ public void writeObject(final JsonObject object) {
+ checkClosed();
+ generator.write(object);
+ close();
+ }
+
+ @Override
+ public void write(final JsonStructure value) {
+ checkClosed();
+ generator.write(value);
+ close();
+ }
+
+ @Override
+ public void close() {
+
+ if(!closed) {
+ closed = true;
+ generator.close();
+ }
+ }
+
+ private void checkClosed() {
+ if(closed) {
+ throw new IllegalStateException("writeArray(), writeObject(), write() or close() method was already called");
+ }
+
+ }
+}
http://git-wip-us.apache.org/repos/asf/incubator-fleece/blob/6e86a53e/johnzon-core/src/main/java/org/apache/johnzon/core/RFC4627AwareInputStreamReader.java
----------------------------------------------------------------------
diff --git a/johnzon-core/src/main/java/org/apache/johnzon/core/RFC4627AwareInputStreamReader.java b/johnzon-core/src/main/java/org/apache/johnzon/core/RFC4627AwareInputStreamReader.java
new file mode 100644
index 0000000..ceff1a9
--- /dev/null
+++ b/johnzon-core/src/main/java/org/apache/johnzon/core/RFC4627AwareInputStreamReader.java
@@ -0,0 +1,130 @@
+/*
+ * 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.PushbackInputStream;
+import java.nio.charset.Charset;
+
+import javax.json.JsonException;
+
+final class RFC4627AwareInputStreamReader extends InputStreamReader {
+
+ RFC4627AwareInputStreamReader(final InputStream in) {
+ this(new PushbackInputStream(in,4));
+ }
+
+ private RFC4627AwareInputStreamReader(final PushbackInputStream in) {
+ super(in, getCharset(in).newDecoder());
+
+ }
+
+
+ /*
+ * RFC 4627
+
+ JSON text SHALL be encoded in Unicode. The default encoding is
+ UTF-8.
+
+ Since the first two characters of a JSON text will always be ASCII
+ characters [RFC0020], it is possible to determine whether an octet
+ stream is UTF-8, UTF-16 (BE or LE), or UTF-32 (BE or LE) by looking
+ at the pattern of nulls in the first four octets.
+
+ 00 00 00 xx UTF-32BE
+ 00 xx 00 xx UTF-16BE
+ xx 00 00 00 UTF-32LE
+ xx 00 xx 00 UTF-16LE
+ xx xx xx xx UTF-8
+
+ */
+
+ private static Charset getCharset(final PushbackInputStream inputStream) {
+ Charset charset = Charset.forName("UTF-8");
+ final byte[] utfBytes = new byte[4];
+ int bomLength=0;
+ try {
+ final int read = inputStream.read(utfBytes);
+ if (read < 2) {
+ throw new JsonException("Invalid Json. Valid Json has at least 2 bytes");
+ } else {
+
+ int first = (utfBytes[0] & 0xFF);
+ int second = (utfBytes[1] & 0xFF);
+
+ if (first == 0x00) {
+ charset = (second == 0x00) ? Charset.forName("UTF-32BE") : Charset.forName("UTF-16BE");
+ } else if (read > 2 && second == 0x00) {
+ int third = (utfBytes[2] & 0xFF);
+ charset = (third == 0x00) ? Charset.forName("UTF-32LE") : Charset.forName("UTF-16LE");
+ } else {
+
+ /*check BOM
+
+ Encoding hex byte order mark
+ UTF-8 EF BB BF
+ UTF-16 (BE) FE FF
+ UTF-16 (LE) FF FE
+ UTF-32 (BE) 00 00 FE FF
+ UTF-32 (LE) FF FE 00 00
+ */
+
+
+
+
+ if(first == 0xFE && second == 0xFF) {
+ charset = Charset.forName("UTF-16BE");
+ bomLength=2;
+ } else if(read > 3 && first == 0x00 && second == 0x00 && (utfBytes[2]&0xff) == 0xFE && (utfBytes[3]&0xff) == 0xFF){
+ charset = Charset.forName("UTF-32BE");
+ bomLength=4;
+ } else if(first == 0xFF && second == 0xFE) {
+
+ if(read > 3 && (utfBytes[2]&0xff) == 0x00 && (utfBytes[3]&0xff) == 0x00) {
+ charset = Charset.forName("UTF-32LE");
+ bomLength=4;
+ }else {
+ charset = Charset.forName("UTF-16LE");
+ bomLength=2;
+ }
+
+ }
+
+ //assume UTF8
+
+ }
+
+ }
+
+ if(bomLength < 4) {
+ inputStream.unread(utfBytes,bomLength==2?2:0,read-bomLength);
+ }
+
+
+
+ } catch (final IOException e) {
+ throw new JsonException("Unable to detect charset due to "+e.getMessage(), e);
+ }
+
+ return charset;
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/incubator-fleece/blob/6e86a53e/johnzon-core/src/main/java/org/apache/johnzon/core/Strings.java
----------------------------------------------------------------------
diff --git a/johnzon-core/src/main/java/org/apache/johnzon/core/Strings.java b/johnzon-core/src/main/java/org/apache/johnzon/core/Strings.java
new file mode 100644
index 0000000..4ed4d9e
--- /dev/null
+++ b/johnzon-core/src/main/java/org/apache/johnzon/core/Strings.java
@@ -0,0 +1,127 @@
+/*
+ * 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.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+
+import javax.json.stream.JsonParsingException;
+
+class Strings implements JsonChars {
+ private static final BufferStrategy.BufferProvider<StringBuilder> BUILDER_CACHE =
+ BufferStrategy.valueOf(System.getProperty("johnzon.string-builder.strategy", "QUEUE"))
+ .newStringBuilderProvider(Integer.getInteger("org.apache.johnzon.default-string-builder", 1024));
+
+ private static final String UNICODE_PREFIX = "\\u";
+ private static final String UNICODE_PREFIX_HELPER = "000";
+ private static final ConcurrentMap<Character, String> UNICODE_CACHE = new ConcurrentHashMap<Character, String>();
+
+ static char asEscapedChar(final char current) {
+ switch (current) {
+ case 'r':
+ return '\r';
+ case 't':
+ return '\t';
+ case 'b':
+ return '\b';
+ case 'f':
+ return '\f';
+ case 'n':
+ return '\n';
+ case '"':
+ return '\"';
+ case '\\':
+ return '\\';
+ case '/':
+ return '/';
+ default:
+ if(Character.isHighSurrogate(current) || Character.isLowSurrogate(current)) {
+ return current;
+ }
+ throw new JsonParsingException("Invalid escape sequence '"+current +"' (Codepoint: "+String.valueOf(current).
+ codePointAt(0),JsonLocationImpl.UNKNOW_LOCATION);
+ }
+
+ }
+
+ static String escape(final String value) {
+
+ if(value == null || value.length()==0) {
+ return value;
+ }
+
+ final StringBuilder builder = BUILDER_CACHE.newBuffer();
+ try {
+ for (int i = 0; i < value.length(); i++) {
+ final char c = value.charAt(i);
+ switch (c) {
+ case QUOTE_CHAR:
+ case ESCAPE_CHAR:
+ builder.append(ESCAPE_CHAR).append(c);
+ break;
+ default:
+ if (c < SPACE) { // we could do a single switch but actually we should rarely enter this if so no need to pay it
+ switch (c) {
+ case EOL:
+ builder.append("\\n");
+ break;
+ case '\r':
+ builder.append("\\r");
+ break;
+ case '\t':
+ builder.append("\\t");
+ break;
+ case '\b':
+ builder.append("\\b");
+ break;
+ case '\f':
+ builder.append("\\f");
+ break;
+ default:
+ builder.append(toUnicode(c));
+ }
+ } else if ((c >= '\u0080' && c < '\u00a0') || (c >= '\u2000' && c < '\u2100')) {
+ builder.append(toUnicode(c));
+ } else {
+ builder.append(c);
+ }
+ }
+ }
+ return builder.toString();
+ } finally {
+ BUILDER_CACHE.release(builder);
+ }
+ }
+
+ private static String toUnicode(final char c) {
+ final String found = UNICODE_CACHE.get(c);
+ if (found != null) {
+ return found;
+ }
+
+ final String hex = UNICODE_PREFIX_HELPER + Integer.toHexString(c);
+ final String s = UNICODE_PREFIX + hex.substring(hex.length() - 4);
+ UNICODE_CACHE.putIfAbsent(c, s);
+ return s;
+ }
+
+ private Strings() {
+ // no-op
+ }
+}
http://git-wip-us.apache.org/repos/asf/incubator-fleece/blob/6e86a53e/johnzon-core/src/main/java/org/apache/johnzon/core/ThreadLocalBufferCache.java
----------------------------------------------------------------------
diff --git a/johnzon-core/src/main/java/org/apache/johnzon/core/ThreadLocalBufferCache.java b/johnzon-core/src/main/java/org/apache/johnzon/core/ThreadLocalBufferCache.java
new file mode 100644
index 0000000..f973fcd
--- /dev/null
+++ b/johnzon-core/src/main/java/org/apache/johnzon/core/ThreadLocalBufferCache.java
@@ -0,0 +1,53 @@
+/*
+ * 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.Serializable;
+
+public abstract class ThreadLocalBufferCache<T> implements Serializable {
+ // alloc are expensive so using a very trivial buffer, we remove from TL to say we use it and reset it when we are done (close)
+ private final ThreadLocal<T> buffers;
+ private final int defaultSize;
+
+ public ThreadLocalBufferCache(final int defaultSize) {
+ this.buffers = new ThreadLocal<T>() {
+ @Override
+ protected T initialValue() {
+ return ThreadLocalBufferCache.this.newValue(defaultSize);
+ }
+ };
+ this.defaultSize = defaultSize;
+ }
+
+ protected abstract T newValue(final int defaultSize);
+
+ public T getCache() {
+ final T cachedLoadedChars = buffers.get();
+ if (cachedLoadedChars == null) {
+ return newValue(defaultSize);
+ }
+ buffers.set(null); // remove just kills the buffers
+ return cachedLoadedChars;
+ }
+
+
+ public void release(final T buffer) {
+ buffers.set(buffer);
+ }
+}
http://git-wip-us.apache.org/repos/asf/incubator-fleece/blob/6e86a53e/johnzon-core/src/main/resources/META-INF/services/javax.json.spi.JsonProvider
----------------------------------------------------------------------
diff --git a/johnzon-core/src/main/resources/META-INF/services/javax.json.spi.JsonProvider b/johnzon-core/src/main/resources/META-INF/services/javax.json.spi.JsonProvider
new file mode 100644
index 0000000..22e9dd7
--- /dev/null
+++ b/johnzon-core/src/main/resources/META-INF/services/javax.json.spi.JsonProvider
@@ -0,0 +1 @@
+org.apache.johnzon.core.JsonProviderImpl
http://git-wip-us.apache.org/repos/asf/incubator-fleece/blob/6e86a53e/johnzon-core/src/test/java/org/apache/johnzon/core/JsonArrayBuilderImplTest.java
----------------------------------------------------------------------
diff --git a/johnzon-core/src/test/java/org/apache/johnzon/core/JsonArrayBuilderImplTest.java b/johnzon-core/src/test/java/org/apache/johnzon/core/JsonArrayBuilderImplTest.java
new file mode 100644
index 0000000..df7a1af
--- /dev/null
+++ b/johnzon-core/src/test/java/org/apache/johnzon/core/JsonArrayBuilderImplTest.java
@@ -0,0 +1,170 @@
+/*
+ * 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 static org.junit.Assert.assertEquals;
+
+import java.math.BigDecimal;
+import java.math.BigInteger;
+
+import javax.json.Json;
+import javax.json.JsonArrayBuilder;
+import javax.json.JsonObjectBuilder;
+import javax.json.JsonValue;
+
+import org.junit.Test;
+
+public class JsonArrayBuilderImplTest {
+ @Test
+ public void array() {
+ final JsonArrayBuilder builder = Json.createArrayBuilder();
+ builder.add("a").add("b");
+ assertEquals("[\"a\",\"b\"]", builder.build().toString());
+ }
+
+ @Test
+ public void escapedStringArray() {
+ final JsonArrayBuilder builder = Json.createArrayBuilder();
+ builder.add("a\"").add("\u0000");
+ assertEquals("[\"a\\\"\",\"\\u0000\"]", builder.build().toString());
+ }
+
+ @Test
+ public void emptyArray() {
+ final JsonArrayBuilder builder = Json.createArrayBuilder();
+ assertEquals("[]", builder.build().toString());
+ }
+
+ @Test
+ public void emptyArrayInEmtyArray() {
+ final JsonArrayBuilder builder = Json.createArrayBuilder();
+ builder.add(Json.createArrayBuilder());
+ assertEquals("[[]]", builder.build().toString());
+ }
+
+ @Test
+ public void arrayInArray() {
+ final JsonArrayBuilder builder = Json.createArrayBuilder();
+ builder.add(3).add(4);
+ final JsonArrayBuilder builder2 = Json.createArrayBuilder();
+ builder2.add(1).add(2).add(builder);
+ assertEquals("[1,2,[3,4]]", builder2.build().toString());
+ }
+
+ @Test
+ public void arrayObjectInArray() {
+ final JsonObjectBuilder objectBuilder = Json.createObjectBuilder();
+ objectBuilder.add("key", "val");
+ final JsonArrayBuilder builder = Json.createArrayBuilder();
+ builder.add(3).add(4).add(objectBuilder);
+ final JsonArrayBuilder builder2 = Json.createArrayBuilder();
+ builder2.add(1).add(2).add(builder);
+ assertEquals("[1,2,[3,4,{\"key\":\"val\"}]]", builder2.build().toString());
+ }
+
+ @Test
+ public void nullArray() {
+ final JsonArrayBuilder builder = Json.createArrayBuilder();
+ builder.addNull().addNull();
+ assertEquals("[null,null]", builder.build().toString());
+ }
+
+ @Test
+ public void nullArrayNonChaining() {
+ final JsonArrayBuilder builder = Json.createArrayBuilder();
+ builder.addNull();
+ builder.addNull();
+ assertEquals("[null,null]", builder.build().toString());
+ }
+
+ @Test
+ public void nullJsonValueArray() {
+ final JsonArrayBuilder builder = Json.createArrayBuilder();
+ builder.add(JsonValue.NULL).add(JsonValue.NULL);
+ assertEquals("[null,null]", builder.build().toString());
+ }
+
+ @Test
+ public void boolJsonValueArray() {
+ final JsonArrayBuilder builder = Json.createArrayBuilder();
+ builder.add(JsonValue.TRUE).add(JsonValue.FALSE);
+ assertEquals("[true,false]", builder.build().toString());
+ }
+
+ @Test
+ public void numJsonValueArray() {
+ final JsonArrayBuilder builder = Json.createArrayBuilder();
+ builder.add(123.12d).add(new BigDecimal("456.789E-12")).add(-0).add(0).add((short)1).add((byte)1);
+ assertEquals("[123.12,4.56789E-10,0,0,1,1]", builder.build().toString());
+ }
+
+ @Test(expected=NullPointerException.class)
+ public void addStringNpeIfNull() {
+ final JsonArrayBuilder builder = Json.createArrayBuilder();
+ builder.add((String) null);
+ }
+
+ @Test(expected=NullPointerException.class)
+ public void addJVNpeIfNull() {
+ final JsonArrayBuilder builder = Json.createArrayBuilder();
+ builder.add((JsonValue) null);
+ }
+
+ @Test(expected=NullPointerException.class)
+ public void addBDNpeIfNull() {
+ final JsonArrayBuilder builder = Json.createArrayBuilder();
+ builder.add((BigDecimal) null);
+ }
+
+ @Test(expected=NullPointerException.class)
+ public void addBINpeIfNull() {
+ final JsonArrayBuilder builder = Json.createArrayBuilder();
+ builder.add((BigInteger) null);
+ }
+
+ @Test(expected=NullPointerException.class)
+ public void addJABuilderNpeIfNull() {
+ final JsonArrayBuilder builder = Json.createArrayBuilder();
+ builder.add((JsonArrayBuilder) null);
+ }
+
+ @Test(expected=NullPointerException.class)
+ public void addJOBuilderNpeIfNull() {
+ final JsonArrayBuilder builder = Json.createArrayBuilder();
+ builder.add((JsonObjectBuilder) null);
+ }
+
+ @Test(expected=NumberFormatException.class)
+ public void addDoubleNpeIfNaN() {
+ final JsonArrayBuilder builder = Json.createArrayBuilder();
+ builder.add((double) Double.NaN);
+ }
+
+ @Test(expected=NumberFormatException.class)
+ public void addDoubleNpeIfPosInfinite() {
+ final JsonArrayBuilder builder = Json.createArrayBuilder();
+ builder.add((double) Double.POSITIVE_INFINITY);
+ }
+
+ @Test(expected=NumberFormatException.class)
+ public void addDoubleNpeIfNegIfinite() {
+ final JsonArrayBuilder builder = Json.createArrayBuilder();
+ builder.add((double) Double.NEGATIVE_INFINITY);
+ }
+}
http://git-wip-us.apache.org/repos/asf/incubator-fleece/blob/6e86a53e/johnzon-core/src/test/java/org/apache/johnzon/core/JsonArrayImplTest.java
----------------------------------------------------------------------
diff --git a/johnzon-core/src/test/java/org/apache/johnzon/core/JsonArrayImplTest.java b/johnzon-core/src/test/java/org/apache/johnzon/core/JsonArrayImplTest.java
new file mode 100644
index 0000000..b0918b1
--- /dev/null
+++ b/johnzon-core/src/test/java/org/apache/johnzon/core/JsonArrayImplTest.java
@@ -0,0 +1,61 @@
+/*
+ * 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 static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import javax.json.Json;
+import javax.json.JsonArray;
+import javax.json.JsonArrayBuilder;
+
+import org.junit.Test;
+
+public class JsonArrayImplTest {
+ @Test
+ public void arrayToString() {
+ JsonArrayBuilder ab = Json.createArrayBuilder();
+
+ ab.add(new JsonStringImpl("a"));
+ ab.add(new JsonStringImpl("b"));
+ assertEquals("[\"a\",\"b\"]", ab.build().toString());
+ }
+
+ @Test
+ public void arrayIndex() {
+ JsonArrayBuilder ab = Json.createArrayBuilder();
+ ab.add(new JsonStringImpl("a"));
+ ab.add(new JsonStringImpl("b"));
+ ab.add(new JsonLongImpl(5));
+ final JsonArray array = (JsonArray) ab.build();
+ assertFalse(array.isEmpty());
+ assertEquals("a", array.getJsonString(0).getString());
+ assertEquals("b", array.getJsonString(1).getString());
+ assertEquals(5, array.getJsonNumber(2).longValue());
+ assertEquals("[\"a\",\"b\",5]", array.toString());
+ }
+
+ @Test
+ public void emptyArray() {
+ final JsonArray array = Json.createArrayBuilder().build();
+ assertTrue(array.isEmpty());
+ assertEquals("[]", array.toString());
+ }
+}
http://git-wip-us.apache.org/repos/asf/incubator-fleece/blob/6e86a53e/johnzon-core/src/test/java/org/apache/johnzon/core/JsonGeneratorImplTest.java
----------------------------------------------------------------------
diff --git a/johnzon-core/src/test/java/org/apache/johnzon/core/JsonGeneratorImplTest.java b/johnzon-core/src/test/java/org/apache/johnzon/core/JsonGeneratorImplTest.java
new file mode 100644
index 0000000..4f73aaa
--- /dev/null
+++ b/johnzon-core/src/test/java/org/apache/johnzon/core/JsonGeneratorImplTest.java
@@ -0,0 +1,281 @@
+/*
+ * 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 static org.junit.Assert.assertEquals;
+
+import java.io.ByteArrayOutputStream;
+import java.math.BigDecimal;
+import java.math.BigInteger;
+import java.util.HashMap;
+
+import javax.json.Json;
+import javax.json.JsonValue;
+import javax.json.stream.JsonGenerationException;
+import javax.json.stream.JsonGenerator;
+
+import org.junit.Test;
+
+public class JsonGeneratorImplTest {
+ @Test
+ public void notFluentGeneratorUsage() {
+ final ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ final JsonGenerator generator = Json.createGenerator(baos);
+ generator.writeStartArray();
+ generator.writeStartObject();
+ generator.writeEnd();
+ generator.writeEnd();
+ generator.close();
+ assertEquals("[{}]", new String(baos.toByteArray()));
+ }
+
+ @Test
+ public void emptyArray() {
+ final ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ final JsonGenerator generator = Json.createGenerator(baos);
+ generator.writeStartArray().writeEnd().close();
+ assertEquals("[]", new String(baos.toByteArray()));
+ }
+
+ @Test
+ public void simpleArray() {
+ final ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ Json.createGenerator(baos).writeStartArray().write(1).write(2).writeEnd().close();
+ assertEquals("[1,2]", new String(baos.toByteArray()));
+ }
+
+ @Test
+ public void stringArray() {
+ final ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ Json.createGenerator(baos).writeStartArray().write("val1").write("val2").writeEnd().close();
+ assertEquals("[\"val1\",\"val2\"]", new String(baos.toByteArray()));
+ }
+
+ @Test
+ public void stringArrayEscapes() {
+ final ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ Json.createGenerator(baos).writeStartArray().write("\"val1\t\u0010").write("val2\\").writeEnd().close();
+ assertEquals("[\"\\\"val1\\t\\u0010\",\"val2\\\\\"]", new String(baos.toByteArray()));
+ }
+
+ @Test
+ public void stringArrayEscapes2() {
+ final ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ Json.createGenerator(baos).writeStartArray().write("\"val1\t\u0067").write("val2\\").writeEnd().close();
+ assertEquals("[\"\\\"val1\\tg\",\"val2\\\\\"]", new String(baos.toByteArray()));
+ }
+
+ @Test
+ public void emptyStringArray() {
+ final ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ Json.createGenerator(baos).writeStartArray().writeNull().write("").writeEnd().close();
+ assertEquals("[null,\"\"]", new String(baos.toByteArray()));
+ }
+
+ @Test
+ public void nullLiteralArray() {
+ final ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ Json.createGenerator(baos).writeStartArray().writeNull().write(JsonValue.NULL).writeEnd().close();
+ assertEquals("[null,null]", new String(baos.toByteArray()));
+ }
+
+ @Test
+ public void boolLiteralArray() {
+ final ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ Json.createGenerator(baos).writeStartArray().write(JsonValue.FALSE).write(JsonValue.TRUE).writeEnd().close();
+ assertEquals("[false,true]", new String(baos.toByteArray()));
+ }
+
+ @Test(expected=JsonGenerationException.class)
+ public void fail1() {
+ final ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ Json.createGenerator(baos)
+ .writeStartArray("test");
+ }
+
+ @Test(expected=JsonGenerationException.class)
+ public void fail2() {
+ final ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ Json.createGenerator(baos)
+ .write("test",1);
+ }
+
+ @Test(expected=JsonGenerationException.class)
+ public void fail3() {
+ final ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ Json.createGenerator(baos)
+ .writeStartObject()
+ .writeStartObject();
+ }
+
+ @Test(expected=JsonGenerationException.class)
+ public void fail4() {
+ final ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ Json.createGenerator(baos)
+ .writeEnd();
+ }
+
+ @Test(expected=JsonGenerationException.class)
+ public void fail5() {
+ final ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ Json.createGenerator(baos)
+ .close();
+ }
+
+ @Test(expected=JsonGenerationException.class)
+ public void fail6() {
+ final ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ Json.createGenerator(baos)
+ .writeStartArray()
+ .writeStartObject("test");
+ }
+
+ @Test(expected=JsonGenerationException.class)
+ public void fail7() {
+ final ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ Json.createGenerator(baos)
+ .writeStartArray()
+ .writeNull()
+ .writeStartObject()
+ .write("a", new BigDecimal("123.123"))
+ .write("b", true)
+ .write("c", new BigInteger("3312"))
+ .write("d", new JsonStringImpl("mystring"))
+ .writeEnd()
+ .close();
+
+ }
+
+ @Test(expected=JsonGenerationException.class)
+ public void fail9() {
+ final ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ Json.createGenerator(baos)
+ .writeStartObject()
+ .write("a", new BigDecimal("123.123"))
+ .write("b", true)
+ .write("c", new BigInteger("3312"))
+ .write("d", new JsonStringImpl("mystring"))
+ .writeEnd()
+ .writeStartObject()
+ .close();
+
+ }
+
+ @Test
+ public void numbers() {
+ final ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ Json.createGenerator(baos)
+ .writeStartArray()
+ .writeNull()
+ .writeStartObject()
+ .write("a", new BigDecimal("123.123"))
+ .write("b", true)
+ .write("c", new BigInteger("3312"))
+ .write("d", new JsonStringImpl("Mystring"))
+ .writeEnd()
+ .writeEnd()
+ .close();
+ assertEquals("[null,{\"a\":123.123,\"b\":true,\"c\":3312,\"d\":\"Mystring\"}]", new String(baos.toByteArray()));
+ }
+
+ @Test
+ public void numbers2() {
+ final ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ Json.createGenerator(baos)
+ .writeStartArray()
+ .writeNull()
+ .writeStartObject()
+ .write("a", 999999999L)
+ .write("b", 123)
+ .write("c", -444444444L)
+ .write("d",-123)
+ .writeEnd()
+ .writeEnd()
+ .close();
+ assertEquals("[null,{\"a\":999999999,\"b\":123,\"c\":-444444444,\"d\":-123}]", new String(baos.toByteArray()));
+ }
+
+ @Test
+ public void arrayInArray() {
+ final ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ Json.createGenerator(baos)
+ .writeStartArray()
+ .writeStartArray()
+ .writeNull()
+ .writeEnd()
+ .writeEnd()
+ .close();
+ assertEquals("[[null]]", new String(baos.toByteArray()));
+ }
+
+
+ @Test
+ public void generate() {
+ final ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ final JsonGenerator generator = Json.createGenerator(baos);
+
+ generator.writeStartObject().write("firstName", "John").write("lastName", "Smith").write("age", 25)
+ .writeStartObject("address").write("streetAddress", "21 2nd Street").write("city", "New York")
+ .write("state", "NY").write("postalCode", "10021").writeEnd().writeStartArray("phoneNumber")
+ .writeStartObject().write("type", "home").write("number", "212 555-1234").writeEnd().writeStartObject()
+ .write("type", "fax").write("number", "646 555-4567").writeEnd().writeEnd().writeEnd().close();
+
+ assertEquals("{\"firstName\":\"John\",\"lastName\":\"Smith\",\"age\":25,\"address\":"
+ + "{\"streetAddress\":\"21 2nd Street\",\"city\":\"New York\",\"state\":\"NY\",\"postalCode\":\"10021\"},"
+ + "\"phoneNumber\":[{\"type\":\"home\",\"number\":\"212 555-1234\"},{\"type\":\"fax\",\"number\":\"646 555-4567\"}]}",
+ new String(baos.toByteArray()));
+ }
+
+ @Test
+ public void pretty() {
+ final ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ final JsonGenerator generator = Json.createGeneratorFactory(new HashMap<String, Object>() {{
+ put(JsonGenerator.PRETTY_PRINTING, true);
+ }}).createGenerator(baos);
+
+ generator.writeStartObject().write("firstName", "John").write("lastName", "Smith")
+ .write("age", 25).writeStartObject("address").write("streetAddress", "21 2nd Street")
+ .write("city", "New York").write("state", "NY").write("postalCode", "10021").writeEnd()
+ .writeStartArray("phoneNumber").writeStartObject().write("type", "home").write("number", "212 555-1234")
+ .writeEnd().writeStartObject().write("type", "fax").write("number", "646 555-4567").writeEnd().writeEnd()
+ .writeEnd().close();
+
+ assertEquals("{\n" +
+ " \"firstName\":\"John\",\n" +
+ " \"lastName\":\"Smith\",\n" +
+ " \"age\":25,\n" +
+ " \"address\":{\n" +
+ " \"streetAddress\":\"21 2nd Street\",\n" +
+ " \"city\":\"New York\",\n" +
+ " \"state\":\"NY\",\n" +
+ " \"postalCode\":\"10021\"\n" +
+ " },\n" +
+ " \"phoneNumber\":[\n" +
+ " {\n" +
+ " \"type\":\"home\",\n" +
+ " \"number\":\"212 555-1234\"\n" +
+ " },\n" +
+ " {\n" +
+ " \"type\":\"fax\",\n" +
+ " \"number\":\"646 555-4567\"\n" +
+ " }\n" +
+ " ]\n" +
+ "}", new String(baos.toByteArray()));
+ }
+}
http://git-wip-us.apache.org/repos/asf/incubator-fleece/blob/6e86a53e/johnzon-core/src/test/java/org/apache/johnzon/core/JsonNumberTest.java
----------------------------------------------------------------------
diff --git a/johnzon-core/src/test/java/org/apache/johnzon/core/JsonNumberTest.java b/johnzon-core/src/test/java/org/apache/johnzon/core/JsonNumberTest.java
new file mode 100644
index 0000000..b610995
--- /dev/null
+++ b/johnzon-core/src/test/java/org/apache/johnzon/core/JsonNumberTest.java
@@ -0,0 +1,51 @@
+/*
+ * 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.math.BigInteger;
+
+import javax.json.Json;
+import javax.json.JsonArray;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+
+
+public class JsonNumberTest {
+
+ @Test(expected=ArithmeticException.class)
+ public void testBigIntegerExact() {
+
+ JsonArray array = Json.createArrayBuilder().add(100.0200).build();
+ array.getJsonNumber(0).bigIntegerValueExact();
+
+
+ }
+
+ @Test
+ public void testBigInteger() {
+
+ JsonArray array = Json.createArrayBuilder().add(100.0200).build();
+ Assert.assertEquals(new BigInteger("100"), array.getJsonNumber(0).bigIntegerValue());
+
+
+ }
+
+}