altoslib: Add JSON-based object saving/restoring code
[fw/altos] / altoslib / AltosJson.java
1 /*
2  * Copyright © 2016 Keith Packard <keithp@keithp.com>
3  *
4  * This program is free software; you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License as published by
6  * the Free Software Foundation; version 2 of the License.
7  *
8  * This program is distributed in the hope that it will be useful, but
9  * WITHOUT ANY WARRANTY; without even the implied warranty of
10  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
11  * General Public License for more details.
12  *
13  * You should have received a copy of the GNU General Public License along
14  * with this program; if not, write to the Free Software Foundation, Inc.,
15  * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
16  */
17
18 package org.altusmetrum.altoslib_11;
19
20 import java.io.*;
21 import java.util.*;
22 import java.text.*;
23 import java.lang.*;
24 import java.lang.reflect.*;
25
26 class JsonUtil {
27         StringBuffer quote(StringBuffer result, String a) {
28                 result.append("\"");
29                 for (int i = 0; i < a.length(); i++) {
30                         char c = a.charAt(i);
31
32                         switch (c) {
33                         case '"':
34                         case '\\':
35                                 result.append('\\').append(c);
36                                 break;
37                         case '\n':
38                                 result.append("\\n");
39                                 break;
40                         default:
41                                 result.append(c);
42                                 break;
43                         }
44                 }
45                 result.append("\"");
46                 return result;
47         }
48
49         StringBuffer append(StringBuffer result, AltosJson value, int indent, boolean pretty) {
50                 value.append(result, indent, pretty);
51                 return result;
52         }
53
54         StringBuffer append(StringBuffer result, String string) {
55                 result.append(string);
56                 return result;
57         }
58
59         StringBuffer indent(StringBuffer result, int indent) {
60                 result.append("\n");
61                 for (int i = 0; i < indent; i++)
62                         result.append("\t");
63                 return result;
64         }
65         static NumberFormat get_nf_json() {
66                 DecimalFormat nf = (DecimalFormat) NumberFormat.getNumberInstance(Locale.ROOT);
67                 nf.setParseIntegerOnly(false);
68                 nf.setGroupingUsed(false);
69                 nf.setMaximumFractionDigits(17);
70                 nf.setMinimumFractionDigits(0);
71                 nf.setMinimumIntegerDigits(1);
72                 nf.setDecimalSeparatorAlwaysShown(false);
73                 return nf;
74         }
75
76         static NumberFormat nf_json = get_nf_json();
77 }
78
79 class JsonHash extends JsonUtil {
80         Hashtable<String,AltosJson> hash;
81
82         void append_hash(StringBuffer result, int indent, boolean pretty) {
83                 boolean         first = true;
84
85                 result.append("{");
86
87                 ArrayList<String> key_list = new ArrayList<String>(hash.keySet());
88
89                 Collections.sort(key_list, new Comparator<String>() {
90                                 @Override
91                                 public int compare(String a, String b) { return a.compareTo(b); }
92                         });
93
94                 for (String key : key_list) {
95                         AltosJson       value = hash.get(key);
96
97                         if (!first)
98                                 result.append(",");
99                         first = false;
100                         if (pretty)
101                                 indent(result, indent+1);
102                         quote(result, key);
103                         append(result, ": ");
104                         append(result, value, indent+1, pretty);
105                 }
106                 if (pretty)
107                         indent(result, indent);
108                 append(result, "}");
109         }
110
111         void put(String key, AltosJson value) {
112                 hash.put(key, value);
113         }
114
115         AltosJson get(String key) {
116                 return hash.get(key);
117         }
118
119         JsonHash() {
120                 hash = new Hashtable<String,AltosJson>();
121         }
122 }
123
124 class JsonArray extends JsonUtil {
125         ArrayList<AltosJson> array;
126
127         void append_array(StringBuffer result, int indent, boolean pretty) {
128                 boolean first = true;
129
130                 append(result, "[");
131                 for (int i = 0; i < array.size(); i++) {
132                         AltosJson       value = array.get(i);
133
134                         if (!first)
135                                 append(result, ",");
136                         first = false;
137                         if (pretty)
138                                 indent(result, indent+1);
139                         append(result, value, indent+1, pretty);
140                 }
141                 if (pretty)
142                         indent(result, indent);
143                 append(result, "]");
144         }
145
146         void put(int index, AltosJson value) {
147                 if (index >= array.size())
148                         array.add(index, value);
149                 else
150                         array.set(index, value);
151         }
152
153         AltosJson get(int index) {
154                 if (index < 0 || index > array.size())
155                         return null;
156                 return array.get(index);
157         }
158
159         int size() {
160                 return array.size();
161         }
162
163         JsonArray() {
164                 array = new ArrayList<AltosJson>();
165         }
166 }
167
168 class JsonToken {
169         double  dval;
170         long    lval;
171         String  sval;
172         boolean bval;
173         int     token;
174
175         static final int _string = 0;
176         static final int _double = 1;
177         static final int _long = 2;
178         static final int _boolean = 3;
179         static final int _oc = 4;
180         static final int _cc = 5;
181         static final int _os = 6;
182         static final int _cs = 7;
183         static final int _comma = 8;
184         static final int _colon = 9;
185         static final int _end = 10;
186         static final int _error = 11;
187
188         static String token_name(int token) {
189                 switch (token) {
190                 case _string:
191                         return "string";
192                 case _double:
193                         return "number";
194                 case _long:
195                         return "number";
196                 case _boolean:
197                         return "boolean";
198                 case _oc:
199                         return "{";
200                 case _cc:
201                         return "}";
202                 case _os:
203                         return "[";
204                 case _cs:
205                         return "]";
206                 case _comma:
207                         return ",";
208                 case _colon:
209                         return ":";
210                 case _end:
211                         return "<EOF>";
212                 case _error:
213                         return "<ERROR>";
214                 default:
215                         return "<UNKNOWN>";
216                 }
217         }
218
219         String token_name() {
220                 return token_name(token);
221         }
222
223         JsonToken(int token) {
224                 this.token = token;
225         }
226
227         JsonToken(int token, boolean bval) {
228                 this.token = token;
229                 this.bval = bval;
230         }
231
232         JsonToken(int token, double dval) {
233                 this.token = token;
234                 this.dval = dval;
235         }
236
237         JsonToken(int token, long lval) {
238                 this.token = token;
239                 this.lval = lval;
240         }
241
242         JsonToken(int token, String sval) {
243                 this.token = token;
244                 this.sval = sval;
245         }
246
247         JsonToken(int token, StringBuffer bval) {
248                 this(token, bval.toString());
249         }
250 }
251
252 /*
253  * Lexer for json
254  */
255 class JsonLexer extends JsonUtil {
256         StringReader    f;
257         int             line;
258         int             ungot = -2;
259         StringBuffer    pending_token;
260         JsonToken       token;
261
262         static class keyword {
263                 String          word;
264                 JsonToken       token;
265
266                 JsonToken match(String value) {
267                         if (word.equals(value))
268                                 return token;
269                         return null;
270                 }
271
272                 keyword(String word, JsonToken token) {
273                         this.word = word;
274                         this.token = token;
275                 }
276         }
277
278         /* boolean values are the only keywords in json
279          */
280         static keyword[] keywords = {
281                 new keyword("true", new JsonToken(JsonToken._boolean, true)),
282                 new keyword("false", new JsonToken(JsonToken._boolean, false)),
283         };
284
285         static JsonToken keyword(String word) {
286                 for (int i = 0; i < keywords.length; i++) {
287                         JsonToken token = keywords[i].match(word);
288                         if (token != null)
289                                 return token;
290                 }
291                 return null;
292         }
293
294         /* Get the next char (-1 for EOF) */
295         int ch() throws IOException {
296                 int c;
297                 if (ungot != -2) {
298                         c = ungot;
299                         ungot = -2;
300                 } else
301                         c = f.read();
302                 if (c != -1)
303                         pending_token.append((char) c);
304                 if (c == '\n')
305                         ++line;
306                 return c;
307         }
308
309         void unch(int c) {
310                 if (ungot != -2)
311                         throw new IllegalArgumentException("ungot buffer full");
312                 pending_token.deleteCharAt( pending_token.length()-1);
313                 if (c == '\n')
314                         --line;
315                 ungot = c;
316         }
317
318         String last_token_string() {
319                 if (pending_token == null)
320                         return null;
321
322                 return pending_token.toString();
323         }
324
325         static boolean is_long_range(double d) {
326                 return -9223372036854775808.0 <= d && d <= 9223372036854775807.0;
327         }
328
329         JsonToken lex() {
330                 pending_token = new StringBuffer();
331
332                 try {
333                         for (;;) {
334                                 int c = ch();
335
336                                 switch (c) {
337                                 case -1:
338                                         return new JsonToken(JsonToken._end);
339                                 case '\n':
340                                 case ' ':
341                                 case '\t':
342                                         continue;
343                                 case '{':
344                                         return new JsonToken(JsonToken._oc);
345                                 case '}':
346                                         return new JsonToken(JsonToken._cc);
347                                 case '[':
348                                         return new JsonToken(JsonToken._os);
349                                 case ']':
350                                         return new JsonToken(JsonToken._cs);
351                                 case ',':
352                                         return new JsonToken(JsonToken._comma);
353                                 case ':':
354                                         return new JsonToken(JsonToken._colon);
355                                 case '0': case '1': case '2': case '3': case '4':
356                                 case '5': case '6': case '7': case '8': case '9':
357                                 case '.': case '-': case '+':
358                                         StringBuffer dbuf = new StringBuffer();
359                                         boolean is_double = false;
360                                         while (Character.isDigit(c) || c == '.' || c == '+' || c == '-' || c == 'e' || c == 'E') {
361                                                 if (c == '.' || c == 'E')
362                                                         is_double = true;
363                                                 dbuf.appendCodePoint(c);
364                                                 c = ch();
365                                         }
366                                         unch(c);
367                                         String dstr = dbuf.toString();
368                                         double dval;
369                                         try {
370                                                 dval = nf_json.parse(dstr).doubleValue();
371                                         } catch (ParseException pe) {
372                                                 return new JsonToken(JsonToken._error, dstr);
373                                         }
374                                         if (is_double || !is_long_range(dval))
375                                                 return new JsonToken(JsonToken._double, dval);
376                                         else {
377                                                 long lval = Long.parseLong(dstr);
378                                                 return new JsonToken(JsonToken._long, lval);
379                                         }
380                                 case '"':
381                                         StringBuffer bval = new StringBuffer();
382                                         for (;;) {
383                                                 c = ch();
384                                                 if (c == '"')
385                                                         break;
386                                                 if (c == '\\') {
387                                                         c = ch();
388                                                         switch (c) {
389                                                         case 'n':
390                                                                 c = '\n';
391                                                                 break;
392                                                         case 't':
393                                                                 c = '\t';
394                                                                 break;
395                                                         default:
396                                                                 break;
397                                                         }
398                                                 }
399                                                 bval.appendCodePoint(c);
400                                         }
401                                         return new JsonToken(JsonToken._string, bval);
402                                 default:
403                                         if (Character.isLetter(c)) {
404                                                 StringBuffer tbuf = new StringBuffer();
405                                                 do {
406                                                         tbuf.appendCodePoint(c);
407                                                         c = ch();
408                                                 } while (Character.isLetter(c));
409                                                 unch(c);
410                                                 JsonToken token = keyword(tbuf.toString());
411                                                 if (token != null)
412                                                         return token;
413                                         }
414                                         break;
415                                 }
416                         }
417                 } catch (IOException ie) {
418                         return new JsonToken(JsonToken._error, "<EIO>");
419                 }
420         }
421
422         void next() {
423                 token = lex();
424         }
425
426         JsonToken expect(int e) {
427                 JsonToken t = token;
428                 if (t.token != e)
429                         throw new IllegalArgumentException(String.format("got \"%s\" while expecting \"%s\"",
430                                                                          token.token_name(),
431                                                                          JsonToken.token_name(e)));
432                 next();
433                 return t;
434         }
435
436         JsonLexer(String s) {
437                 f = new StringReader(s);
438                 line = 1;
439                 token = null;
440         }
441 }
442
443 /*
444  * Parse a json string into a AltosJson object
445  */
446 class JsonParse {
447         JsonLexer       lexer;
448
449         void parse_error(String format, Object ... arguments) {
450                 throw new IllegalArgumentException(String.format("line %d: JSON parse error %s\n",
451                                                                  lexer.line,
452                                                                  String.format(format, arguments)));
453         }
454
455         /* Hashes are { string: value ... } */
456         JsonHash hash() {
457                 JsonHash        hash = new JsonHash();
458
459                 /* skip the open brace */
460                 lexer.next();
461                 for (;;) {
462                         /* Allow for empty hashes */
463                         if (lexer.token.token == JsonToken._cc) {
464                                 lexer.next();
465                                 return hash;
466                         }
467
468                         /* string : value */
469                         String key = lexer.expect(JsonToken._string).sval;
470                         lexer.expect(JsonToken._colon);
471                         AltosJson value = value();
472                         hash.put(key, value);
473
474                         switch (lexer.token.token) {
475                         case JsonToken._comma:
476                                 lexer.next();
477                                 break;
478                         case JsonToken._cc:
479                                 lexer.next();
480                                 return hash;
481                         default:
482                                 parse_error("got %s expect \",\" or \"}\"", lexer.token.token_name());
483                                 return null;
484                         }
485                 }
486         }
487
488         /* Arrays are [ value ... ] */
489         JsonArray array() {
490                 JsonArray       array = new JsonArray();
491
492                 lexer.next();
493                 for (int i = 0;; i++) {
494                         /* Allow for empty arrays */
495                         if (lexer.token.token == JsonToken._cs) {
496                                 lexer.next();
497                                 return array;
498                         }
499
500                         AltosJson value = value();
501                         array.put(i, value);
502                         switch (lexer.token.token) {
503                         case JsonToken._comma:
504                                 lexer.next();
505                                 break;
506                         case JsonToken._cs:
507                                 lexer.next();
508                                 return array;
509                         default:
510                                 parse_error("got %s expect \",\" or \"]\"", lexer.token.token_name());
511                                 return null;
512                         }
513                 }
514         }
515
516         /* Json is a simple LL language; one token is sufficient to
517          * identify the next object in the input
518          */
519         AltosJson value() {
520                 switch (lexer.token.token) {
521                 case JsonToken._oc:
522                         return new AltosJson(hash());
523                 case JsonToken._os:
524                         return new AltosJson(array());
525                 case JsonToken._double:
526                         double dval = lexer.token.dval;
527                         lexer.next();
528                         return new AltosJson(dval);
529                 case JsonToken._long:
530                         long lval = lexer.token.lval;
531                         lexer.next();
532                         return new AltosJson(lval);
533                 case JsonToken._string:
534                         String sval = lexer.token.sval;
535                         lexer.next();
536                         return new AltosJson(sval);
537                 case JsonToken._boolean:
538                         boolean bval = lexer.token.bval;
539                         lexer.next();
540                         return new AltosJson(bval);
541                 default:
542                         parse_error("Unexpected token \"%s\"", lexer.token.token_name());
543                 }
544                 return null;
545         }
546
547         AltosJson parse() {
548                 lexer.next();
549                 return value();
550         }
551
552         JsonParse(String s) {
553                 lexer = new JsonLexer(s);
554         }
555 }
556
557 public class AltosJson extends JsonUtil {
558         private static final int        type_none = 0;
559         private static final int        type_hash = 1;
560         private static final int        type_array = 2;
561         private static final int        type_double = 3;
562         private static final int        type_long = 4;
563         private static final int        type_string = 5;
564         private static final int        type_boolean = 6;
565
566         private int             type;
567
568         private JsonHash        hash;
569         private JsonArray       array;
570         private double          d_number;
571         private long            l_number;
572         private String          string;
573         private boolean         bool;
574
575         /* Generate string representation of the value
576          */
577         StringBuffer append(StringBuffer result, int indent, boolean pretty) {
578                 switch (type) {
579                 case type_hash:
580                         hash.append_hash(result, indent, pretty);
581                         break;
582                 case type_array:
583                         array.append_array(result, indent, pretty);
584                         break;
585                 case type_double:
586                         String dval = nf_json.format(d_number);
587                         if (dval.equals("-0"))
588                                 dval = "0";
589                         result.append(dval);
590                         break;
591                 case type_long:
592                         result.append(new Long(l_number).toString());
593                         break;
594                 case type_string:
595                         quote(result, string);
596                         break;
597                 case type_boolean:
598                         result.append(bool ? "true" : "false");
599                         break;
600                 }
601                 return result;
602         }
603
604         private String toString(int indent, boolean pretty) {
605                 StringBuffer result = new StringBuffer();
606                 append(result, indent, pretty);
607                 return result.toString();
608         }
609
610         public String toString() {
611                 return toString(0, false);
612         }
613
614         public String toPrettyString() {
615                 return toString(0, true);
616         }
617
618         /* Parse string representation to a value
619          */
620
621         public static AltosJson fromString(String string) {
622                 JsonParse       parse = new JsonParse(string);
623                 try {
624                         return parse.parse();
625                 } catch (IllegalArgumentException ie) {
626                         System.out.printf("json:\n%s\n%s\n", string, ie.getMessage());
627                         return null;
628                 }
629         }
630
631         /* Accessor functions
632          */
633         private boolean assert_type(boolean setting, int type, int other_type, String error) {
634                 if (setting && this.type == type_none) {
635                         this.type = type;
636                         return false;
637                 }
638                 if (this.type != type && this.type != other_type)
639                         throw new IllegalArgumentException(error);
640                 return true;
641         }
642
643         private boolean assert_type(boolean setting, int type, String error) {
644                 return assert_type(setting, type, type, error);
645         }
646
647         private void assert_hash(boolean setting) {
648                 if (!assert_type(setting, type_hash, "not a hash"))
649                         hash = new JsonHash();
650         }
651
652         private void assert_array(boolean setting) {
653                 if (!assert_type(setting, type_array, "not an array"))
654                         array = new JsonArray();
655         }
656
657         private void assert_number() {
658                 assert_type(false, type_double, type_long, "not a number");
659         }
660
661         private void assert_double() {
662                 assert_type(true, type_double, type_long, "not a number");
663         }
664
665         private void assert_long() {
666                 assert_type(true, type_long, type_double, "not a number");
667         }
668
669         private void assert_string(boolean setting) {
670                 assert_type(setting, type_string, "not a string");
671         }
672
673         private void assert_boolean(boolean setting) {
674                 assert_type(setting, type_boolean, "not a boolean");
675         }
676
677         /* Primitive accessors
678          */
679         public double number() {
680                 assert_number();
681                 if (type == type_double)
682                         return d_number;
683                 else
684                         return (double) l_number;
685         }
686
687         public long l_number() {
688                 assert_number();
689                 if (type == type_double)
690                         return (long) d_number;
691                 else
692                         return l_number;
693         }
694
695         public String string() {
696                 assert_string(false);
697                 return string;
698         }
699
700         public boolean bool() {
701                 assert_boolean(false);
702                 return bool;
703         }
704
705         public AltosJson get(int index) {
706                 assert_array(false);
707                 return array.get(index);
708         }
709
710         public AltosJson get(String key) {
711                 assert_hash(false);
712                 return hash.get(key);
713         }
714
715         public int size() {
716                 assert_array(false);
717                 return array.size();
718         }
719
720         /* Typed accessors with defaulting
721          */
722         public double get_double(String key, double def) {
723                 AltosJson value = get(key);
724                 if (value != null) {
725                         return value.number();
726                 }
727                 return def;
728         }
729
730         public long get_long(String key, long def) {
731                 AltosJson value = get(key);
732                 if (value != null)
733                         return value.l_number();
734                 return def;
735         }
736
737         public int get_int(String key, int def) {
738                 AltosJson value = get(key);
739                 if (value != null)
740                         return (int) value.l_number();
741                 return def;
742         }
743
744         public String get_string(String key, String def) {
745                 AltosJson value = get(key);
746                 if (value != null)
747                         return value.string();
748                 return def;
749         }
750
751         public boolean get_boolean(String key, boolean def) {
752                 AltosJson value = get(key);
753                 if (value != null)
754                         return value.bool();
755                 return def;
756         }
757
758         public double get_double(int index, double def) {
759                 AltosJson value = get(index);
760                 if (value != null)
761                         return value.number();
762                 return def;
763         }
764
765         public long get_long(int index, long def) {
766                 AltosJson value = get(index);
767                 if (value != null)
768                         return value.l_number();
769                 return def;
770         }
771
772         public int get_int(int index, int def) {
773                 AltosJson value = get(index);
774                 if (value != null)
775                         return (int) value.l_number();
776                 return def;
777         }
778
779         public String get_string(int index, String def) {
780                 AltosJson value = get(index);
781                 if (value != null)
782                         return value.string();
783                 return def;
784         }
785
786         public boolean get_boolean(int index, boolean def) {
787                 AltosJson value = get(index);
788                 if (value != null)
789                         return value.bool();
790                 return def;
791         }
792
793         public double[] get_double_array(String key, double[] def) {
794                 AltosJson value = get(key);
795                 if (value != null) {
796                         double[] ret = new double[value.size()];
797                         for (int i = 0; i < value.size(); i++)
798                                 ret[i] = value.get_double(i, def == null ? 0 : def[i]);
799                         return ret;
800                 }
801                 return def;
802         }
803
804         public int[] get_int_array(String key, int[] def) {
805                 AltosJson value = get(key);
806                 if (value != null) {
807                         int[] ret = new int[value.size()];
808                         for (int i = 0; i < value.size(); i++)
809                                 ret[i] = value.get_int(i, def == null ? 0 : def[i]);
810                         return ret;
811                 }
812                 return def;
813         }
814
815         /* Array setter functions
816          */
817         public AltosJson put(int index, AltosJson value) {
818                 assert_array(true);
819                 array.put(index, value);
820                 return value;
821         }
822
823         public Object put(int index, Object value) {
824                 assert_array(true);
825                 if (value != null)
826                         array.put(index, new AltosJson(value));
827                 return value;
828         }
829
830         public double put(int index, double value) {
831                 assert_array(true);
832                 array.put(index, new AltosJson(value));
833                 return value;
834         }
835
836         public AltosJson put(int index, double[] value) {
837                 if (value != null) {
838                         assert_array(true);
839                         array.put(index, new AltosJson(value));
840                 }
841                 return this;
842         }
843
844         public int[] put(int index, int[] value) {
845                 if (value != null) {
846                         assert_array(true);
847                         array.put(index, new AltosJson(value));
848                 }
849                 return value;
850         }
851
852         public String put(int index, String value) {
853                 if (value != null) {
854                         assert_array(true);
855                         array.put(index, new AltosJson(value));
856                 }
857                 return value;
858         }
859
860         public boolean put(int index, boolean value) {
861                 assert_array(true);
862                 array.put(index, new AltosJson(value));
863                 return value;
864         }
865
866         /* Hash setter functions
867          */
868         public AltosJson put(String key, AltosJson value) {
869                 assert_hash(true);
870                 hash.put(key, value);
871                 return value;
872         }
873
874         public Object put(String key, Object value) {
875                 assert_hash(true);
876                 if (value != null)
877                         hash.put(key, new AltosJson(value));
878                 return value;
879         }
880
881         public double put(String key, double value) {
882                 assert_hash(true);
883                 hash.put(key, new AltosJson(value));
884                 return value;
885         }
886
887         public String put(String key, String value) {
888                 if (value != null) {
889                         assert_hash(true);
890                         hash.put(key, new AltosJson(value));
891                 }
892                 return value;
893         }
894
895         public boolean put(String key, boolean value) {
896                 assert_hash(true);
897                 hash.put(key, new AltosJson(value));
898                 return value;
899         }
900
901         public AltosJson[] put(String key, AltosJson[] value) {
902                 if (value != null) {
903                         assert_hash(true);
904                         hash.put(key, new AltosJson(value));
905                 }
906                 return value;
907         }
908
909         public double[] put(String key, double[] value) {
910                 if (value != null) {
911                         assert_hash(true);
912                         hash.put(key, new AltosJson(value));
913                 }
914                 return value;
915         }
916
917         public int[] put(String key, int[] value) {
918                 if (value != null) {
919                         assert_hash(true);
920                         hash.put(key, new AltosJson(value));
921                 }
922                 return value;
923         }
924
925         /* Primitive setter functions
926          */
927         public double put(double value) {
928                 assert_double();
929                 d_number = value;
930                 return value;
931         }
932
933         public byte put(byte value) {
934                 assert_long();
935                 l_number = value;
936                 return value;
937         }
938
939         public char put(char value) {
940                 assert_long();
941                 l_number = value;
942                 return value;
943         }
944
945         public int put(int value) {
946                 assert_long();
947                 l_number = value;
948                 return value;
949         }
950
951         public long put(long value) {
952                 assert_long();
953                 l_number = value;
954                 return value;
955         }
956
957         public String put(String value) {
958                 assert_string(true);
959                 string = value;
960                 return value;
961         }
962
963         public boolean put(boolean value) {
964                 assert_boolean(true);
965                 bool = value;
966                 return value;
967         }
968
969         private boolean isInnerClass(Class c) {
970                 for (Field field : c.getDeclaredFields())
971                         if (field.isSynthetic())
972                                 return true;
973                 return false;
974         }
975
976         /* Construct an object of the specified class from the JSON
977          * representation.
978          *
979          * This works as long as the structure is non-recursive, and
980          * all inner classes are only members of their immediate outer
981          * class
982          */
983         private Object make(Class c, Class enclosing_class, Object enclosing_object) {
984                 Object  ret;
985                 if (c == Boolean.TYPE) {
986                         ret = bool();
987                 } else if (c == Byte.TYPE) {
988                         ret = (Byte) (byte) l_number();
989                 } else if (c == Character.TYPE) {
990                         ret = (Character) (char) l_number();
991                 } else if (c == Integer.TYPE) {
992                         ret = (Integer) (int) l_number();
993                 } else if (c == Long.TYPE) {
994                         ret = l_number();
995                 } else if (c == Double.TYPE) {
996                         ret = number();
997                 } else if (c == String.class) {
998                         ret = string();
999                 } else if (c.isArray()) {
1000                         assert_array(false);
1001
1002                         Class element_class = c.getComponentType();
1003                         if (element_class == Double.TYPE) {
1004                                 double[] array = (double[]) Array.newInstance(element_class, size());
1005                                 for (int i = 0; i < array.length; i++)
1006                                         array[i] = (Double) get(i).make(element_class);
1007                                 ret = array;
1008                         } else {
1009                                 Object[] array = (Object[]) Array.newInstance(element_class, size());
1010                                 for (int i = 0; i < array.length; i++)
1011                                         array[i] = get(i).make(element_class);
1012                                 ret = array;
1013                         }
1014                 } else {
1015                         assert_hash(false);
1016                         Object object = null;
1017                         try {
1018                                 /* Inner classes have a hidden extra parameter
1019                                  * to the constructor. Assume that the enclosing object is
1020                                  * of the enclosing class and construct the object
1021                                  * based on that.
1022                                  */
1023                                 if (enclosing_class != null && isInnerClass(c)) {
1024                                         Constructor<?> ctor = ((Class<?>)c).getDeclaredConstructor((Class<?>) enclosing_class);
1025                                         object = ctor.newInstance(enclosing_object);
1026                                 } else {
1027                                         object = c.newInstance();
1028                                 }
1029                                 for (; c != null; c = c.getSuperclass()) {
1030                                         for (Field field : c.getDeclaredFields()) {
1031                                                 String  fieldName = field.getName();
1032                                                 Class   fieldClass = field.getType();
1033                                                 String  className = fieldClass.getName();
1034
1035                                                 if (Modifier.isStatic(field.getModifiers()))
1036                                                         continue;
1037                                                 if (field.isSynthetic())
1038                                                         continue;
1039                                                 try {
1040                                                         AltosJson json = get(fieldName);
1041                                                         if (json != null) {
1042                                                                 Object val = json.make(fieldClass, c, object);
1043                                                                 field.setAccessible(true);
1044                                                                 field.set(object, val);
1045                                                         }
1046                                                 } catch (IllegalAccessException ie) {
1047                                                 }
1048                                         }
1049                                 }
1050                                 ret = object;
1051                         } catch (InvocationTargetException ie) {
1052                                 ret = null;
1053                         } catch (NoSuchMethodException ie) {
1054                                 ret = null;
1055                         } catch (InstantiationException ie) {
1056                                 ret = null;
1057                         } catch (IllegalAccessException ie) {
1058                                 ret = null;
1059                         }
1060                 }
1061                 return ret;
1062         }
1063
1064         /* This is the public API for the
1065          * above function which doesn't handle
1066          * inner classes
1067          */
1068         public Object make(Class c) {
1069                 return make(c, null, null);
1070         }
1071
1072         /* Constructors, one for each primitive type, String and Object */
1073         public AltosJson(boolean bool) {
1074                 type = type_boolean;
1075                 this.bool = bool;
1076         }
1077
1078         public AltosJson(byte number) {
1079                 type = type_long;
1080                 this.l_number = number;
1081         }
1082
1083         public AltosJson(char number) {
1084                 type = type_long;
1085                 this.l_number = number;
1086         }
1087
1088         public AltosJson(int number) {
1089                 type = type_long;
1090                 this.l_number = number;
1091         }
1092
1093         public AltosJson(long number) {
1094                 type = type_long;
1095                 this.l_number = number;
1096         }
1097
1098         public AltosJson(double number) {
1099                 type = type_double;
1100                 this.d_number = number;
1101         }
1102
1103         public AltosJson(String string) {
1104                 type = type_string;
1105                 this.string = string;
1106         }
1107
1108         public AltosJson(Object object) {
1109                 if (object instanceof Boolean) {
1110                         type = type_boolean;
1111                         bool = (Boolean) object;
1112                 } else if (object instanceof Byte) {
1113                         type = type_long;
1114                         l_number = (Byte) object;
1115                 } else if (object instanceof Character) {
1116                         type = type_long;
1117                         l_number = (Character) object;
1118                 } else if (object instanceof Integer) {
1119                         type = type_long;
1120                         l_number = (Integer) object;
1121                 } else if (object instanceof Long) {
1122                         type = type_long;
1123                         l_number = (Long) object;
1124                 } else if (object instanceof Double) {
1125                         type = type_double;
1126                         d_number = (Double) object;
1127                 } else if (object instanceof String) {
1128                         type = type_string;
1129                         string = (String) object;
1130                 } else if (object.getClass().isArray()) {
1131                         assert_array(true);
1132
1133                         Class component_class = object.getClass().getComponentType();
1134                         if (component_class == Boolean.TYPE) {
1135                                 boolean[] array = (boolean[]) object;
1136                                 for (int i = 0; i < array.length; i++)
1137                                         put(i, new AltosJson(array[i]));
1138                         } else if (component_class == Byte.TYPE) {
1139                                 byte[] array = (byte[]) object;
1140                                 for (int i = 0; i < array.length; i++)
1141                                         put(i, new AltosJson(array[i]));
1142                         } else if (component_class == Character.TYPE) {
1143                                 char[] array = (char[]) object;
1144                                 for (int i = 0; i < array.length; i++)
1145                                         put(i, new AltosJson(array[i]));
1146                         } else if (component_class == Integer.TYPE) {
1147                                 int[] array = (int[]) object;
1148                                 for (int i = 0; i < array.length; i++)
1149                                         put(i, new AltosJson(array[i]));
1150                         } else if (component_class == Long.TYPE) {
1151                                 long[] array = (long[]) object;
1152                                 for (int i = 0; i < array.length; i++)
1153                                         put(i, new AltosJson(array[i]));
1154                         } else if (component_class == Double.TYPE) {
1155                                 double[] array = (double[]) object;
1156                                 for (int i = 0; i < array.length; i++)
1157                                         put(i, new AltosJson(array[i]));
1158                         } else {
1159                                 Object[] array = (Object[]) object;
1160                                 for (int i = 0; i < array.length; i++)
1161                                         put(i, new AltosJson(array[i]));
1162                         }
1163                 } else {
1164                         assert_hash(true);
1165                         for (Class c = object.getClass(); c != null; c = c.getSuperclass()) {
1166                                 for (Field field : c.getDeclaredFields()) {
1167                                         String  fieldName = field.getName();
1168                                         Class   fieldClass = field.getType();
1169                                         String  className = fieldClass.getName();
1170
1171                                         /* Skip static fields */
1172                                         if (Modifier.isStatic(field.getModifiers()))
1173                                                 continue;
1174
1175                                         /* Skip synthetic fields. We're assuming
1176                                          * those are always an inner class reference
1177                                          * to the outer class object
1178                                          */
1179                                         if (field.isSynthetic())
1180                                                 continue;
1181                                         try {
1182                                                 /* We may need to force the field to be accessible if
1183                                                  * it is private
1184                                                  */
1185                                                 field.setAccessible(true);
1186                                                 Object val = field.get(object);
1187                                                 if (val != null) {
1188                                                         AltosJson json = new AltosJson(val);
1189                                                         put(fieldName, json);
1190                                                 }
1191                                         } catch (IllegalAccessException ie) {
1192                                         }
1193                                 }
1194                         }
1195                 }
1196         }
1197
1198         /* Array constructors, one for each primitive type, String and Object */
1199         public AltosJson(boolean[] bools) {
1200                 assert_array(true);
1201                 for(int i = 0; i < bools.length; i++)
1202                         put(i, new AltosJson(bools[i]));
1203         }
1204
1205         public AltosJson(byte[] numbers) {
1206                 assert_array(true);
1207                 for(int i = 0; i < numbers.length; i++)
1208                         put(i, new AltosJson(numbers[i]));
1209         }
1210
1211         public AltosJson(char[] numbers) {
1212                 assert_array(true);
1213                 for(int i = 0; i < numbers.length; i++)
1214                         put(i, new AltosJson(numbers[i]));
1215         }
1216
1217         public AltosJson(int[] numbers) {
1218                 assert_array(true);
1219                 for(int i = 0; i < numbers.length; i++)
1220                         put(i, new AltosJson(numbers[i]));
1221         }
1222
1223         public AltosJson(long[] numbers) {
1224                 assert_array(true);
1225                 for(int i = 0; i < numbers.length; i++)
1226                         put(i, new AltosJson(numbers[i]));
1227         }
1228
1229         public AltosJson(double[] numbers) {
1230                 assert_array(true);
1231                 for(int i = 0; i < numbers.length; i++)
1232                         put(i, new AltosJson(numbers[i]));
1233         }
1234
1235         public AltosJson(String[] strings) {
1236                 assert_array(true);
1237                 for(int i = 0; i < strings.length; i++)
1238                         put(i, new AltosJson(strings[i]));
1239         }
1240
1241         public AltosJson(AltosJson[] jsons) {
1242                 assert_array(true);
1243                 for (int i = 0; i < jsons.length; i++)
1244                         put(i, jsons[i]);
1245         }
1246
1247         public AltosJson(Object[] array) {
1248                 assert_array(true);
1249                 for (int i = 0; i < array.length; i++)
1250                         put(i, new AltosJson(array[i]));
1251         }
1252
1253         /* Empty constructor
1254          */
1255         public AltosJson() {
1256                 type = type_none;
1257         }
1258
1259         public AltosJson(JsonHash hash) {
1260                 type = type_hash;
1261                 this.hash = hash;
1262         }
1263
1264         public AltosJson(JsonArray array) {
1265                 type = type_array;
1266                 this.array = array;
1267         }
1268 }