com.speedment.common.json.internal.JsonDeserializer Maven / Gradle / Ivy
Show all versions of runtime-deploy Show documentation
/**
*
* Copyright (c) 2006-2019, Speedment, Inc. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License"); You may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package com.speedment.common.json.internal;
import com.speedment.common.json.JsonSyntaxException;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.StringJoiner;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Consumer;
/**
* Internal class that parses a stream of JSON characters into an object
* representation.
*
* This implementation includes a line and column counter that makes it easier
* to debug an erroneous JSON string. These might add additional overhead to the
* parsing time and should therefore only be used if the input is expected to
* have errors.
*
* @author Emil Forslund
* @since 1.0.0
*/
public final class JsonDeserializer implements AutoCloseable {
private final static String ENCODING = "UTF-8";
private final static int TAB_SIZE = 4;
private final InputStreamReader reader;
private final AtomicLong row;
private final AtomicLong col;
private int character;
public JsonDeserializer(InputStream in) throws UnsupportedEncodingException {
reader = new InputStreamReader(in, ENCODING);
row = new AtomicLong(0);
col = new AtomicLong(0);
}
private enum CloseMethod {
EXIT_FROM_PARENT,
CONTINUE_IN_PARENT,
NOT_DECIDED
}
public Object get() throws IOException {
switch (nextNonBlankspace()) {
case 0x7B : // { (begin parsing object)
return parseObject();
case 0x5B : // [ (begin parsing array)
return parseArray();
case 0x22 : // " (begin parsing string)
return parseString();
case 0x66 : // f (begin parsing false)
return parseFalse();
case 0x74 : // t (begin parsing true)
return parseTrue();
case 0x6E : // n (begin parsing null)
return parseNull();
// Digit '0 - 9'
case 0x30 : case 0x31 : case 0x32 : case 0x33 : case 0x34 :
case 0x35 : case 0x36 : case 0x37 : case 0x38 : case 0x39 :
case 0x2E : // . (decimal sign)
case 0x2D : // - (minus sign)
final AtomicReference number = new AtomicReference<>();
if (parseNumber(number::set) == CloseMethod.NOT_DECIDED) {
return number.get();
}
}
throw unexpectedCharacterException();
}
private Map parseObject() throws IOException {
final Map object = new LinkedHashMap<>();
firstChar: switch (nextNonBlankspace()) {
// If the map should be closed with no entries:
case 0x7D : // } (close the map)
return object;
// If this character begins a new entry
case 0x22 : // " (begin key)
final CloseMethod close = parseEntryInto(object);
switch (close) {
case EXIT_FROM_PARENT :
if (character == 0x7D) { // }
return object;
} else {
throw unexpectedCharacterException();
}
case CONTINUE_IN_PARENT :
break firstChar;
case NOT_DECIDED :
switch (nextNonBlankspace()) {
case 0x2C : // , (continue with next entry)
break firstChar;
case 0x7D : // } (close the map)
return object;
default :
throw unexpectedCharacterException();
}
default :
throw new IllegalStateException(
"Unknown enum constant '" + close + "'."
);
}
// If the first non-whitespace character was not neither
// a '}' nor a '"':
default :
throw unexpectedCharacterException();
}
// Parse each remaining entry
while ((nextNonBlankspace()) == 0x22) { // "
final CloseMethod close = parseEntryInto(object);
switch (close) {
case EXIT_FROM_PARENT:
if (character == 0x7D) { // }
return object;
} else {
throw unexpectedCharacterException();
}
case CONTINUE_IN_PARENT:
continue;
case NOT_DECIDED:
switch (nextNonBlankspace()) {
case 0x2C: // , (continue with next entry)
continue;
case 0x7D: // } (close the map)
return object;
default:
throw unexpectedCharacterException();
}
default:
throw new IllegalStateException(
"Unknown enum constant '" + close + "'."
);
}
}
throw unexpectedCharacterException();
}
private CloseMethod parseEntryInto(Map object) throws IOException {
final StringBuilder keyBuilder = new StringBuilder();
parseKey: while (true) {
switch (next()) {
// If this is an escape character, add the following character
// without parsing it.
case 0x5C : // backslash
keyBuilder.append(Character.toChars(next()));
continue;
// If this terminates the key, break the loop.
case 0x22 : // " (end key)
break parseKey;
// Every other character should be added to the key.
default :
keyBuilder.append(Character.toChars(character));
}
}
final String key = keyBuilder.toString();
// Read the assignment operator
if (nextNonBlankspace() != 0x3A) { // :
throw unexpectedCharacterException();
}
// Read the value
switch (nextNonBlankspace()) {
case 0x7B : // { (begin parsing object)
if (object.put(key, parseObject()) != null) {
throw duplicateKeyException(key);
}
return CloseMethod.NOT_DECIDED;
case 0x5B : // [ (begin parsing array)
if (object.put(key, parseArray()) != null) {
throw duplicateKeyException(key);
}
return CloseMethod.NOT_DECIDED;
case 0x22 : // " (begin parsing string)
if (object.put(key, parseString()) != null) {
throw duplicateKeyException(key);
}
return CloseMethod.NOT_DECIDED;
case 0x66 : // f (begin parsing false)
if (object.put(key, parseFalse()) != null) {
throw duplicateKeyException(key);
}
return CloseMethod.NOT_DECIDED;
case 0x74 : // t (begin parsing true)
if (object.put(key, parseTrue()) != null) {
throw duplicateKeyException(key);
}
return CloseMethod.NOT_DECIDED;
case 0x6E : // n (begin parsing null)
if (object.put(key, parseNull()) != null) {
throw duplicateKeyException(key);
}
return CloseMethod.NOT_DECIDED;
// Digit '0 - 9'
case 0x30 : case 0x31 : case 0x32 : case 0x33 : case 0x34 :
case 0x35 : case 0x36 : case 0x37 : case 0x38 : case 0x39 :
case 0x2E : // . (decimal sign)
case 0x2D : // - (minus sign)
return parseNumber(num -> {
if (object.put(key, num) != null) {
throw duplicateKeyException(key);
}
});
default :
throw unexpectedCharacterException();
}
}
private List