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