/* * Copyright 2009 Google Inc. * * 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 com.google.gwt.dev.json; import java.io.IOException; import java.io.Reader; /** * Implementation of parsing a JSON string into instances of {@link JsonValue}. */ class Tokenizer { private static final int INVALID_CHAR = -1; private static final String STOPCHARS = ",:]}/\\\"[{;=#"; private static JsonNumber getNumberForLiteral(String literal) throws JsonException { try { // The .2 is not a good value, we would need 0.2 if (literal.indexOf('.') > 0 || literal.indexOf('e') > 0 || literal.indexOf('E') > 0) { return JsonNumber.create(Double.parseDouble(literal)); } return JsonNumber.create(Long.parseLong(literal)); } catch (NumberFormatException e) { throw new JsonException("Invalid number literal: " + literal); } } private static JsonValue getValueForLiteral(String literal) throws JsonException { if ("".equals(literal)) { throw new JsonException("Missing value"); } if ("null".equals(literal)) { return JsonValue.NULL; } if ("true".equals(literal)) { return JsonBoolean.create(true); } if ("false".equals(literal)) { return JsonBoolean.create(false); } final char c = literal.charAt(0); if (c == '-' || Character.isDigit(c)) { return getNumberForLiteral(literal); } throw new JsonException("Invalid literal: \"" + literal + "\""); } private int pushBackBuffer = INVALID_CHAR; private final Reader reader; Tokenizer(Reader reader) { this.reader = reader; } void back(char c) { assert pushBackBuffer == INVALID_CHAR; pushBackBuffer = c; } void back(int c) { back((char) c); } int next() throws IOException { if (pushBackBuffer != INVALID_CHAR) { final int c = pushBackBuffer; pushBackBuffer = INVALID_CHAR; return c; } return reader.read(); } String next(int n) throws IOException, 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 = reader.read(buffer, pos, n - pos)) != -1)) { pos += len; } if (pos < n) { throw new JsonException(/* TODO(knorton): Add message. */); } return String.valueOf(buffer); } int nextNonWhitespace() throws IOException { while (true) { final int c = next(); if (!Character.isWhitespace(c)) { return c; } } } String nextString() throws IOException, JsonException { final StringBuffer buffer = new StringBuffer(); int c = next(); assert 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(knorton): I'm not sure I 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 == '"') { return buffer.toString(); } buffer.append((char) c); } } } String nextUntilOneOf(String chars) throws IOException { final StringBuffer buffer = new StringBuffer(); int c = next(); while (c != INVALID_CHAR) { if (Character.isWhitespace(c) || chars.indexOf((char) c) >= 0) { back(c); break; } buffer.append((char) c); c = next(); } return buffer.toString(); } JsonValue nextValue() throws IOException, JsonException { final int c = nextNonWhitespace(); back(c); switch (c) { case '"': return JsonString.create(nextString()); case '{': return JsonObject.parse(this); case '[': return JsonArray.parse(this); default: return getValueForLiteral(nextUntilOneOf(STOPCHARS)); } } }