org.apache.logging.log4j.kit.json.JsonReader Maven / Gradle / Ivy
/*
* 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.logging.log4j.kit.json;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.text.CharacterIterator;
import java.text.StringCharacterIterator;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
/**
* A simple JSON parser mapping tokens to basic Java types.
*
* The type mapping is as follows:
*
*
* - objects are mapped to {@link LinkedHashMap LinkedHashMap<String,Object>}
*
- arrays are mapped to {@link LinkedList}
*
- strings are mapped to {@link String} with proper Unicode and
* escape character conversion
*
- true, false, and null are mapped to their Java
* counterparts
*
- floating point numbers are mapped to {@link BigDecimal}
*
- integral numbers are mapped to either primitive types
* (int, long) or {@link BigInteger}
*
*
* This code is heavily influenced by the reader of
* mjson.
*/
public final class JsonReader {
private enum Delimiter {
OBJECT_START("{"),
OBJECT_END("}"),
ARRAY_START("["),
ARRAY_END("]"),
COLON(":"),
COMMA(",");
private final String string;
Delimiter(final String string) {
this.string = string;
}
private static boolean exists(final Object token) {
for (final Delimiter delimiter : values()) {
if (delimiter.string.equals(token)) {
return true;
}
}
return false;
}
}
private CharacterIterator it;
private int readCharIndex = -1;
private char readChar;
private int readTokenStartIndex = -1;
private Object readToken;
private final StringBuilder buffer;
private JsonReader() {
this.buffer = new StringBuilder();
}
public static Object read(final String json) {
Objects.requireNonNull(json, "json");
final JsonReader reader = new JsonReader();
return reader.read(new StringCharacterIterator(json));
}
private Object read(final CharacterIterator ci) {
it = ci;
readCharIndex = 0;
readChar = it.first();
final Object token = readToken();
if (token instanceof Delimiter) {
final String message = String.format("was not expecting %s at index %d", readToken, readTokenStartIndex);
throw new IllegalArgumentException(message);
}
skipWhiteSpace();
if (it.getIndex() != it.getEndIndex()) {
final String message = String.format("was not expecting input at index %d: %c", readCharIndex, readChar);
throw new IllegalArgumentException(message);
}
return token;
}
private Object readToken() {
skipWhiteSpace();
readTokenStartIndex = readCharIndex;
final char prevChar = readChar;
readChar();
switch (prevChar) {
case '"':
readToken = readString();
break;
case '[':
readToken = readArray();
break;
case ']':
readToken = Delimiter.ARRAY_END;
break;
case ',':
readToken = Delimiter.COMMA;
break;
case '{':
readToken = readObject();
break;
case '}':
readToken = Delimiter.OBJECT_END;
break;
case ':':
readToken = Delimiter.COLON;
break;
case 't':
readToken = readTrue();
break;
case 'f':
readToken = readFalse();
break;
case 'n':
readToken = readNull();
break;
default:
unreadChar();
if (Character.isDigit(readChar) || readChar == '-') {
readToken = readNumber();
} else {
final String message = String.format("invalid character at index %d: %c", readCharIndex, readChar);
throw new IllegalArgumentException(message);
}
}
return readToken;
}
private void skipWhiteSpace() {
boolean inComment = false;
do {
if (inComment) {
if (readChar == '*') {
if (readChar() == '/') {
inComment = false;
} else {
unreadChar();
}
} // else continue
} else {
if (readChar == '/') {
if (readChar() == '*') {
inComment = true;
} else {
unreadChar();
}
} else if (!Character.isWhitespace(readChar)) {
break;
} // else continue
}
} while (readChar() != CharacterIterator.DONE);
}
private char readChar() {
if (it.getIndex() == it.getEndIndex()) {
throw new IllegalArgumentException("premature end of input");
}
readChar = it.next();
readCharIndex = it.getIndex();
return readChar;
}
private void unreadChar() {
readChar = it.previous();
readCharIndex = it.getIndex();
}
private String readString() {
buffer.setLength(0);
while (readChar != '"') {
if (readChar == '\\') {
readChar();
if (readChar == 'u') {
final char unicodeChar = readUnicodeChar();
bufferChar(unicodeChar);
} else {
switch (readChar) {
case '"':
case '\\':
bufferReadChar();
break;
case 'b':
bufferChar('\b');
break;
case 'f':
bufferChar('\f');
break;
case 'n':
bufferChar('\n');
break;
case 'r':
bufferChar('\r');
break;
case 't':
bufferChar('\t');
break;
default: {
final String message = String.format(
"was expecting an escape character at index %d: %c", readCharIndex, readChar);
throw new IllegalArgumentException(message);
}
}
}
} else {
bufferReadChar();
}
}
readChar();
return buffer.toString();
}
private void bufferReadChar() {
bufferChar(readChar);
}
private void bufferChar(final char c) {
buffer.append(c);
readChar();
}
private char readUnicodeChar() {
int value = 0;
for (int i = 0; i < 4; i++) {
readChar();
if (readChar >= '0' && readChar <= '9') {
value = (value << 4) + readChar - '0';
} else if (readChar >= 'a' && readChar <= 'f') {
value = (value << 4) + (readChar - 'a') + 10;
} else if (readChar >= 'A' && readChar <= 'F') {
value = (value << 4) + (readChar - 'A') + 10;
} else {
final String message =
String.format("was expecting a unicode character at index %d: %c", readCharIndex, readChar);
throw new IllegalArgumentException(message);
}
}
return (char) value;
}
private Map readObject() {
final Map object = new LinkedHashMap<>();
String key = readObjectKey();
while (readToken != Delimiter.OBJECT_END) {
expectDelimiter(Delimiter.COLON, readToken());
if (readToken != Delimiter.OBJECT_END) {
final Object value = readToken();
object.put(key, value);
if (readToken() == Delimiter.COMMA) {
key = readObjectKey();
if (key == null || Delimiter.exists(key)) {
final String message = String.format(
"was expecting an object key at index %d: %s", readTokenStartIndex, readToken);
throw new IllegalArgumentException(message);
} else if (object.containsKey(key)) {
final String message =
String.format("found duplicate object key at index %d: %s", readTokenStartIndex, key);
throw new IllegalArgumentException(message);
}
} else {
expectDelimiter(Delimiter.OBJECT_END, readToken);
}
}
}
return object;
}
private List