altoslib: Add JSON-based object saving/restoring code
authorKeith Packard <keithp@keithp.com>
Fri, 17 Jun 2016 07:00:09 +0000 (00:00 -0700)
committerKeith Packard <keithp@keithp.com>
Fri, 17 Jun 2016 07:00:09 +0000 (00:00 -0700)
This uses Java reflection to construct JSON strings for
most Java objects.

Signed-off-by: Keith Packard <keithp@keithp.com>
altoslib/AltosJson.java [new file with mode: 0644]

diff --git a/altoslib/AltosJson.java b/altoslib/AltosJson.java
new file mode 100644 (file)
index 0000000..6ae7e7d
--- /dev/null
@@ -0,0 +1,1268 @@
+/*
+ * 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; 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<String,AltosJson> hash;
+
+       void append_hash(StringBuffer result, int indent, boolean pretty) {
+               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(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<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 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, 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, "<EIO>");
+               }
+       }
+
+       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;
+       }
+}