/* * Copyright 2015 Red Hat, Inc. and/or its affiliates. * * 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 org.dashbuilder.json; /** * Implementation of parsing a JSON string into instances of {@link * org.dashbuilder.json.JsonValue}. */ class JsonTokenizer { private static final int INVALID_CHAR = -1; private static final String STOPCHARS = ",:]}/\\\"[{;=#"; private JsonFactory jsonFactory; private boolean lenient = true; private int pushBackBuffer = INVALID_CHAR; private final String json; private int position = 0; JsonTokenizer(JsonFactory serverJsonFactory, String json) { this.jsonFactory = serverJsonFactory; this.json = json; } void back(char c) { assert pushBackBuffer == INVALID_CHAR; pushBackBuffer = c; } void back(int c) { back((char) c); } int next() { if (pushBackBuffer != INVALID_CHAR) { final int c = pushBackBuffer; pushBackBuffer = INVALID_CHAR; return c; } return position < json.length() ? json.charAt(position++) : INVALID_CHAR; } String next(int n) throws JsonException { if (n == 0) { return ""; } char[] buffer = new char[n]; int pos = 0; if (pushBackBuffer != INVALID_CHAR) { buffer[0] = (char) pushBackBuffer; pos = 1; pushBackBuffer = INVALID_CHAR; } int len; while ((pos < n) && ((len = read(buffer, pos, n - pos)) != -1)) { pos += len; } if (pos < n) { throw new JsonException("Position " + pos + " less than " + n); } return String.valueOf(buffer); } int nextNonWhitespace() { while (true) { final int c = next(); if (!Character.isSpace((char) c)) { return c; } } } String nextString(int startChar) throws JsonException { final StringBuilder buffer = new StringBuilder(); int c = next(); assert c == '"' || (lenient && c == '\''); while (true) { c = next(); switch (c) { case '\r': case '\n': throw new JsonException(""); case '\\': c = next(); switch (c) { case 'b': buffer.append('\b'); break; case 't': buffer.append('\t'); break; case 'n': buffer.append('\n'); break; case 'f': buffer.append('\f'); break; case 'r': buffer.append('\r'); break; // TODO: Not sure should even support this escaping // mode since JSON is always UTF-8. case 'u': buffer.append((char) Integer.parseInt(next(4), 16)); break; default: buffer.append((char) c); } break; default: if (c == startChar) { return buffer.toString(); } buffer.append((char) c); } } } String nextUntilOneOf(String chars) { final StringBuilder buffer = new StringBuilder(); int c = next(); while (c != INVALID_CHAR) { if (Character.isSpace((char) c) || chars.indexOf((char) c) >= 0) { back(c); break; } buffer.append((char) c); c = next(); } return buffer.toString(); } <T extends JsonValue> T nextValue() throws JsonException { final int c = nextNonWhitespace(); back(c); switch (c) { case '"': case '\'': return (T) jsonFactory.create(nextString(c)); case '{': return (T) parseObject(); case '[': return (T) parseArray(); default: return (T) getValueForLiteral(nextUntilOneOf(STOPCHARS)); } } JsonArray parseArray() throws JsonException { final JsonArray array = jsonFactory.createArray(); int c = nextNonWhitespace(); assert c == '['; while (true) { c = nextNonWhitespace(); switch (c) { case ']': return array; default: back(c); array.set(array.length(), (JsonValue) nextValue()); final int d = nextNonWhitespace(); switch (d) { case ']': return array; case ',': break; default: throw new JsonException("Invalid array: expected , or ]"); } } } } JsonObject parseObject() throws JsonException { final JsonObject object = jsonFactory.createObject(); int c = nextNonWhitespace(); if (c != '{') { throw new JsonException( "Payload does not begin with '{'. Got " + c + "(" + Character.valueOf((char) c) + ")"); } while (true) { c = nextNonWhitespace(); switch (c) { case '}': // We're done. return object; case '"': case '\'': back(c); // Ready to start a key. final String key = nextString(c); if (nextNonWhitespace() != ':') { throw new JsonException( "Invalid object: expecting \":\""); } // TODO: Make sure this key is not already set. object.put(key, (JsonValue) nextValue()); switch (nextNonWhitespace()) { case ',': break; case '}': return object; default: throw new JsonException( "Invalid object: expecting } or ,"); } break; case ',': break; default: if (lenient && (Character.isDigit((char) c) || Character.isLetterOrDigit((char) c))) { StringBuilder keyBuffer = new StringBuilder(); keyBuffer.append(c); while (true) { c = next(); if (Character.isDigit((char) c) || Character.isLetterOrDigit((char) c)) { keyBuffer.append(c); } else { back(c); break; } } if (nextNonWhitespace() != ':') { throw new JsonException( "Invalid object: expecting \":\""); } // TODO: Make sure this key is not already set. object.put(keyBuffer.toString(), (JsonValue) nextValue()); switch (nextNonWhitespace()) { case ',': break; case '}': return object; default: throw new JsonException( "Invalid object: expecting } or ,"); } } else { throw new JsonException("Invalid object: "); } } } } private JsonNumber getNumberForLiteral(String literal) throws JsonException { try { return jsonFactory.create(Double.parseDouble(literal)); } catch (NumberFormatException e) { throw new JsonException("Invalid number literal: " + literal); } } private JsonValue getValueForLiteral(String literal) throws JsonException { if ("".equals(literal)) { throw new JsonException("Missing value"); } if ("null".equals(literal) || "undefined".equals(literal)) { return jsonFactory.createNull(); } if ("true".equals(literal)) { return jsonFactory.create(true); } if ("false".equals(literal)) { return jsonFactory.create(false); } final char c = literal.charAt(0); if (c == '-' || Character.isDigit(c)) { return getNumberForLiteral(literal); } throw new JsonException("Invalid literal: \"" + literal + "\""); } private int read(char[] buffer, int pos, int len) { int maxLen = Math.min(json.length() - position, len); String src = json.substring(position, position + maxLen); char result[] = src.toCharArray(); System.arraycopy(result, 0, buffer, pos, maxLen); position += maxLen; return maxLen; } }