blob: 3eb707557e955aafe46b6995a602fb199deb636b [file] [log] [blame]
/*
* 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.felix.resolver.test.util;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.Map;
public class JsonReader {
public static Object read(Reader reader) throws IOException {
return new JsonReader(reader).parse();
}
public static Object read(InputStream is) throws IOException {
return new JsonReader(new InputStreamReader(is)).parse();
}
//
// Implementation
//
private final Reader reader;
private final StringBuilder recorder;
private int current;
private int line = 1;
private int column = 0;
JsonReader(Reader reader) {
this.reader = reader;
recorder = new StringBuilder();
}
public Object parse() throws IOException {
read();
skipWhiteSpace();
Object result = readValue();
skipWhiteSpace();
if (!endOfText()) {
throw error("Unexpected character");
}
return result;
}
private Object readValue() throws IOException {
switch (current) {
case 'n':
return readNull();
case 't':
return readTrue();
case 'f':
return readFalse();
case '"':
return readString();
case '[':
return readArray();
case '{':
return readObject();
case '-':
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
return readNumber();
default:
throw expected("value");
}
}
private Collection<?> readArray() throws IOException {
read();
Collection<Object> array = new ArrayList<Object>();
skipWhiteSpace();
if (readChar(']')) {
return array;
}
do {
skipWhiteSpace();
array.add(readValue());
skipWhiteSpace();
} while (readChar(','));
if (!readChar(']')) {
throw expected("',' or ']'");
}
return array;
}
private Map<String, Object> readObject() throws IOException {
read();
Map<String, Object> object = new LinkedHashMap<String, Object>();
skipWhiteSpace();
if (readChar('}')) {
return object;
}
do {
skipWhiteSpace();
String name = readName();
skipWhiteSpace();
if (!readChar(':')) {
throw expected("':'");
}
skipWhiteSpace();
object.put(name, readValue());
skipWhiteSpace();
} while (readChar(','));
if (!readChar('}')) {
throw expected("',' or '}'");
}
return object;
}
private Object readNull() throws IOException {
read();
readRequiredChar('u');
readRequiredChar('l');
readRequiredChar('l');
return null;
}
private Boolean readTrue() throws IOException {
read();
readRequiredChar('r');
readRequiredChar('u');
readRequiredChar('e');
return Boolean.TRUE;
}
private Boolean readFalse() throws IOException {
read();
readRequiredChar('a');
readRequiredChar('l');
readRequiredChar('s');
readRequiredChar('e');
return Boolean.FALSE;
}
private void readRequiredChar(char ch) throws IOException {
if (!readChar(ch)) {
throw expected("'" + ch + "'");
}
}
private String readString() throws IOException {
read();
recorder.setLength(0);
while (current != '"') {
if (current == '\\') {
readEscape();
} else if (current < 0x20) {
throw expected("valid string character");
} else {
recorder.append((char) current);
read();
}
}
read();
return recorder.toString();
}
private void readEscape() throws IOException {
read();
switch (current) {
case '"':
case '/':
case '\\':
recorder.append((char) current);
break;
case 'b':
recorder.append('\b');
break;
case 'f':
recorder.append('\f');
break;
case 'n':
recorder.append('\n');
break;
case 'r':
recorder.append('\r');
break;
case 't':
recorder.append('\t');
break;
case 'u':
char[] hexChars = new char[4];
for (int i = 0; i < 4; i++) {
read();
if (!isHexDigit(current)) {
throw expected("hexadecimal digit");
}
hexChars[i] = (char) current;
}
recorder.append((char) Integer.parseInt(String.valueOf(hexChars), 16));
break;
default:
throw expected("valid escape sequence");
}
read();
}
private Number readNumber() throws IOException {
recorder.setLength(0);
readAndAppendChar('-');
int firstDigit = current;
if (!readAndAppendDigit()) {
throw expected("digit");
}
if (firstDigit != '0') {
while (readAndAppendDigit()) {
}
}
readFraction();
readExponent();
return Double.parseDouble(recorder.toString());
}
private boolean readFraction() throws IOException {
if (!readAndAppendChar('.')) {
return false;
}
if (!readAndAppendDigit()) {
throw expected("digit");
}
while (readAndAppendDigit()) {
}
return true;
}
private boolean readExponent() throws IOException {
if (!readAndAppendChar('e') && !readAndAppendChar('E')) {
return false;
}
if (!readAndAppendChar('+')) {
readAndAppendChar('-');
}
if (!readAndAppendDigit()) {
throw expected("digit");
}
while (readAndAppendDigit()) {
}
return true;
}
private String readName() throws IOException {
if (current != '"') {
throw expected("name");
}
readString();
return recorder.toString();
}
private boolean readAndAppendChar(char ch) throws IOException {
if (current != ch) {
return false;
}
recorder.append(ch);
read();
return true;
}
private boolean readChar(char ch) throws IOException {
if (current != ch) {
return false;
}
read();
return true;
}
private boolean readAndAppendDigit() throws IOException {
if (!isDigit(current)) {
return false;
}
recorder.append((char) current);
read();
return true;
}
private void skipWhiteSpace() throws IOException {
while (isWhiteSpace(current) && !endOfText()) {
read();
}
}
private void read() throws IOException {
if (endOfText()) {
throw error("Unexpected end of input");
}
column++;
if (current == '\n') {
line++;
column = 0;
}
current = reader.read();
}
private boolean endOfText() {
return current == -1;
}
private IOException expected(String expected) {
if (endOfText()) {
return error("Unexpected end of input");
}
return error("Expected " + expected);
}
private IOException error(String message) {
return new IOException(message + " at " + line + ":" + column);
}
private static boolean isWhiteSpace(int ch) {
return ch == ' ' || ch == '\t' || ch == '\n' || ch == '\r';
}
private static boolean isDigit(int ch) {
return ch >= '0' && ch <= '9';
}
private static boolean isHexDigit(int ch) {
return ch >= '0' && ch <= '9' || ch >= 'a' && ch <= 'f' || ch >= 'A' && ch <= 'F';
}
}