mirror of
https://github.com/servalproject/serval-dna.git
synced 2025-02-20 17:33:08 +00:00
Refactor Java JSON parsing into JSONTokeniser
This commit is contained in:
parent
9cbd7c365c
commit
0a54414744
43
java/org/servalproject/json/JSONInputException.java
Normal file
43
java/org/servalproject/json/JSONInputException.java
Normal 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);
|
||||
}
|
||||
|
||||
}
|
377
java/org/servalproject/json/JSONTokeniser.java
Normal file
377
java/org/servalproject/json/JSONTokeniser.java
Normal 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();
|
||||
}
|
||||
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user