Refactor Java JSON parsing into JSONTokeniser

This commit is contained in:
Andrew Bettison 2014-06-17 13:26:03 +09:30
parent 9cbd7c365c
commit 0a54414744
3 changed files with 529 additions and 370 deletions

View File

@ -0,0 +1,43 @@
/**
* Copyright (C) 2014 Serval Project Inc.
*
* This file is part of Serval Software (http://www.servalproject.org)
*
* Serval Software is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This source code is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this source code; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
package org.servalproject.json;
/**
* Thrown when there is any problem with JSON input. This exception class is subclassed to
* specialise it to specific causes, such as JSON syntax error, unexpected JSON token, etc.
*
* @author Andrew Bettison <andrew@servalproject.com>
*/
public class JSONInputException extends Exception
{
public JSONInputException(String message) {
super(message);
}
public JSONInputException(Throwable cause) {
super(cause);
}
public JSONInputException(String message, Throwable cause) {
super(message, cause);
}
}

View File

@ -0,0 +1,377 @@
/**
* Copyright (C) 2014 Serval Project Inc.
*
* This file is part of Serval Software (http://www.servalproject.org)
*
* Serval Software is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This source code is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this source code; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
package org.servalproject.json;
import java.lang.StringBuilder;
import java.lang.NumberFormatException;
import java.io.IOException;
import java.io.Reader;
import java.io.PushbackReader;
import java.util.Collection;
public class JSONTokeniser {
PushbackReader reader;
public enum Token {
START_OBJECT,
END_OBJECT,
START_ARRAY,
END_ARRAY,
COMMA,
COLON,
NULL,
EOF
};
public static class SyntaxException extends JSONInputException
{
public SyntaxException(String message) {
super(message);
}
}
public static class UnexpectedException extends JSONInputException
{
public UnexpectedException(String got, Class expecting) {
super("unexpected " + got + ", expecting " + expecting.getName());
}
public UnexpectedException(String got, Object expecting) {
super("unexpected " + got + ", expecting " + jsonTokenDescription(expecting));
}
}
public static class UnexpectedEOFException extends UnexpectedException
{
public UnexpectedEOFException(Class expecting) {
super("EOF", expecting);
}
public UnexpectedEOFException(Object expecting) {
super("EOF", expecting);
}
}
public static class UnexpectedTokenException extends UnexpectedException
{
public UnexpectedTokenException(Object got, Class expecting) {
super(jsonTokenDescription(got), expecting);
}
public UnexpectedTokenException(Object got, Object expecting) {
super(jsonTokenDescription(got), expecting);
}
}
// Can accept any PushbackReader, because we only need one character of unread().
public JSONTokeniser(PushbackReader pbrd)
{
reader = pbrd;
}
public JSONTokeniser(Reader rd)
{
reader = new PushbackReader(rd);
}
public static void match(Object tok, Token exactly) throws SyntaxException
{
if (tok != exactly)
throw new SyntaxException("JSON syntax error: expecting " + exactly + ", got " + jsonTokenDescription(tok));
}
public void consume(Token exactly) throws SyntaxException, UnexpectedException, IOException
{
match(nextToken(), exactly);
}
@SuppressWarnings("unchecked")
public static <T> T narrow(Object tok, Class<T> cls) throws UnexpectedException
{
assert !cls.isAssignableFrom(Token.class); // can only narrow to values
if (tok == Token.EOF)
throw new UnexpectedEOFException(cls);
if (tok instanceof Token)
throw new UnexpectedTokenException(tok, cls);
// Convert:
// Integer --> Float or Double
// Float --> Double
// Double --> Float
if (cls == Double.class && (tok instanceof Float || tok instanceof Integer))
tok = new Double(((Number)tok).doubleValue());
else if (cls == Float.class && (tok instanceof Double || tok instanceof Integer))
tok = new Float(((Number)tok).floatValue());
if (cls.isInstance(tok))
return (T)tok;
throw new UnexpectedTokenException(tok, cls);
}
public <T> T consume(Class<T> cls) throws SyntaxException, UnexpectedException, IOException
{
return narrow(nextToken(), cls);
}
public Object consume() throws SyntaxException, UnexpectedException, IOException
{
return consume(Object.class);
}
public String consume(String exactly) throws SyntaxException, UnexpectedException, IOException
{
String tok = consume(String.class);
if (tok.equals(exactly))
return tok;
throw new UnexpectedTokenException(tok, exactly);
}
public <T> int consumeArray(Collection<T> collection, Class<T> cls) throws SyntaxException, UnexpectedException, IOException
{
int added = 0;
consume(Token.START_ARRAY);
Object tok = nextToken();
if (tok != Token.END_ARRAY) {
while (true) {
collection.add(narrow(tok, cls));
++added;
tok = nextToken();
if (tok == Token.END_ARRAY)
break;
match(tok, Token.COMMA);
tok = nextToken();
}
}
return added;
}
public static boolean jsonIsToken(Object tok)
{
return tok instanceof Token || tok instanceof String || tok instanceof Double || tok instanceof Integer || tok instanceof Boolean;
}
public static String jsonTokenDescription(Object tok)
{
if (tok instanceof String)
return "\"" + tok + "\"";
if (tok instanceof Number)
return "" + tok;
if (tok instanceof Boolean)
return "" + tok;
assert tok instanceof Token;
return tok.toString();
}
private void readWord(String word) throws SyntaxException, IOException
{
int len = 0;
while (len < word.length()) {
char[] buf = new char[word.length() - len];
int n = this.reader.read(buf, 0, buf.length);
if (n == -1)
throw new SyntaxException("EOF in middle of \"" + word + "\"");
for (int i = 0; i < n; ++i)
if (buf[i] != word.charAt(len++))
throw new SyntaxException("expecting \"" + word + "\"");
}
}
private int readHex(int digits) throws SyntaxException, IOException
{
char[] buf = new char[digits];
int len = 0;
while (len < buf.length) {
int n = this.reader.read(buf, len, buf.length - len);
if (n == -1)
throw new SyntaxException("EOF in middle of " + digits + " hex digits");
len += n;
}
String hex = new String(buf);
try {
return Integer.valueOf(hex, 16);
}
catch (NumberFormatException e) {
throw new SyntaxException("expecting " + digits + " hex digits, got \"" + hex + "\"");
}
}
public Object nextToken() throws SyntaxException, IOException
{
while (true) {
int c = this.reader.read();
switch (c) {
case -1:
return Token.EOF;
case '\t':
case '\r':
case '\n':
case ' ':
break;
case '{':
return Token.START_OBJECT;
case '}':
return Token.END_OBJECT;
case '[':
return Token.START_ARRAY;
case ']':
return Token.END_ARRAY;
case ',':
return Token.COMMA;
case ':':
return Token.COLON;
case 't':
this.reader.unread(c);
readWord("true");
return Boolean.TRUE;
case 'f':
this.reader.unread(c);
readWord("false");
return Boolean.FALSE;
case 'n':
this.reader.unread(c);
readWord("null");
return Token.NULL;
case '"': {
StringBuilder sb = new StringBuilder();
boolean slosh = false;
while (true) {
c = this.reader.read();
if (c == -1)
throw new SyntaxException("unexpected EOF in JSON string");
if (slosh) {
switch (c) {
case '"': case '/': case '\\': sb.append('"'); break;
case 'b': sb.append('\b'); break;
case 'f': sb.append('\f'); break;
case 'n': sb.append('\n'); break;
case 'r': sb.append('\r'); break;
case 't': sb.append('\t'); break;
case 'u':
int code = readHex(4);
sb.append((char)code);
// fall through
default:
throw new SyntaxException("malformed JSON string");
}
}
else {
switch (c) {
case '"':
return sb.toString();
case '\\':
slosh = true;
break;
default:
sb.append((char)c);
break;
}
}
}
}
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
case '-': {
StringBuilder sb = new StringBuilder();
if (c == '-') {
sb.append((char)c);
c = this.reader.read();
}
if (c == '0') {
sb.append((char)c);
c = this.reader.read();
}
else if (Character.isDigit(c)) {
do {
sb.append((char)c);
c = this.reader.read();
}
while (Character.isDigit(c));
}
else
throw new SyntaxException("malformed JSON number");
boolean isfloat = false;
if (c == '.') {
isfloat = true;
sb.append((char)c);
c = this.reader.read();
if (c == -1)
throw new SyntaxException("unexpected EOF in JSON number");
if (!Character.isDigit(c))
throw new SyntaxException("malformed JSON number");
do {
sb.append((char)c);
c = this.reader.read();
}
while (Character.isDigit(c));
}
if (c == 'e' || c == 'E') {
isfloat = true;
sb.append((char)c);
c = this.reader.read();
if (c == '+' || c == '-') {
sb.append((char)c);
c = this.reader.read();
}
if (c == -1)
throw new SyntaxException("unexpected EOF in JSON number");
if (!Character.isDigit(c))
throw new SyntaxException("malformed JSON number");
do {
sb.append((char)c);
c = this.reader.read();
}
while (Character.isDigit(c));
}
this.reader.unread(c);
String number = sb.toString();
try {
if (isfloat)
return Double.parseDouble(number);
else
return Integer.parseInt(number);
}
catch (NumberFormatException e) {
throw new SyntaxException("malformed JSON number: " + number);
}
}
default:
throw new SyntaxException("malformed JSON: '" + (char)c + "'");
}
}
}
public void close() throws IOException
{
this.reader.close();
}
}

View File

@ -21,24 +21,21 @@
package org.servalproject.servaldna.meshms;
import java.io.IOException;
import java.lang.StringBuilder;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.io.PushbackReader;
import java.util.Collection;
import java.util.Vector;
import java.net.HttpURLConnection;
import org.servalproject.servaldna.ServalDHttpConnectionFactory;
import org.servalproject.servaldna.ServalDInterfaceException;
import org.servalproject.servaldna.SubscriberId;
import org.servalproject.json.JSONTokeniser;
import org.servalproject.json.JSONInputException;
public class MeshMSConversationList {
private ServalDHttpConnectionFactory httpConnector;
private SubscriberId sid;
private HttpURLConnection httpConnection;
private PushbackReader reader;
private JSONTokeniser json;
private Vector<String> headers;
int columnIndex__id;
int columnIndex_my_sid;
@ -54,385 +51,127 @@ public class MeshMSConversationList {
this.sid = sid;
}
public boolean isConnected()
{
return this.json != null;
}
public void connect() throws ServalDInterfaceException, IOException
{
columnIndex__id = -1;
columnIndex_my_sid = -1;
columnIndex_their_sid = -1;
columnIndex_read = -1;
columnIndex_last_message = -1;
columnIndex_read_offset = -1;
rowCount = 0;
httpConnection = httpConnector.newServalDHttpConnection("/restful/meshms/" + sid.toHex() + "/conversationlist.json");
httpConnection.connect();
reader = new PushbackReader(new InputStreamReader(httpConnection.getInputStream(), "US-ASCII"));
consume(reader, JsonToken.START_OBJECT);
consume(reader, "header");
consume(reader, JsonToken.COLON);
headers = new Vector<String>();
consumeArray(reader, headers, String.class);
if (headers.size() < 1)
throw new ServalDInterfaceException("empty JSON headers array");
for (int i = 0; i < headers.size(); ++i) {
String header = headers.get(i);
if (header.equals("_id"))
columnIndex__id = i;
else if (header.equals("my_sid"))
columnIndex_my_sid = i;
else if (header.equals("their_sid"))
columnIndex_their_sid = i;
else if (header.equals("read"))
columnIndex_read = i;
else if (header.equals("last_message"))
columnIndex_last_message = i;
else if (header.equals("read_offset"))
columnIndex_read_offset = i;
try {
columnIndex__id = -1;
columnIndex_my_sid = -1;
columnIndex_their_sid = -1;
columnIndex_read = -1;
columnIndex_last_message = -1;
columnIndex_read_offset = -1;
rowCount = 0;
httpConnection = httpConnector.newServalDHttpConnection("/restful/meshms/" + sid.toHex() + "/conversationlist.json");
httpConnection.connect();
if (httpConnection.getResponseCode() != HttpURLConnection.HTTP_OK)
throw new ServalDInterfaceException("unexpected HTTP response code: " + httpConnection.getResponseCode());
if (!httpConnection.getContentType().equals("application/json"))
throw new ServalDInterfaceException("unexpected HTTP Content-Type: " + httpConnection.getContentType());
json = new JSONTokeniser(new InputStreamReader(httpConnection.getInputStream(), "US-ASCII"));
json = new JSONTokeniser(new InputStreamReader(httpConnection.getInputStream(), "US-ASCII"));
json.consume(JSONTokeniser.Token.START_OBJECT);
json.consume("header");
json.consume(JSONTokeniser.Token.COLON);
headers = new Vector<String>();
json.consumeArray(headers, String.class);
if (headers.size() < 1)
throw new ServalDInterfaceException("empty JSON headers array");
for (int i = 0; i < headers.size(); ++i) {
String header = headers.get(i);
if (header.equals("_id"))
columnIndex__id = i;
else if (header.equals("my_sid"))
columnIndex_my_sid = i;
else if (header.equals("their_sid"))
columnIndex_their_sid = i;
else if (header.equals("read"))
columnIndex_read = i;
else if (header.equals("last_message"))
columnIndex_last_message = i;
else if (header.equals("read_offset"))
columnIndex_read_offset = i;
}
if (columnIndex__id == -1)
throw new ServalDInterfaceException("missing JSON column: _id");
if (columnIndex_my_sid == -1)
throw new ServalDInterfaceException("missing JSON column: my_sid");
if (columnIndex_their_sid == -1)
throw new ServalDInterfaceException("missing JSON column: their_sid");
if (columnIndex_read == -1)
throw new ServalDInterfaceException("missing JSON column: read");
if (columnIndex_last_message == -1)
throw new ServalDInterfaceException("missing JSON column: last_message");
if (columnIndex_read_offset == -1)
throw new ServalDInterfaceException("missing JSON column: read_offset");
json.consume(JSONTokeniser.Token.COMMA);
json.consume("rows");
json.consume(JSONTokeniser.Token.COLON);
json.consume(JSONTokeniser.Token.START_ARRAY);
}
catch (JSONInputException e) {
throw new ServalDInterfaceException(e);
}
if (columnIndex__id == -1)
throw new ServalDInterfaceException("missing JSON column: _id");
if (columnIndex_my_sid == -1)
throw new ServalDInterfaceException("missing JSON column: my_sid");
if (columnIndex_their_sid == -1)
throw new ServalDInterfaceException("missing JSON column: their_sid");
if (columnIndex_read == -1)
throw new ServalDInterfaceException("missing JSON column: read");
if (columnIndex_last_message == -1)
throw new ServalDInterfaceException("missing JSON column: last_message");
if (columnIndex_read_offset == -1)
throw new ServalDInterfaceException("missing JSON column: read_offset");
consume(reader, JsonToken.COMMA);
consume(reader, "rows");
consume(reader, JsonToken.COLON);
consume(reader, JsonToken.START_ARRAY);
}
public MeshMSConversation nextConversation() throws ServalDInterfaceException, IOException
{
Object tok = nextJsonToken(reader);
if (tok == JsonToken.END_ARRAY) {
consume(reader, JsonToken.END_OBJECT);
consume(reader, JsonToken.EOF);
return null;
}
if (rowCount != 0) {
match(tok, JsonToken.COMMA);
tok = nextJsonToken(reader);
}
match(tok, JsonToken.START_ARRAY);
Object[] row = new Object[headers.size()];
for (int i = 0; i < headers.size(); ++i) {
if (i != 0)
consume(reader, JsonToken.COMMA);
row[i] = consume(reader);
}
consume(reader, JsonToken.END_ARRAY);
int _id = narrow(row[columnIndex__id], Integer.class);
SubscriberId my_sid;
try {
my_sid = new SubscriberId(narrow(row[columnIndex_my_sid], String.class));
Object tok = json.nextToken();
if (tok == JSONTokeniser.Token.END_ARRAY) {
json.consume(JSONTokeniser.Token.END_OBJECT);
json.consume(JSONTokeniser.Token.EOF);
return null;
}
if (rowCount != 0) {
JSONTokeniser.match(tok, JSONTokeniser.Token.COMMA);
tok = json.nextToken();
}
JSONTokeniser.match(tok, JSONTokeniser.Token.START_ARRAY);
Object[] row = new Object[headers.size()];
for (int i = 0; i < headers.size(); ++i) {
if (i != 0)
json.consume(JSONTokeniser.Token.COMMA);
row[i] = json.consume();
}
json.consume(JSONTokeniser.Token.END_ARRAY);
int _id = JSONTokeniser.narrow(row[columnIndex__id], Integer.class);
SubscriberId my_sid;
try {
my_sid = new SubscriberId(JSONTokeniser.narrow(row[columnIndex_my_sid], String.class));
}
catch (SubscriberId.InvalidHexException e) {
throw new ServalDInterfaceException("invalid column value: my_sid", e);
}
SubscriberId their_sid;
try {
their_sid = new SubscriberId(JSONTokeniser.narrow(row[columnIndex_their_sid], String.class));
}
catch (SubscriberId.InvalidHexException e) {
throw new ServalDInterfaceException("invalid column value: their_sid", e);
}
boolean is_read = JSONTokeniser.narrow(row[columnIndex_read], Boolean.class);
int last_message = JSONTokeniser.narrow(row[columnIndex_last_message], Integer.class);
int read_offset = JSONTokeniser.narrow(row[columnIndex_read_offset], Integer.class);
return new MeshMSConversation(rowCount++, _id, my_sid, their_sid, is_read, last_message, read_offset);
}
catch (SubscriberId.InvalidHexException e) {
throw new ServalDInterfaceException("invalid JSON column value: my_sid", e);
catch (JSONInputException e) {
throw new ServalDInterfaceException(e);
}
SubscriberId their_sid;
try {
their_sid = new SubscriberId(narrow(row[columnIndex_their_sid], String.class));
}
catch (SubscriberId.InvalidHexException e) {
throw new ServalDInterfaceException("invalid JSON column value: their_sid", e);
}
boolean is_read = narrow(row[columnIndex_read], Boolean.class);
int last_message = narrow(row[columnIndex_last_message], Integer.class);
int read_offset = narrow(row[columnIndex_read_offset], Integer.class);
return new MeshMSConversation(rowCount++, _id, my_sid, their_sid, is_read, last_message, read_offset);
}
public void close() throws IOException
{
if (reader != null) {
reader.close();
reader = null;
}
}
static void match(Object tok, JsonToken exactly) throws ServalDInterfaceException
{
if (tok != exactly)
throw new ServalDInterfaceException("unexpected JSON token " + exactly + ", got: " + jsonTokenDescription(tok));
}
static void consume(PushbackReader rd, JsonToken exactly) throws ServalDInterfaceException, IOException
{
match(nextJsonToken(rd), exactly);
}
@SuppressWarnings("unchecked")
static <T> T narrow(Object tok, Class<T> cls) throws ServalDInterfaceException
{
assert !cls.isAssignableFrom(JsonToken.class); // can only narrow to values
if (tok == JsonToken.EOF)
throw new ServalDInterfaceException("unexpected EOF");
if (tok instanceof JsonToken)
throw new ServalDInterfaceException("expecting JSON " + cls.getName() + ", got: " + tok);
// Convert:
// Integer --> Float or Double
// Float --> Double
// Double --> Float
if (cls == Double.class && (tok instanceof Float || tok instanceof Integer))
tok = new Double(((Number)tok).doubleValue());
else if (cls == Float.class && (tok instanceof Double || tok instanceof Integer))
tok = new Float(((Number)tok).floatValue());
if (cls.isInstance(tok))
return (T)tok;
throw new ServalDInterfaceException("expecting JSON " + cls.getName() + ", got: " + jsonTokenDescription(tok));
}
static <T> T consume(PushbackReader rd, Class<T> cls) throws ServalDInterfaceException, IOException
{
return narrow(nextJsonToken(rd), cls);
}
static Object consume(PushbackReader rd) throws ServalDInterfaceException, IOException
{
return consume(rd, Object.class);
}
static String consume(PushbackReader rd, String exactly) throws ServalDInterfaceException, IOException
{
String tok = consume(rd, String.class);
if (tok.equals(exactly))
return tok;
throw new ServalDInterfaceException("unexpected JSON String \"" + exactly + "\", got: " + jsonTokenDescription(tok));
}
static <T> int consumeArray(PushbackReader rd, Collection<T> collection, Class<T> cls) throws ServalDInterfaceException, IOException
{
int added = 0;
consume(rd, JsonToken.START_ARRAY);
Object tok = nextJsonToken(rd);
if (tok != JsonToken.END_ARRAY) {
while (true) {
try {
collection.add(narrow(tok, cls));
++added;
}
catch (ClassCastException e) {
throw new ServalDInterfaceException("unexpected JSON token: " + jsonTokenDescription(tok));
}
tok = nextJsonToken(rd);
if (tok == JsonToken.END_ARRAY)
break;
match(tok, JsonToken.COMMA);
tok = nextJsonToken(rd);
}
}
return added;
}
enum JsonToken {
START_OBJECT,
END_OBJECT,
START_ARRAY,
END_ARRAY,
COMMA,
COLON,
NULL,
EOF
};
static boolean jsonIsToken(Object tok)
{
return tok instanceof JsonToken || tok instanceof String || tok instanceof Double || tok instanceof Integer || tok instanceof Boolean;
}
static String jsonTokenDescription(Object tok)
{
if (tok instanceof String)
return "\"" + tok + "\"";
if (tok instanceof Number)
return "" + tok;
if (tok instanceof Boolean)
return "" + tok;
assert tok instanceof JsonToken;
return tok.toString();
}
static void readAll(Reader rd, char[] word) throws ServalDInterfaceException, IOException
{
int len = 0;
while (len < word.length) {
int n = rd.read(word, len, word.length - len);
if (n == -1)
throw new ServalDInterfaceException("unexpected EOF");
len += n;
}
}
static Object nextJsonToken(PushbackReader rd) throws ServalDInterfaceException, IOException
{
while (true) {
int c = rd.read();
switch (c) {
case -1:
return JsonToken.EOF;
case '\t':
case '\r':
case '\n':
case ' ':
break;
case '{':
return JsonToken.START_OBJECT;
case '}':
return JsonToken.END_OBJECT;
case '[':
return JsonToken.START_ARRAY;
case ']':
return JsonToken.END_ARRAY;
case ',':
return JsonToken.COMMA;
case ':':
return JsonToken.COLON;
case 't': {
char[] word = new char[3];
readAll(rd, word);
if (word[0] == 'r' && word[1] == 'u' && word[2] == 'e')
return Boolean.TRUE;
}
throw new ServalDInterfaceException("malformed JSON");
case 'f': {
char[] word = new char[4];
readAll(rd, word);
if (word[0] == 'a' && word[1] == 'l' && word[2] == 's' && word[3] == 'e')
return Boolean.FALSE;
}
throw new ServalDInterfaceException("malformed JSON");
case 'n': {
char[] word = new char[3];
readAll(rd, word);
if (word[0] == 'u' && word[1] == 'l' && word[2] == 'l')
return JsonToken.NULL;
}
throw new ServalDInterfaceException("malformed JSON");
case '"': {
StringBuilder sb = new StringBuilder();
boolean slosh = false;
while (true) {
c = rd.read();
if (c == -1)
throw new ServalDInterfaceException("unexpected EOF in JSON string");
if (slosh) {
switch (c) {
case '"': case '/': case '\\': sb.append('"'); break;
case 'b': sb.append('\b'); break;
case 'f': sb.append('\f'); break;
case 'n': sb.append('\n'); break;
case 'r': sb.append('\r'); break;
case 't': sb.append('\t'); break;
case 'u':
char[] hex = new char[4];
readAll(rd, hex);
int code = Integer.valueOf(new String(hex), 16);
if (code >= 0 && code <= 0xffff) {
sb.append((char)code);
break;
}
// fall through
default:
throw new ServalDInterfaceException("malformed JSON string");
}
}
else {
switch (c) {
case '"':
return sb.toString();
case '\\':
slosh = true;
break;
default:
sb.append((char)c);
break;
}
}
}
}
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
case '-': {
StringBuilder sb = new StringBuilder();
if (c == '-') {
sb.append((char)c);
c = rd.read();
}
if (c == '0') {
sb.append((char)c);
c = rd.read();
}
else if (Character.isDigit(c)) {
do {
sb.append((char)c);
c = rd.read();
}
while (Character.isDigit(c));
}
else
throw new ServalDInterfaceException("malformed JSON number");
boolean isfloat = false;
if (c == '.') {
isfloat = true;
sb.append((char)c);
c = rd.read();
if (c == -1)
throw new ServalDInterfaceException("unexpected EOF in JSON number");
if (!Character.isDigit(c))
throw new ServalDInterfaceException("malformed JSON number");
do {
sb.append((char)c);
c = rd.read();
}
while (Character.isDigit(c));
}
if (c == 'e' || c == 'E') {
isfloat = true;
sb.append((char)c);
c = rd.read();
if (c == '+' || c == '-') {
sb.append((char)c);
c = rd.read();
}
if (c == -1)
throw new ServalDInterfaceException("unexpected EOF in JSON number");
if (!Character.isDigit(c))
throw new ServalDInterfaceException("malformed JSON number");
do {
sb.append((char)c);
c = rd.read();
}
while (Character.isDigit(c));
}
rd.unread(c);
String number = sb.toString();
try {
if (isfloat)
return Double.parseDouble(number);
else
return Integer.parseInt(number);
}
catch (NumberFormatException e) {
throw new ServalDInterfaceException("malformed JSON number: " + number);
}
}
default:
throw new ServalDInterfaceException("malformed JSON: '" + (char)c + "'");
}
httpConnection = null;
if (json != null) {
json.close();
json = null;
}
headers = null;
}
}