/* * 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'; } }