1368 lines
31 KiB
Java
1368 lines
31 KiB
Java
/*
|
|
* Copyright © 2016 Keith Packard <keithp@keithp.com>
|
|
*
|
|
* This program 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 2 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program 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 program; if not, write to the Free Software Foundation, Inc.,
|
|
* 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
|
|
*/
|
|
|
|
package org.altusmetrum.altoslib_14;
|
|
|
|
import java.io.*;
|
|
import java.util.*;
|
|
import java.text.*;
|
|
import java.lang.*;
|
|
import java.lang.reflect.*;
|
|
|
|
class JsonUtil {
|
|
Writer quote(Writer writer, String a) throws IOException {
|
|
writer.append("\"");
|
|
for (int i = 0; i < a.length(); i++) {
|
|
char c = a.charAt(i);
|
|
|
|
switch (c) {
|
|
case '"':
|
|
case '\\':
|
|
writer.append('\\').append(c);
|
|
break;
|
|
case '\n':
|
|
writer.append("\\n");
|
|
break;
|
|
default:
|
|
writer.append(c);
|
|
break;
|
|
}
|
|
}
|
|
writer.append("\"");
|
|
return writer;
|
|
}
|
|
|
|
Writer append(Writer result, AltosJson value, int indent, boolean pretty) throws IOException {
|
|
value.append(result, indent, pretty);
|
|
return result;
|
|
}
|
|
|
|
Writer append(Writer result, String string) throws IOException {
|
|
result.append(string);
|
|
return result;
|
|
}
|
|
|
|
Writer indent(Writer result, int indent) throws IOException {
|
|
result.append("\n");
|
|
for (int i = 0; i < indent; i++)
|
|
result.append("\t");
|
|
return result;
|
|
}
|
|
|
|
NumberFormat _nf_json;
|
|
|
|
NumberFormat nf_json() {
|
|
if (_nf_json == null) {
|
|
DecimalFormat nf = (DecimalFormat) NumberFormat.getNumberInstance(Locale.ROOT);
|
|
nf.setParseIntegerOnly(false);
|
|
nf.setGroupingUsed(false);
|
|
nf.setMaximumFractionDigits(17);
|
|
nf.setMinimumFractionDigits(0);
|
|
nf.setMinimumIntegerDigits(1);
|
|
nf.setDecimalSeparatorAlwaysShown(false);
|
|
_nf_json = nf;
|
|
}
|
|
return _nf_json;
|
|
}
|
|
}
|
|
|
|
class JsonHash extends JsonUtil {
|
|
Hashtable<String,AltosJson> hash;
|
|
|
|
void append_hash(Writer result, int indent, boolean pretty) throws IOException {
|
|
boolean first = true;
|
|
|
|
result.append("{");
|
|
|
|
ArrayList<String> key_list = new ArrayList<String>(hash.keySet());
|
|
|
|
Collections.sort(key_list, new Comparator<String>() {
|
|
@Override
|
|
public int compare(String a, String b) { return a.compareTo(b); }
|
|
});
|
|
|
|
for (String key : key_list) {
|
|
AltosJson value = hash.get(key);
|
|
|
|
if (!first)
|
|
result.append(",");
|
|
first = false;
|
|
if (pretty)
|
|
indent(result, indent+1);
|
|
quote(result, key);
|
|
append(result, ": ");
|
|
append(result, value, indent+1, pretty);
|
|
}
|
|
if (pretty)
|
|
indent(result, indent);
|
|
append(result, "}");
|
|
}
|
|
|
|
void put(String key, AltosJson value) {
|
|
hash.put(key, value);
|
|
}
|
|
|
|
AltosJson get(String key) {
|
|
return hash.get(key);
|
|
}
|
|
|
|
JsonHash() {
|
|
hash = new Hashtable<String,AltosJson>();
|
|
}
|
|
}
|
|
|
|
class JsonArray extends JsonUtil {
|
|
ArrayList<AltosJson> array;
|
|
|
|
void append_array(Writer result, int indent, boolean pretty) throws IOException {
|
|
boolean first = true;
|
|
|
|
append(result, "[");
|
|
for (int i = 0; i < array.size(); i++) {
|
|
AltosJson value = array.get(i);
|
|
|
|
if (!first)
|
|
append(result, ",");
|
|
first = false;
|
|
if (pretty)
|
|
indent(result, indent+1);
|
|
append(result, value, indent+1, pretty);
|
|
}
|
|
if (pretty)
|
|
indent(result, indent);
|
|
append(result, "]");
|
|
}
|
|
|
|
void put(int index, AltosJson value) {
|
|
if (index >= array.size())
|
|
array.add(index, value);
|
|
else
|
|
array.set(index, value);
|
|
}
|
|
|
|
AltosJson get(int index) {
|
|
if (index < 0 || index > array.size())
|
|
return null;
|
|
return array.get(index);
|
|
}
|
|
|
|
int size() {
|
|
return array.size();
|
|
}
|
|
|
|
JsonArray() {
|
|
array = new ArrayList<AltosJson>();
|
|
}
|
|
}
|
|
|
|
class JsonToken {
|
|
double dval;
|
|
long lval;
|
|
String sval;
|
|
boolean bval;
|
|
int token;
|
|
|
|
static final int _string = 0;
|
|
static final int _double = 1;
|
|
static final int _long = 2;
|
|
static final int _boolean = 3;
|
|
static final int _oc = 4;
|
|
static final int _cc = 5;
|
|
static final int _os = 6;
|
|
static final int _cs = 7;
|
|
static final int _comma = 8;
|
|
static final int _colon = 9;
|
|
static final int _end = 10;
|
|
static final int _error = 11;
|
|
static final int _none = 12;
|
|
|
|
static String token_name(int token) {
|
|
switch (token) {
|
|
case _string:
|
|
return "string";
|
|
case _double:
|
|
return "number";
|
|
case _long:
|
|
return "number";
|
|
case _boolean:
|
|
return "boolean";
|
|
case _oc:
|
|
return "{";
|
|
case _cc:
|
|
return "}";
|
|
case _os:
|
|
return "[";
|
|
case _cs:
|
|
return "]";
|
|
case _comma:
|
|
return ",";
|
|
case _colon:
|
|
return ":";
|
|
case _end:
|
|
return "<EOF>";
|
|
case _error:
|
|
return "<ERROR>";
|
|
default:
|
|
return "<UNKNOWN>";
|
|
}
|
|
}
|
|
|
|
String token_name() {
|
|
return token_name(token);
|
|
}
|
|
|
|
JsonToken(int token) {
|
|
this.token = token;
|
|
}
|
|
|
|
JsonToken(int token, boolean bval) {
|
|
this.token = token;
|
|
this.bval = bval;
|
|
}
|
|
|
|
JsonToken(int token, double dval) {
|
|
this.token = token;
|
|
this.dval = dval;
|
|
}
|
|
|
|
JsonToken(int token, long lval) {
|
|
this.token = token;
|
|
this.lval = lval;
|
|
}
|
|
|
|
JsonToken(int token, String sval) {
|
|
this.token = token;
|
|
this.sval = sval;
|
|
}
|
|
|
|
JsonToken(int token, Writer bval) {
|
|
this(token, bval.toString());
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Lexer for json
|
|
*/
|
|
class JsonLexer extends JsonUtil {
|
|
InputStream f;
|
|
int line;
|
|
int ungot = -2;
|
|
StringBuffer pending_token;
|
|
private JsonToken token;
|
|
|
|
static class keyword {
|
|
String word;
|
|
JsonToken token;
|
|
|
|
JsonToken match(String value) {
|
|
if (word.equals(value))
|
|
return token;
|
|
return null;
|
|
}
|
|
|
|
keyword(String word, JsonToken token) {
|
|
this.word = word;
|
|
this.token = token;
|
|
}
|
|
}
|
|
|
|
/* boolean values are the only keywords in json
|
|
*/
|
|
static keyword[] keywords = {
|
|
new keyword("true", new JsonToken(JsonToken._boolean, true)),
|
|
new keyword("false", new JsonToken(JsonToken._boolean, false)),
|
|
new keyword("NegInfinity", new JsonToken(JsonToken._double, Double.NEGATIVE_INFINITY)),
|
|
new keyword("Infinity", new JsonToken(JsonToken._double, Double.POSITIVE_INFINITY)),
|
|
new keyword("NaN", new JsonToken(JsonToken._double, Double.NaN))
|
|
};
|
|
|
|
static JsonToken keyword(String word) {
|
|
for (int i = 0; i < keywords.length; i++) {
|
|
JsonToken token = keywords[i].match(word);
|
|
if (token != null)
|
|
return token;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
/* Get the next char (-1 for EOF) */
|
|
int ch() throws IOException {
|
|
int c;
|
|
if (ungot != -2) {
|
|
c = ungot;
|
|
ungot = -2;
|
|
} else
|
|
c = f.read();
|
|
if (c != -1)
|
|
pending_token.append((char) c);
|
|
if (c == '\n')
|
|
++line;
|
|
return c;
|
|
}
|
|
|
|
void unch(int c) {
|
|
if (ungot != -2)
|
|
throw new IllegalArgumentException("ungot buffer full");
|
|
pending_token.deleteCharAt( pending_token.length()-1);
|
|
if (c == '\n')
|
|
--line;
|
|
ungot = c;
|
|
}
|
|
|
|
String last_token_string() {
|
|
if (pending_token == null)
|
|
return null;
|
|
|
|
return pending_token.toString();
|
|
}
|
|
|
|
static boolean is_long_range(double d) {
|
|
return -9223372036854775808.0 <= d && d <= 9223372036854775807.0;
|
|
}
|
|
|
|
JsonToken lex() {
|
|
pending_token = new StringBuffer();
|
|
|
|
try {
|
|
for (;;) {
|
|
int c = ch();
|
|
|
|
switch (c) {
|
|
case -1:
|
|
return new JsonToken(JsonToken._end);
|
|
case '\n':
|
|
case ' ':
|
|
case '\t':
|
|
continue;
|
|
case '{':
|
|
return new JsonToken(JsonToken._oc);
|
|
case '}':
|
|
return new JsonToken(JsonToken._cc);
|
|
case '[':
|
|
return new JsonToken(JsonToken._os);
|
|
case ']':
|
|
return new JsonToken(JsonToken._cs);
|
|
case ',':
|
|
return new JsonToken(JsonToken._comma);
|
|
case ':':
|
|
return new JsonToken(JsonToken._colon);
|
|
case '0': case '1': case '2': case '3': case '4':
|
|
case '5': case '6': case '7': case '8': case '9':
|
|
case '.': case '-': case '+':
|
|
StringBuffer dbuf = new StringBuffer();
|
|
boolean is_double = false;
|
|
while (Character.isDigit(c) || c == '.' || c == '+' || c == '-' || c == 'e' || c == 'E') {
|
|
if (c == '.' || c == 'E')
|
|
is_double = true;
|
|
dbuf.appendCodePoint(c);
|
|
c = ch();
|
|
}
|
|
unch(c);
|
|
String dstr = dbuf.toString();
|
|
double dval;
|
|
try {
|
|
dval = nf_json().parse(dstr).doubleValue();
|
|
} catch (ParseException pe) {
|
|
return new JsonToken(JsonToken._error, dstr);
|
|
}
|
|
if (is_double || !is_long_range(dval))
|
|
return new JsonToken(JsonToken._double, dval);
|
|
else {
|
|
long lval = Long.parseLong(dstr);
|
|
return new JsonToken(JsonToken._long, lval);
|
|
}
|
|
case '"':
|
|
Writer bval = new StringWriter();
|
|
for (;;) {
|
|
c = ch();
|
|
if (c == '"')
|
|
break;
|
|
if (c == '\\') {
|
|
c = ch();
|
|
switch (c) {
|
|
case 'n':
|
|
c = '\n';
|
|
break;
|
|
case 't':
|
|
c = '\t';
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
bval.write(c);
|
|
}
|
|
return new JsonToken(JsonToken._string, bval);
|
|
default:
|
|
if (Character.isLetter(c)) {
|
|
StringBuffer tbuf = new StringBuffer();
|
|
do {
|
|
tbuf.appendCodePoint(c);
|
|
c = ch();
|
|
} while (Character.isLetter(c));
|
|
unch(c);
|
|
JsonToken token = keyword(tbuf.toString());
|
|
if (token != null)
|
|
return token;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
} catch (IOException ie) {
|
|
return new JsonToken(JsonToken._error, "<EIO>");
|
|
}
|
|
}
|
|
|
|
void next() {
|
|
token = null;
|
|
}
|
|
|
|
JsonToken token() {
|
|
if (token == null)
|
|
token = lex();
|
|
return token;
|
|
}
|
|
|
|
JsonToken expect(int e) {
|
|
JsonToken t = token();
|
|
if (t.token != e)
|
|
throw new IllegalArgumentException(String.format("got \"%s\" while expecting \"%s\"",
|
|
token.token_name(),
|
|
JsonToken.token_name(e)));
|
|
next();
|
|
return t;
|
|
}
|
|
|
|
JsonLexer(String s) {
|
|
f = new AltosStringInputStream(s);
|
|
line = 1;
|
|
token = null;
|
|
}
|
|
|
|
JsonLexer(InputStream f) {
|
|
this.f = f;
|
|
line = 1;
|
|
token = null;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Parse a json string into a AltosJson object
|
|
*/
|
|
class JsonParse {
|
|
JsonLexer lexer;
|
|
|
|
void parse_error(String format, Object ... arguments) {
|
|
throw new IllegalArgumentException(String.format("line %d: JSON parse error %s\n",
|
|
lexer.line,
|
|
String.format(format, arguments)));
|
|
}
|
|
|
|
/* Hashes are { string: value ... } */
|
|
JsonHash hash() {
|
|
JsonHash hash = new JsonHash();
|
|
|
|
/* skip the open brace */
|
|
lexer.next();
|
|
for (;;) {
|
|
/* Allow for empty hashes */
|
|
if (lexer.token().token == JsonToken._cc) {
|
|
lexer.next();
|
|
return hash;
|
|
}
|
|
|
|
/* string : value */
|
|
String key = lexer.expect(JsonToken._string).sval;
|
|
lexer.expect(JsonToken._colon);
|
|
AltosJson value = value();
|
|
hash.put(key, value);
|
|
|
|
switch (lexer.token().token) {
|
|
case JsonToken._comma:
|
|
lexer.next();
|
|
break;
|
|
case JsonToken._cc:
|
|
lexer.next();
|
|
return hash;
|
|
default:
|
|
parse_error("got %s expect \",\" or \"}\"", lexer.token().token_name());
|
|
return null;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Arrays are [ value ... ] */
|
|
JsonArray array() {
|
|
JsonArray array = new JsonArray();
|
|
|
|
lexer.next();
|
|
for (int i = 0;; i++) {
|
|
/* Allow for empty arrays */
|
|
if (lexer.token().token == JsonToken._cs) {
|
|
lexer.next();
|
|
return array;
|
|
}
|
|
|
|
AltosJson value = value();
|
|
array.put(i, value);
|
|
switch (lexer.token().token) {
|
|
case JsonToken._comma:
|
|
lexer.next();
|
|
break;
|
|
case JsonToken._cs:
|
|
lexer.next();
|
|
return array;
|
|
default:
|
|
parse_error("got %s expect \",\" or \"]\"", lexer.token().token_name());
|
|
return null;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Json is a simple LL language; one token is sufficient to
|
|
* identify the next object in the input
|
|
*/
|
|
AltosJson value() {
|
|
switch (lexer.token().token) {
|
|
case JsonToken._oc:
|
|
return new AltosJson(hash());
|
|
case JsonToken._os:
|
|
return new AltosJson(array());
|
|
case JsonToken._double:
|
|
double dval = lexer.token().dval;
|
|
lexer.next();
|
|
return new AltosJson(dval);
|
|
case JsonToken._long:
|
|
long lval = lexer.token().lval;
|
|
lexer.next();
|
|
return new AltosJson(lval);
|
|
case JsonToken._string:
|
|
String sval = lexer.token().sval;
|
|
lexer.next();
|
|
return new AltosJson(sval);
|
|
case JsonToken._boolean:
|
|
boolean bval = lexer.token().bval;
|
|
lexer.next();
|
|
return new AltosJson(bval);
|
|
default:
|
|
parse_error("Unexpected token \"%s\"", lexer.token().token_name());
|
|
}
|
|
return null;
|
|
}
|
|
|
|
AltosJson parse() {
|
|
lexer.next();
|
|
return value();
|
|
}
|
|
|
|
JsonParse(String s) {
|
|
lexer = new JsonLexer(s);
|
|
}
|
|
|
|
JsonParse(InputStream f) {
|
|
lexer = new JsonLexer(f);
|
|
}
|
|
}
|
|
|
|
public class AltosJson extends JsonUtil {
|
|
private static final int type_none = 0;
|
|
private static final int type_hash = 1;
|
|
private static final int type_array = 2;
|
|
private static final int type_double = 3;
|
|
private static final int type_long = 4;
|
|
private static final int type_string = 5;
|
|
private static final int type_boolean = 6;
|
|
|
|
private int type;
|
|
|
|
private JsonHash hash;
|
|
private JsonArray array;
|
|
private double d_number;
|
|
private long l_number;
|
|
private String string;
|
|
private boolean bool;
|
|
|
|
/* Generate string representation of the value
|
|
*/
|
|
Writer append(Writer result, int indent, boolean pretty) throws IOException {
|
|
switch (type) {
|
|
case type_hash:
|
|
hash.append_hash(result, indent, pretty);
|
|
break;
|
|
case type_array:
|
|
array.append_array(result, indent, pretty);
|
|
break;
|
|
case type_double:
|
|
if (Double.isInfinite(d_number)) {
|
|
if (d_number < 0)
|
|
result.append("NegInfinity");
|
|
else
|
|
result.append("Infinity");
|
|
} else if (Double.isNaN(d_number)) {
|
|
result.append("NaN");
|
|
} else {
|
|
String dval = nf_json().format(d_number);
|
|
if (dval.equals("-0"))
|
|
dval = "0";
|
|
result.append(dval);
|
|
}
|
|
break;
|
|
case type_long:
|
|
result.append(Long.valueOf(l_number).toString());
|
|
break;
|
|
case type_string:
|
|
quote(result, string);
|
|
break;
|
|
case type_boolean:
|
|
result.append(bool ? "true" : "false");
|
|
break;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
private String toString(int indent, boolean pretty) {
|
|
try {
|
|
Writer result = new StringWriter();
|
|
append(result, indent, pretty);
|
|
return result.toString();
|
|
} catch (Exception e) {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
public String toString() {
|
|
return toString(0, false);
|
|
}
|
|
|
|
public String toPrettyString() {
|
|
return toString(0, true);
|
|
}
|
|
|
|
public void write(Writer w, int indent, boolean pretty) throws IOException {
|
|
append(w, indent, pretty);
|
|
}
|
|
|
|
public void write(Writer w) throws IOException {
|
|
write(w, 0, true);
|
|
}
|
|
|
|
/* Parse string representation to a value
|
|
*/
|
|
|
|
public static AltosJson fromString(String string) {
|
|
JsonParse parse = new JsonParse(string);
|
|
try {
|
|
return parse.parse();
|
|
} catch (IllegalArgumentException ie) {
|
|
System.out.printf("json:\n%s\n%s\n", string, ie.getMessage());
|
|
return null;
|
|
}
|
|
}
|
|
|
|
public static AltosJson fromInputStream(InputStream f) {
|
|
JsonParse parse = new JsonParse(f);
|
|
try {
|
|
return parse.parse();
|
|
} catch (IllegalArgumentException ie) {
|
|
System.out.printf("json:\n%s\n", ie.getMessage());
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/* Accessor functions
|
|
*/
|
|
private boolean assert_type(boolean setting, int type, int other_type, String error) {
|
|
if (setting && this.type == type_none) {
|
|
this.type = type;
|
|
return false;
|
|
}
|
|
if (this.type != type && this.type != other_type)
|
|
throw new IllegalArgumentException(error);
|
|
return true;
|
|
}
|
|
|
|
private boolean assert_type(boolean setting, int type, String error) {
|
|
return assert_type(setting, type, type, error);
|
|
}
|
|
|
|
private void assert_hash(boolean setting) {
|
|
if (!assert_type(setting, type_hash, "not a hash"))
|
|
hash = new JsonHash();
|
|
}
|
|
|
|
private void assert_array(boolean setting) {
|
|
if (!assert_type(setting, type_array, "not an array"))
|
|
array = new JsonArray();
|
|
}
|
|
|
|
private void assert_number() {
|
|
assert_type(false, type_double, type_long, "not a number");
|
|
}
|
|
|
|
private void assert_double() {
|
|
assert_type(true, type_double, type_long, "not a number");
|
|
}
|
|
|
|
private void assert_long() {
|
|
assert_type(true, type_long, type_double, "not a number");
|
|
}
|
|
|
|
private void assert_string(boolean setting) {
|
|
assert_type(setting, type_string, "not a string");
|
|
}
|
|
|
|
private void assert_boolean(boolean setting) {
|
|
assert_type(setting, type_boolean, "not a boolean");
|
|
}
|
|
|
|
/* Primitive accessors
|
|
*/
|
|
public double number() {
|
|
assert_number();
|
|
if (type == type_double)
|
|
return d_number;
|
|
else
|
|
return (double) l_number;
|
|
}
|
|
|
|
public long l_number() {
|
|
assert_number();
|
|
if (type == type_double)
|
|
return (long) d_number;
|
|
else
|
|
return l_number;
|
|
}
|
|
|
|
public String string() {
|
|
assert_string(false);
|
|
return string;
|
|
}
|
|
|
|
public boolean bool() {
|
|
assert_boolean(false);
|
|
return bool;
|
|
}
|
|
|
|
public AltosJson get(int index) {
|
|
assert_array(false);
|
|
return array.get(index);
|
|
}
|
|
|
|
public AltosJson get(String key) {
|
|
assert_hash(false);
|
|
return hash.get(key);
|
|
}
|
|
|
|
public int size() {
|
|
assert_array(false);
|
|
return array.size();
|
|
}
|
|
|
|
/* Typed accessors with defaulting
|
|
*/
|
|
public double get_double(String key, double def) {
|
|
AltosJson value = get(key);
|
|
if (value != null) {
|
|
return value.number();
|
|
}
|
|
return def;
|
|
}
|
|
|
|
public long get_long(String key, long def) {
|
|
AltosJson value = get(key);
|
|
if (value != null)
|
|
return value.l_number();
|
|
return def;
|
|
}
|
|
|
|
public int get_int(String key, int def) {
|
|
AltosJson value = get(key);
|
|
if (value != null)
|
|
return (int) value.l_number();
|
|
return def;
|
|
}
|
|
|
|
public String get_string(String key, String def) {
|
|
AltosJson value = get(key);
|
|
if (value != null)
|
|
return value.string();
|
|
return def;
|
|
}
|
|
|
|
public boolean get_boolean(String key, boolean def) {
|
|
AltosJson value = get(key);
|
|
if (value != null)
|
|
return value.bool();
|
|
return def;
|
|
}
|
|
|
|
public double get_double(int index, double def) {
|
|
AltosJson value = get(index);
|
|
if (value != null)
|
|
return value.number();
|
|
return def;
|
|
}
|
|
|
|
public long get_long(int index, long def) {
|
|
AltosJson value = get(index);
|
|
if (value != null)
|
|
return value.l_number();
|
|
return def;
|
|
}
|
|
|
|
public int get_int(int index, int def) {
|
|
AltosJson value = get(index);
|
|
if (value != null)
|
|
return (int) value.l_number();
|
|
return def;
|
|
}
|
|
|
|
public String get_string(int index, String def) {
|
|
AltosJson value = get(index);
|
|
if (value != null)
|
|
return value.string();
|
|
return def;
|
|
}
|
|
|
|
public boolean get_boolean(int index, boolean def) {
|
|
AltosJson value = get(index);
|
|
if (value != null)
|
|
return value.bool();
|
|
return def;
|
|
}
|
|
|
|
public double[] get_double_array(String key, double[] def) {
|
|
AltosJson value = get(key);
|
|
if (value != null) {
|
|
double[] ret = new double[value.size()];
|
|
for (int i = 0; i < value.size(); i++)
|
|
ret[i] = value.get_double(i, def == null ? 0 : def[i]);
|
|
return ret;
|
|
}
|
|
return def;
|
|
}
|
|
|
|
public int[] get_int_array(String key, int[] def) {
|
|
AltosJson value = get(key);
|
|
if (value != null) {
|
|
int[] ret = new int[value.size()];
|
|
for (int i = 0; i < value.size(); i++)
|
|
ret[i] = value.get_int(i, def == null ? 0 : def[i]);
|
|
return ret;
|
|
}
|
|
return def;
|
|
}
|
|
|
|
/* Array setter functions
|
|
*/
|
|
public AltosJson put(int index, AltosJson value) {
|
|
assert_array(true);
|
|
array.put(index, value);
|
|
return value;
|
|
}
|
|
|
|
public Object put(int index, Object value) {
|
|
assert_array(true);
|
|
if (value != null)
|
|
array.put(index, new AltosJson(value));
|
|
return value;
|
|
}
|
|
|
|
public double put(int index, double value) {
|
|
assert_array(true);
|
|
array.put(index, new AltosJson(value));
|
|
return value;
|
|
}
|
|
|
|
public AltosJson put(int index, double[] value) {
|
|
if (value != null) {
|
|
assert_array(true);
|
|
array.put(index, new AltosJson(value));
|
|
}
|
|
return this;
|
|
}
|
|
|
|
public int[] put(int index, int[] value) {
|
|
if (value != null) {
|
|
assert_array(true);
|
|
array.put(index, new AltosJson(value));
|
|
}
|
|
return value;
|
|
}
|
|
|
|
public String put(int index, String value) {
|
|
if (value != null) {
|
|
assert_array(true);
|
|
array.put(index, new AltosJson(value));
|
|
}
|
|
return value;
|
|
}
|
|
|
|
public boolean put(int index, boolean value) {
|
|
assert_array(true);
|
|
array.put(index, new AltosJson(value));
|
|
return value;
|
|
}
|
|
|
|
/* Hash setter functions
|
|
*/
|
|
public AltosJson put(String key, AltosJson value) {
|
|
assert_hash(true);
|
|
hash.put(key, value);
|
|
return value;
|
|
}
|
|
|
|
public Object put(String key, Object value) {
|
|
assert_hash(true);
|
|
if (value != null)
|
|
hash.put(key, new AltosJson(value));
|
|
return value;
|
|
}
|
|
|
|
public double put(String key, double value) {
|
|
assert_hash(true);
|
|
hash.put(key, new AltosJson(value));
|
|
return value;
|
|
}
|
|
|
|
public String put(String key, String value) {
|
|
if (value != null) {
|
|
assert_hash(true);
|
|
hash.put(key, new AltosJson(value));
|
|
}
|
|
return value;
|
|
}
|
|
|
|
public boolean put(String key, boolean value) {
|
|
assert_hash(true);
|
|
hash.put(key, new AltosJson(value));
|
|
return value;
|
|
}
|
|
|
|
public AltosJson[] put(String key, AltosJson[] value) {
|
|
if (value != null) {
|
|
assert_hash(true);
|
|
hash.put(key, new AltosJson(value));
|
|
}
|
|
return value;
|
|
}
|
|
|
|
public double[] put(String key, double[] value) {
|
|
if (value != null) {
|
|
assert_hash(true);
|
|
hash.put(key, new AltosJson(value));
|
|
}
|
|
return value;
|
|
}
|
|
|
|
public int[] put(String key, int[] value) {
|
|
if (value != null) {
|
|
assert_hash(true);
|
|
hash.put(key, new AltosJson(value));
|
|
}
|
|
return value;
|
|
}
|
|
|
|
/* Primitive setter functions
|
|
*/
|
|
public double put(double value) {
|
|
assert_double();
|
|
d_number = value;
|
|
return value;
|
|
}
|
|
|
|
public byte put(byte value) {
|
|
assert_long();
|
|
l_number = value;
|
|
return value;
|
|
}
|
|
|
|
public char put(char value) {
|
|
assert_long();
|
|
l_number = value;
|
|
return value;
|
|
}
|
|
|
|
public int put(int value) {
|
|
assert_long();
|
|
l_number = value;
|
|
return value;
|
|
}
|
|
|
|
public long put(long value) {
|
|
assert_long();
|
|
l_number = value;
|
|
return value;
|
|
}
|
|
|
|
public String put(String value) {
|
|
assert_string(true);
|
|
string = value;
|
|
return value;
|
|
}
|
|
|
|
public boolean put(boolean value) {
|
|
assert_boolean(true);
|
|
bool = value;
|
|
return value;
|
|
}
|
|
|
|
private boolean isInnerClass(Class c) {
|
|
for (Field field : c.getDeclaredFields())
|
|
if (field.isSynthetic())
|
|
return true;
|
|
return false;
|
|
}
|
|
|
|
/* Construct an object of the specified class from the JSON
|
|
* representation.
|
|
*
|
|
* This works as long as the structure is non-recursive, and
|
|
* all inner classes are only members of their immediate outer
|
|
* class
|
|
*/
|
|
@SuppressWarnings("unchecked")
|
|
private Object make(Class c, Class enclosing_class, Object enclosing_object) {
|
|
Object ret;
|
|
if (c == Boolean.TYPE) {
|
|
ret = bool();
|
|
} else if (c == Byte.TYPE) {
|
|
ret = (Byte) (byte) l_number();
|
|
} else if (c == Character.TYPE) {
|
|
ret = (Character) (char) l_number();
|
|
} else if (c == Integer.TYPE) {
|
|
ret = (Integer) (int) l_number();
|
|
} else if (c == Long.TYPE) {
|
|
ret = l_number();
|
|
} else if (c == Double.TYPE) {
|
|
ret = number();
|
|
} else if (c == String.class) {
|
|
ret = string();
|
|
} else if (c.isArray()) {
|
|
assert_array(false);
|
|
|
|
Class element_class = c.getComponentType();
|
|
if (element_class == Boolean.TYPE) {
|
|
boolean[] array = (boolean[]) Array.newInstance(element_class, size());
|
|
for (int i = 0; i < array.length; i++)
|
|
array[i] = (Boolean) get(i).make(element_class);
|
|
ret = array;
|
|
} else if (element_class == Byte.TYPE) {
|
|
byte[] array = (byte[]) Array.newInstance(element_class, size());
|
|
for (int i = 0; i < array.length; i++)
|
|
array[i] = (Byte) get(i).make(element_class);
|
|
ret = array;
|
|
} else if (element_class == Character.TYPE) {
|
|
char[] array = (char[]) Array.newInstance(element_class, size());
|
|
for (int i = 0; i < array.length; i++)
|
|
array[i] = (Character) get(i).make(element_class);
|
|
ret = array;
|
|
} else if (element_class == Integer.TYPE) {
|
|
int[] array = (int[]) Array.newInstance(element_class, size());
|
|
for (int i = 0; i < array.length; i++)
|
|
array[i] = (Integer) get(i).make(element_class);
|
|
ret = array;
|
|
} else if (element_class == Long.TYPE) {
|
|
long[] array = (long[]) Array.newInstance(element_class, size());
|
|
for (int i = 0; i < array.length; i++)
|
|
array[i] = (Long) get(i).make(element_class);
|
|
ret = array;
|
|
} else if (element_class == Double.TYPE) {
|
|
double[] array = (double[]) Array.newInstance(element_class, size());
|
|
for (int i = 0; i < array.length; i++)
|
|
array[i] = (Double) get(i).make(element_class);
|
|
ret = array;
|
|
} else {
|
|
Object[] array = (Object[]) Array.newInstance(element_class, size());
|
|
for (int i = 0; i < array.length; i++)
|
|
array[i] = get(i).make(element_class);
|
|
ret = array;
|
|
}
|
|
} else {
|
|
assert_hash(false);
|
|
Object object = null;
|
|
try {
|
|
/* Inner classes have a hidden extra parameter
|
|
* to the constructor. Assume that the enclosing object is
|
|
* of the enclosing class and construct the object
|
|
* based on that.
|
|
*/
|
|
if (enclosing_class != null && isInnerClass(c)) {
|
|
Constructor<?> ctor = ((Class<?>)c).getDeclaredConstructor((Class<?>) enclosing_class);
|
|
object = ctor.newInstance(enclosing_object);
|
|
} else {
|
|
object = c.getDeclaredConstructor().newInstance();
|
|
}
|
|
for (; c != Object.class; c = c.getSuperclass()) {
|
|
for (Field field : c.getDeclaredFields()) {
|
|
String fieldName = field.getName();
|
|
Class fieldClass = field.getType();
|
|
|
|
if (Modifier.isStatic(field.getModifiers()))
|
|
continue;
|
|
if (field.isSynthetic())
|
|
continue;
|
|
try {
|
|
AltosJson json = get(fieldName);
|
|
if (json != null) {
|
|
Object val = json.make(fieldClass, c, object);
|
|
field.setAccessible(true);
|
|
field.set(object, val);
|
|
}
|
|
} catch (IllegalAccessException ie) {
|
|
System.out.printf("%s:%s %s\n",
|
|
c.getName(), fieldName, ie.toString());
|
|
}
|
|
}
|
|
}
|
|
ret = object;
|
|
} catch (InvocationTargetException ie) {
|
|
System.out.printf("%s: %s\n",
|
|
c.getName(), ie.toString());
|
|
ret = null;
|
|
} catch (NoSuchMethodException ie) {
|
|
System.out.printf("%s: %s\n",
|
|
c.getName(), ie.toString());
|
|
ret = null;
|
|
} catch (InstantiationException ie) {
|
|
System.out.printf("%s: %s\n",
|
|
c.getName(), ie.toString());
|
|
ret = null;
|
|
} catch (IllegalAccessException ie) {
|
|
System.out.printf("%s: %s\n",
|
|
c.getName(), ie.toString());
|
|
ret = null;
|
|
}
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
/* This is the public API for the
|
|
* above function which doesn't handle
|
|
* inner classes
|
|
*/
|
|
public Object make(Class c) {
|
|
return make(c, null, null);
|
|
}
|
|
|
|
/* Constructors, one for each primitive type, String and Object */
|
|
public AltosJson(boolean bool) {
|
|
type = type_boolean;
|
|
this.bool = bool;
|
|
}
|
|
|
|
public AltosJson(byte number) {
|
|
type = type_long;
|
|
this.l_number = number;
|
|
}
|
|
|
|
public AltosJson(char number) {
|
|
type = type_long;
|
|
this.l_number = number;
|
|
}
|
|
|
|
public AltosJson(int number) {
|
|
type = type_long;
|
|
this.l_number = number;
|
|
}
|
|
|
|
public AltosJson(long number) {
|
|
type = type_long;
|
|
this.l_number = number;
|
|
}
|
|
|
|
public AltosJson(double number) {
|
|
type = type_double;
|
|
this.d_number = number;
|
|
}
|
|
|
|
public AltosJson(String string) {
|
|
type = type_string;
|
|
this.string = string;
|
|
}
|
|
|
|
public AltosJson(Object object) {
|
|
if (object instanceof Boolean) {
|
|
type = type_boolean;
|
|
bool = (Boolean) object;
|
|
} else if (object instanceof Byte) {
|
|
type = type_long;
|
|
l_number = (Byte) object;
|
|
} else if (object instanceof Character) {
|
|
type = type_long;
|
|
l_number = (Character) object;
|
|
} else if (object instanceof Integer) {
|
|
type = type_long;
|
|
l_number = (Integer) object;
|
|
} else if (object instanceof Long) {
|
|
type = type_long;
|
|
l_number = (Long) object;
|
|
} else if (object instanceof Double) {
|
|
type = type_double;
|
|
d_number = (Double) object;
|
|
} else if (object instanceof String) {
|
|
type = type_string;
|
|
string = (String) object;
|
|
} else if (object == null) {
|
|
System.out.printf("unexpected null object\n");
|
|
} else if (object.getClass() == null) {
|
|
System.out.printf("unexpected null object class\n");
|
|
} else if (object.getClass().isArray()) {
|
|
assert_array(true);
|
|
|
|
Class component_class = object.getClass().getComponentType();
|
|
if (component_class == Boolean.TYPE) {
|
|
boolean[] array = (boolean[]) object;
|
|
for (int i = 0; i < array.length; i++)
|
|
put(i, new AltosJson(array[i]));
|
|
} else if (component_class == Byte.TYPE) {
|
|
byte[] array = (byte[]) object;
|
|
for (int i = 0; i < array.length; i++)
|
|
put(i, new AltosJson(array[i]));
|
|
} else if (component_class == Character.TYPE) {
|
|
char[] array = (char[]) object;
|
|
for (int i = 0; i < array.length; i++)
|
|
put(i, new AltosJson(array[i]));
|
|
} else if (component_class == Integer.TYPE) {
|
|
int[] array = (int[]) object;
|
|
for (int i = 0; i < array.length; i++)
|
|
put(i, new AltosJson(array[i]));
|
|
} else if (component_class == Long.TYPE) {
|
|
long[] array = (long[]) object;
|
|
for (int i = 0; i < array.length; i++)
|
|
put(i, new AltosJson(array[i]));
|
|
} else if (component_class == Double.TYPE) {
|
|
double[] array = (double[]) object;
|
|
for (int i = 0; i < array.length; i++)
|
|
put(i, new AltosJson(array[i]));
|
|
} else {
|
|
Object[] array = (Object[]) object;
|
|
for (int i = 0; i < array.length; i++)
|
|
put(i, new AltosJson(array[i]));
|
|
}
|
|
} else {
|
|
assert_hash(true);
|
|
for (Class c = object.getClass(); c != Object.class; c = c.getSuperclass()) {
|
|
for (Field field : c.getDeclaredFields()) {
|
|
String fieldName = field.getName();
|
|
|
|
/* XXX hack to allow fields to be not converted */
|
|
if (fieldName.startsWith("__"))
|
|
continue;
|
|
|
|
/* Skip static fields */
|
|
if (Modifier.isStatic(field.getModifiers()))
|
|
continue;
|
|
|
|
/* Skip synthetic fields. We're assuming
|
|
* those are always an inner class reference
|
|
* to the outer class object
|
|
*/
|
|
if (field.isSynthetic())
|
|
continue;
|
|
try {
|
|
/* We may need to force the field to be accessible if
|
|
* it is private
|
|
*/
|
|
field.setAccessible(true);
|
|
Object val = field.get(object);
|
|
if (val != null) {
|
|
AltosJson json = new AltosJson(val);
|
|
put(fieldName, json);
|
|
}
|
|
} catch (IllegalAccessException ie) {
|
|
System.out.printf("%s:%s %s\n",
|
|
c.getName(), fieldName, ie.toString());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Array constructors, one for each primitive type, String and Object */
|
|
public AltosJson(boolean[] bools) {
|
|
assert_array(true);
|
|
for(int i = 0; i < bools.length; i++)
|
|
put(i, new AltosJson(bools[i]));
|
|
}
|
|
|
|
public AltosJson(byte[] numbers) {
|
|
assert_array(true);
|
|
for(int i = 0; i < numbers.length; i++)
|
|
put(i, new AltosJson(numbers[i]));
|
|
}
|
|
|
|
public AltosJson(char[] numbers) {
|
|
assert_array(true);
|
|
for(int i = 0; i < numbers.length; i++)
|
|
put(i, new AltosJson(numbers[i]));
|
|
}
|
|
|
|
public AltosJson(int[] numbers) {
|
|
assert_array(true);
|
|
for(int i = 0; i < numbers.length; i++)
|
|
put(i, new AltosJson(numbers[i]));
|
|
}
|
|
|
|
public AltosJson(long[] numbers) {
|
|
assert_array(true);
|
|
for(int i = 0; i < numbers.length; i++)
|
|
put(i, new AltosJson(numbers[i]));
|
|
}
|
|
|
|
public AltosJson(double[] numbers) {
|
|
assert_array(true);
|
|
for(int i = 0; i < numbers.length; i++)
|
|
put(i, new AltosJson(numbers[i]));
|
|
}
|
|
|
|
public AltosJson(String[] strings) {
|
|
assert_array(true);
|
|
for(int i = 0; i < strings.length; i++)
|
|
put(i, new AltosJson(strings[i]));
|
|
}
|
|
|
|
public AltosJson(AltosJson[] jsons) {
|
|
assert_array(true);
|
|
for (int i = 0; i < jsons.length; i++)
|
|
put(i, jsons[i]);
|
|
}
|
|
|
|
public AltosJson(Object[] array) {
|
|
assert_array(true);
|
|
for (int i = 0; i < array.length; i++)
|
|
put(i, new AltosJson(array[i]));
|
|
}
|
|
|
|
/* Empty constructor
|
|
*/
|
|
public AltosJson() {
|
|
type = type_none;
|
|
}
|
|
|
|
public AltosJson(JsonHash hash) {
|
|
type = type_hash;
|
|
this.hash = hash;
|
|
}
|
|
|
|
public AltosJson(JsonArray array) {
|
|
type = type_array;
|
|
this.array = array;
|
|
}
|
|
}
|