From 93de1d7ec841c55f5a1a63d34b422780a6fbe3c3 Mon Sep 17 00:00:00 2001 From: Keith Packard Date: Fri, 17 Jun 2016 00:00:09 -0700 Subject: [PATCH] altoslib: Add JSON-based object saving/restoring code This uses Java reflection to construct JSON strings for most Java objects. Signed-off-by: Keith Packard --- altoslib/AltosJson.java | 1268 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 1268 insertions(+) create mode 100644 altoslib/AltosJson.java diff --git a/altoslib/AltosJson.java b/altoslib/AltosJson.java new file mode 100644 index 00000000..6ae7e7dc --- /dev/null +++ b/altoslib/AltosJson.java @@ -0,0 +1,1268 @@ +/* + * Copyright © 2016 Keith Packard + * + * 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; version 2 of the License. + * + * 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_11; + +import java.io.*; +import java.util.*; +import java.text.*; +import java.lang.*; +import java.lang.reflect.*; + +class JsonUtil { + StringBuffer quote(StringBuffer result, String a) { + result.append("\""); + for (int i = 0; i < a.length(); i++) { + char c = a.charAt(i); + + switch (c) { + case '"': + case '\\': + result.append('\\').append(c); + break; + case '\n': + result.append("\\n"); + break; + default: + result.append(c); + break; + } + } + result.append("\""); + return result; + } + + StringBuffer append(StringBuffer result, AltosJson value, int indent, boolean pretty) { + value.append(result, indent, pretty); + return result; + } + + StringBuffer append(StringBuffer result, String string) { + result.append(string); + return result; + } + + StringBuffer indent(StringBuffer result, int indent) { + result.append("\n"); + for (int i = 0; i < indent; i++) + result.append("\t"); + return result; + } + static NumberFormat get_nf_json() { + 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); + return nf; + } + + static NumberFormat nf_json = get_nf_json(); +} + +class JsonHash extends JsonUtil { + Hashtable hash; + + void append_hash(StringBuffer result, int indent, boolean pretty) { + boolean first = true; + + result.append("{"); + + ArrayList key_list = new ArrayList(hash.keySet()); + + Collections.sort(key_list, new Comparator() { + @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(); + } +} + +class JsonArray extends JsonUtil { + ArrayList array; + + void append_array(StringBuffer result, int indent, boolean pretty) { + 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(); + } +} + +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 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 ""; + case _error: + return ""; + default: + return ""; + } + } + + 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, StringBuffer bval) { + this(token, bval.toString()); + } +} + +/* + * Lexer for json + */ +class JsonLexer extends JsonUtil { + StringReader f; + int line; + int ungot = -2; + StringBuffer pending_token; + 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)), + }; + + 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 '"': + StringBuffer bval = new StringBuffer(); + 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.appendCodePoint(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, ""); + } + } + + void next() { + token = lex(); + } + + 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 StringReader(s); + 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); + } +} + +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 + */ + StringBuffer append(StringBuffer result, int indent, boolean pretty) { + 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: + String dval = nf_json.format(d_number); + if (dval.equals("-0")) + dval = "0"; + result.append(dval); + break; + case type_long: + result.append(new Long(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) { + StringBuffer result = new StringBuffer(); + append(result, indent, pretty); + return result.toString(); + } + + public String toString() { + return toString(0, false); + } + + public String toPrettyString() { + return toString(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; + } + } + + /* 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 + */ + 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 == 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.newInstance(); + } + for (; c != null; c = c.getSuperclass()) { + for (Field field : c.getDeclaredFields()) { + String fieldName = field.getName(); + Class fieldClass = field.getType(); + String className = fieldClass.getName(); + + 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) { + } + } + } + ret = object; + } catch (InvocationTargetException ie) { + ret = null; + } catch (NoSuchMethodException ie) { + ret = null; + } catch (InstantiationException ie) { + ret = null; + } catch (IllegalAccessException ie) { + 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.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 != null; c = c.getSuperclass()) { + for (Field field : c.getDeclaredFields()) { + String fieldName = field.getName(); + Class fieldClass = field.getType(); + String className = fieldClass.getName(); + + /* 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) { + } + } + } + } + } + + /* 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; + } +} -- 2.30.2