All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.logging.log4j.layout.template.json.util.JsonReader Maven / Gradle / Ivy

There is a newer version: 3.0.0-beta2
Show newest version
/*
 * 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.layout.template.json.util;

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 (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 { String message = String.format( "invalid character at index %d: %c", readCharIndex, readChar); throw new IllegalArgumentException(message); } } return readToken; } private void skipWhiteSpace() { do { if (!Character.isWhitespace(readChar)) { break; } } 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) { Object value = readToken(); object.put(key, value); if (readToken() == Delimiter.COMMA) { key = readObjectKey(); if (key == null || Delimiter.exists(key)) { String message = String.format( "was expecting an object key at index %d: %s", readTokenStartIndex, readToken); throw new IllegalArgumentException(message); } } else { expectDelimiter(Delimiter.OBJECT_END, readToken); } } } return object; } private List readArray() { @SuppressWarnings("JdkObsolete") final List array = new LinkedList<>(); readToken(); while (readToken != Delimiter.ARRAY_END) { if (readToken instanceof Delimiter) { final String message = String.format( "was expecting an array element at index %d: %s", readTokenStartIndex, readToken); throw new IllegalArgumentException(message); } array.add(readToken); if (readToken() == Delimiter.COMMA) { if (readToken() == Delimiter.ARRAY_END) { final String message = String.format( "was expecting an array element at index %d: %s", readTokenStartIndex, readToken); throw new IllegalArgumentException(message); } } else { expectDelimiter(Delimiter.ARRAY_END, readToken); } } return array; } private String readObjectKey() { readToken(); if (readToken == Delimiter.OBJECT_END) { return null; } else if (readToken instanceof String) { return (String) readToken; } else { final String message = String.format( "was expecting an object key at index %d: %s", readTokenStartIndex, readToken); throw new IllegalArgumentException(message); } } private void expectDelimiter( final Delimiter expectedDelimiter, final Object actualToken) { if (!expectedDelimiter.equals(actualToken)) { String message = String.format( "was expecting %s at index %d: %s", expectedDelimiter, readTokenStartIndex, actualToken); throw new IllegalArgumentException(message); } } private boolean readTrue() { if (readChar != 'r' || readChar() != 'u' || readChar() != 'e') { String message = String.format( "was expecting keyword 'true' at index %d: %s", readCharIndex, readChar); throw new IllegalArgumentException(message); } readChar(); return true; } private boolean readFalse() { if (readChar != 'a' || readChar() != 'l' || readChar() != 's' || readChar() != 'e') { String message = String.format( "was expecting keyword 'false' at index %d: %s", readCharIndex, readChar); throw new IllegalArgumentException(message); } readChar(); return false; } private Object readNull() { if (readChar != 'u' || readChar() != 'l' || readChar() != 'l') { String message = String.format( "was expecting keyword 'null' at index %d: %s", readCharIndex, readChar); throw new IllegalArgumentException(message); } readChar(); return null; } private Number readNumber() { // Read sign. buffer.setLength(0); if (readChar == '-') { bufferReadChar(); } // Read fraction. boolean floatingPoint = false; bufferDigits(); if (readChar == '.') { bufferReadChar(); bufferDigits(); floatingPoint = true; } // Read exponent. if (readChar == 'e' || readChar == 'E') { floatingPoint = true; bufferReadChar(); if (readChar == '+' || readChar == '-') { bufferReadChar(); } bufferDigits(); } // Convert the read number. final String string = buffer.toString(); if (floatingPoint) { return new BigDecimal(string); } else { final BigInteger bigInteger = new BigInteger(string); try { return bigInteger.intValueExact(); } catch (ArithmeticException ignoredIntOverflow) { try { return bigInteger.longValueExact(); } catch (ArithmeticException ignoredLongOverflow) { return bigInteger; } } } } private void bufferDigits() { boolean found = false; while (Character.isDigit(readChar)) { found = true; bufferReadChar(); } if (!found) { final String message = String.format( "was expecting a digit at index %d: %c", readCharIndex, readChar); throw new IllegalArgumentException(message); } } }