blob: 928790d1c3a1014417a13e9fc108c1b8e2278c65 [file] [log] [blame]
/*
* 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));
}
}
}