2 * Copyright © 2016 Keith Packard <keithp@keithp.com>
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.
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.
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.
18 package org.altusmetrum.altoslib_11;
24 import java.lang.reflect.*;
27 StringBuffer quote(StringBuffer result, String a) {
29 for (int i = 0; i < a.length(); i++) {
35 result.append('\\').append(c);
49 StringBuffer append(StringBuffer result, AltosJson value, int indent, boolean pretty) {
50 value.append(result, indent, pretty);
54 StringBuffer append(StringBuffer result, String string) {
55 result.append(string);
59 StringBuffer indent(StringBuffer result, int indent) {
61 for (int i = 0; i < indent; i++)
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);
76 static NumberFormat nf_json = get_nf_json();
79 class JsonHash extends JsonUtil {
80 Hashtable<String,AltosJson> hash;
82 void append_hash(StringBuffer result, int indent, boolean pretty) {
87 ArrayList<String> key_list = new ArrayList<String>(hash.keySet());
89 Collections.sort(key_list, new Comparator<String>() {
91 public int compare(String a, String b) { return a.compareTo(b); }
94 for (String key : key_list) {
95 AltosJson value = hash.get(key);
101 indent(result, indent+1);
103 append(result, ": ");
104 append(result, value, indent+1, pretty);
107 indent(result, indent);
111 void put(String key, AltosJson value) {
112 hash.put(key, value);
115 AltosJson get(String key) {
116 return hash.get(key);
120 hash = new Hashtable<String,AltosJson>();
124 class JsonArray extends JsonUtil {
125 ArrayList<AltosJson> array;
127 void append_array(StringBuffer result, int indent, boolean pretty) {
128 boolean first = true;
131 for (int i = 0; i < array.size(); i++) {
132 AltosJson value = array.get(i);
138 indent(result, indent+1);
139 append(result, value, indent+1, pretty);
142 indent(result, indent);
146 void put(int index, AltosJson value) {
147 if (index >= array.size())
148 array.add(index, value);
150 array.set(index, value);
153 AltosJson get(int index) {
154 if (index < 0 || index > array.size())
156 return array.get(index);
164 array = new ArrayList<AltosJson>();
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;
188 static String token_name(int token) {
219 String token_name() {
220 return token_name(token);
223 JsonToken(int token) {
227 JsonToken(int token, boolean bval) {
232 JsonToken(int token, double dval) {
237 JsonToken(int token, long lval) {
242 JsonToken(int token, String sval) {
247 JsonToken(int token, StringBuffer bval) {
248 this(token, bval.toString());
255 class JsonLexer extends JsonUtil {
259 StringBuffer pending_token;
262 static class keyword {
266 JsonToken match(String value) {
267 if (word.equals(value))
272 keyword(String word, JsonToken token) {
278 /* boolean values are the only keywords in json
280 static keyword[] keywords = {
281 new keyword("true", new JsonToken(JsonToken._boolean, true)),
282 new keyword("false", new JsonToken(JsonToken._boolean, false)),
283 new keyword("NegInfinity", new JsonToken(JsonToken._double, Double.NEGATIVE_INFINITY)),
284 new keyword("Infinity", new JsonToken(JsonToken._double, Double.POSITIVE_INFINITY)),
285 new keyword("NaN", new JsonToken(JsonToken._double, Double.NaN))
288 static JsonToken keyword(String word) {
289 for (int i = 0; i < keywords.length; i++) {
290 JsonToken token = keywords[i].match(word);
297 /* Get the next char (-1 for EOF) */
298 int ch() throws IOException {
306 pending_token.append((char) c);
314 throw new IllegalArgumentException("ungot buffer full");
315 pending_token.deleteCharAt( pending_token.length()-1);
321 String last_token_string() {
322 if (pending_token == null)
325 return pending_token.toString();
328 static boolean is_long_range(double d) {
329 return -9223372036854775808.0 <= d && d <= 9223372036854775807.0;
333 pending_token = new StringBuffer();
341 return new JsonToken(JsonToken._end);
347 return new JsonToken(JsonToken._oc);
349 return new JsonToken(JsonToken._cc);
351 return new JsonToken(JsonToken._os);
353 return new JsonToken(JsonToken._cs);
355 return new JsonToken(JsonToken._comma);
357 return new JsonToken(JsonToken._colon);
358 case '0': case '1': case '2': case '3': case '4':
359 case '5': case '6': case '7': case '8': case '9':
360 case '.': case '-': case '+':
361 StringBuffer dbuf = new StringBuffer();
362 boolean is_double = false;
363 while (Character.isDigit(c) || c == '.' || c == '+' || c == '-' || c == 'e' || c == 'E') {
364 if (c == '.' || c == 'E')
366 dbuf.appendCodePoint(c);
370 String dstr = dbuf.toString();
373 dval = nf_json.parse(dstr).doubleValue();
374 } catch (ParseException pe) {
375 return new JsonToken(JsonToken._error, dstr);
377 if (is_double || !is_long_range(dval))
378 return new JsonToken(JsonToken._double, dval);
380 long lval = Long.parseLong(dstr);
381 return new JsonToken(JsonToken._long, lval);
384 StringBuffer bval = new StringBuffer();
402 bval.appendCodePoint(c);
404 return new JsonToken(JsonToken._string, bval);
406 if (Character.isLetter(c)) {
407 StringBuffer tbuf = new StringBuffer();
409 tbuf.appendCodePoint(c);
411 } while (Character.isLetter(c));
413 JsonToken token = keyword(tbuf.toString());
420 } catch (IOException ie) {
421 return new JsonToken(JsonToken._error, "<EIO>");
429 JsonToken expect(int e) {
432 throw new IllegalArgumentException(String.format("got \"%s\" while expecting \"%s\"",
434 JsonToken.token_name(e)));
439 JsonLexer(String s) {
440 f = new StringReader(s);
447 * Parse a json string into a AltosJson object
452 void parse_error(String format, Object ... arguments) {
453 throw new IllegalArgumentException(String.format("line %d: JSON parse error %s\n",
455 String.format(format, arguments)));
458 /* Hashes are { string: value ... } */
460 JsonHash hash = new JsonHash();
462 /* skip the open brace */
465 /* Allow for empty hashes */
466 if (lexer.token.token == JsonToken._cc) {
472 String key = lexer.expect(JsonToken._string).sval;
473 lexer.expect(JsonToken._colon);
474 AltosJson value = value();
475 hash.put(key, value);
477 switch (lexer.token.token) {
478 case JsonToken._comma:
485 parse_error("got %s expect \",\" or \"}\"", lexer.token.token_name());
491 /* Arrays are [ value ... ] */
493 JsonArray array = new JsonArray();
496 for (int i = 0;; i++) {
497 /* Allow for empty arrays */
498 if (lexer.token.token == JsonToken._cs) {
503 AltosJson value = value();
505 switch (lexer.token.token) {
506 case JsonToken._comma:
513 parse_error("got %s expect \",\" or \"]\"", lexer.token.token_name());
519 /* Json is a simple LL language; one token is sufficient to
520 * identify the next object in the input
523 switch (lexer.token.token) {
525 return new AltosJson(hash());
527 return new AltosJson(array());
528 case JsonToken._double:
529 double dval = lexer.token.dval;
531 return new AltosJson(dval);
532 case JsonToken._long:
533 long lval = lexer.token.lval;
535 return new AltosJson(lval);
536 case JsonToken._string:
537 String sval = lexer.token.sval;
539 return new AltosJson(sval);
540 case JsonToken._boolean:
541 boolean bval = lexer.token.bval;
543 return new AltosJson(bval);
545 parse_error("Unexpected token \"%s\"", lexer.token.token_name());
555 JsonParse(String s) {
556 lexer = new JsonLexer(s);
560 public class AltosJson extends JsonUtil {
561 private static final int type_none = 0;
562 private static final int type_hash = 1;
563 private static final int type_array = 2;
564 private static final int type_double = 3;
565 private static final int type_long = 4;
566 private static final int type_string = 5;
567 private static final int type_boolean = 6;
571 private JsonHash hash;
572 private JsonArray array;
573 private double d_number;
574 private long l_number;
575 private String string;
576 private boolean bool;
578 /* Generate string representation of the value
580 StringBuffer append(StringBuffer result, int indent, boolean pretty) {
583 hash.append_hash(result, indent, pretty);
586 array.append_array(result, indent, pretty);
589 if (Double.isInfinite(d_number)) {
591 result.append("NegInfinity");
593 result.append("Infinity");
594 } else if (Double.isNaN(d_number)) {
595 result.append("NaN");
597 String dval = nf_json.format(d_number);
598 if (dval.equals("-0"))
604 result.append(new Long(l_number).toString());
607 quote(result, string);
610 result.append(bool ? "true" : "false");
616 private String toString(int indent, boolean pretty) {
617 StringBuffer result = new StringBuffer();
618 append(result, indent, pretty);
619 return result.toString();
622 public String toString() {
623 return toString(0, false);
626 public String toPrettyString() {
627 return toString(0, true);
630 /* Parse string representation to a value
633 public static AltosJson fromString(String string) {
634 JsonParse parse = new JsonParse(string);
636 return parse.parse();
637 } catch (IllegalArgumentException ie) {
638 System.out.printf("json:\n%s\n%s\n", string, ie.getMessage());
643 /* Accessor functions
645 private boolean assert_type(boolean setting, int type, int other_type, String error) {
646 if (setting && this.type == type_none) {
650 if (this.type != type && this.type != other_type)
651 throw new IllegalArgumentException(error);
655 private boolean assert_type(boolean setting, int type, String error) {
656 return assert_type(setting, type, type, error);
659 private void assert_hash(boolean setting) {
660 if (!assert_type(setting, type_hash, "not a hash"))
661 hash = new JsonHash();
664 private void assert_array(boolean setting) {
665 if (!assert_type(setting, type_array, "not an array"))
666 array = new JsonArray();
669 private void assert_number() {
670 assert_type(false, type_double, type_long, "not a number");
673 private void assert_double() {
674 assert_type(true, type_double, type_long, "not a number");
677 private void assert_long() {
678 assert_type(true, type_long, type_double, "not a number");
681 private void assert_string(boolean setting) {
682 assert_type(setting, type_string, "not a string");
685 private void assert_boolean(boolean setting) {
686 assert_type(setting, type_boolean, "not a boolean");
689 /* Primitive accessors
691 public double number() {
693 if (type == type_double)
696 return (double) l_number;
699 public long l_number() {
701 if (type == type_double)
702 return (long) d_number;
707 public String string() {
708 assert_string(false);
712 public boolean bool() {
713 assert_boolean(false);
717 public AltosJson get(int index) {
719 return array.get(index);
722 public AltosJson get(String key) {
724 return hash.get(key);
732 /* Typed accessors with defaulting
734 public double get_double(String key, double def) {
735 AltosJson value = get(key);
737 return value.number();
742 public long get_long(String key, long def) {
743 AltosJson value = get(key);
745 return value.l_number();
749 public int get_int(String key, int def) {
750 AltosJson value = get(key);
752 return (int) value.l_number();
756 public String get_string(String key, String def) {
757 AltosJson value = get(key);
759 return value.string();
763 public boolean get_boolean(String key, boolean def) {
764 AltosJson value = get(key);
770 public double get_double(int index, double def) {
771 AltosJson value = get(index);
773 return value.number();
777 public long get_long(int index, long def) {
778 AltosJson value = get(index);
780 return value.l_number();
784 public int get_int(int index, int def) {
785 AltosJson value = get(index);
787 return (int) value.l_number();
791 public String get_string(int index, String def) {
792 AltosJson value = get(index);
794 return value.string();
798 public boolean get_boolean(int index, boolean def) {
799 AltosJson value = get(index);
805 public double[] get_double_array(String key, double[] def) {
806 AltosJson value = get(key);
808 double[] ret = new double[value.size()];
809 for (int i = 0; i < value.size(); i++)
810 ret[i] = value.get_double(i, def == null ? 0 : def[i]);
816 public int[] get_int_array(String key, int[] def) {
817 AltosJson value = get(key);
819 int[] ret = new int[value.size()];
820 for (int i = 0; i < value.size(); i++)
821 ret[i] = value.get_int(i, def == null ? 0 : def[i]);
827 /* Array setter functions
829 public AltosJson put(int index, AltosJson value) {
831 array.put(index, value);
835 public Object put(int index, Object value) {
838 array.put(index, new AltosJson(value));
842 public double put(int index, double value) {
844 array.put(index, new AltosJson(value));
848 public AltosJson put(int index, double[] value) {
851 array.put(index, new AltosJson(value));
856 public int[] put(int index, int[] value) {
859 array.put(index, new AltosJson(value));
864 public String put(int index, String value) {
867 array.put(index, new AltosJson(value));
872 public boolean put(int index, boolean value) {
874 array.put(index, new AltosJson(value));
878 /* Hash setter functions
880 public AltosJson put(String key, AltosJson value) {
882 hash.put(key, value);
886 public Object put(String key, Object value) {
889 hash.put(key, new AltosJson(value));
893 public double put(String key, double value) {
895 hash.put(key, new AltosJson(value));
899 public String put(String key, String value) {
902 hash.put(key, new AltosJson(value));
907 public boolean put(String key, boolean value) {
909 hash.put(key, new AltosJson(value));
913 public AltosJson[] put(String key, AltosJson[] value) {
916 hash.put(key, new AltosJson(value));
921 public double[] put(String key, double[] value) {
924 hash.put(key, new AltosJson(value));
929 public int[] put(String key, int[] value) {
932 hash.put(key, new AltosJson(value));
937 /* Primitive setter functions
939 public double put(double value) {
945 public byte put(byte value) {
951 public char put(char value) {
957 public int put(int value) {
963 public long put(long value) {
969 public String put(String value) {
975 public boolean put(boolean value) {
976 assert_boolean(true);
981 private boolean isInnerClass(Class c) {
982 for (Field field : c.getDeclaredFields())
983 if (field.isSynthetic())
988 /* Construct an object of the specified class from the JSON
991 * This works as long as the structure is non-recursive, and
992 * all inner classes are only members of their immediate outer
995 private Object make(Class c, Class enclosing_class, Object enclosing_object) {
997 if (c == Boolean.TYPE) {
999 } else if (c == Byte.TYPE) {
1000 ret = (Byte) (byte) l_number();
1001 } else if (c == Character.TYPE) {
1002 ret = (Character) (char) l_number();
1003 } else if (c == Integer.TYPE) {
1004 ret = (Integer) (int) l_number();
1005 } else if (c == Long.TYPE) {
1007 } else if (c == Double.TYPE) {
1009 } else if (c == String.class) {
1011 } else if (c.isArray()) {
1012 assert_array(false);
1014 Class element_class = c.getComponentType();
1015 if (element_class == Boolean.TYPE) {
1016 boolean[] array = (boolean[]) Array.newInstance(element_class, size());
1017 for (int i = 0; i < array.length; i++)
1018 array[i] = (Boolean) get(i).make(element_class);
1020 } else if (element_class == Byte.TYPE) {
1021 byte[] array = (byte[]) Array.newInstance(element_class, size());
1022 for (int i = 0; i < array.length; i++)
1023 array[i] = (Byte) get(i).make(element_class);
1025 } else if (element_class == Character.TYPE) {
1026 char[] array = (char[]) Array.newInstance(element_class, size());
1027 for (int i = 0; i < array.length; i++)
1028 array[i] = (Character) get(i).make(element_class);
1030 } else if (element_class == Integer.TYPE) {
1031 int[] array = (int[]) Array.newInstance(element_class, size());
1032 for (int i = 0; i < array.length; i++)
1033 array[i] = (Integer) get(i).make(element_class);
1035 } else if (element_class == Long.TYPE) {
1036 long[] array = (long[]) Array.newInstance(element_class, size());
1037 for (int i = 0; i < array.length; i++)
1038 array[i] = (Long) get(i).make(element_class);
1040 } else if (element_class == Double.TYPE) {
1041 double[] array = (double[]) Array.newInstance(element_class, size());
1042 for (int i = 0; i < array.length; i++)
1043 array[i] = (Double) get(i).make(element_class);
1046 Object[] array = (Object[]) Array.newInstance(element_class, size());
1047 for (int i = 0; i < array.length; i++)
1048 array[i] = get(i).make(element_class);
1053 Object object = null;
1055 /* Inner classes have a hidden extra parameter
1056 * to the constructor. Assume that the enclosing object is
1057 * of the enclosing class and construct the object
1060 if (enclosing_class != null && isInnerClass(c)) {
1061 Constructor<?> ctor = ((Class<?>)c).getDeclaredConstructor((Class<?>) enclosing_class);
1062 object = ctor.newInstance(enclosing_object);
1064 object = c.newInstance();
1066 for (; c != Object.class; c = c.getSuperclass()) {
1067 for (Field field : c.getDeclaredFields()) {
1068 String fieldName = field.getName();
1069 Class fieldClass = field.getType();
1071 if (Modifier.isStatic(field.getModifiers()))
1073 if (field.isSynthetic())
1076 AltosJson json = get(fieldName);
1078 Object val = json.make(fieldClass, c, object);
1079 field.setAccessible(true);
1080 field.set(object, val);
1082 } catch (IllegalAccessException ie) {
1083 System.out.printf("%s:%s %s\n",
1084 c.getName(), fieldName, ie.toString());
1089 } catch (InvocationTargetException ie) {
1090 System.out.printf("%s: %s\n",
1091 c.getName(), ie.toString());
1093 } catch (NoSuchMethodException ie) {
1094 System.out.printf("%s: %s\n",
1095 c.getName(), ie.toString());
1097 } catch (InstantiationException ie) {
1098 System.out.printf("%s: %s\n",
1099 c.getName(), ie.toString());
1101 } catch (IllegalAccessException ie) {
1102 System.out.printf("%s: %s\n",
1103 c.getName(), ie.toString());
1110 /* This is the public API for the
1111 * above function which doesn't handle
1114 public Object make(Class c) {
1115 return make(c, null, null);
1118 /* Constructors, one for each primitive type, String and Object */
1119 public AltosJson(boolean bool) {
1120 type = type_boolean;
1124 public AltosJson(byte number) {
1126 this.l_number = number;
1129 public AltosJson(char number) {
1131 this.l_number = number;
1134 public AltosJson(int number) {
1136 this.l_number = number;
1139 public AltosJson(long number) {
1141 this.l_number = number;
1144 public AltosJson(double number) {
1146 this.d_number = number;
1149 public AltosJson(String string) {
1151 this.string = string;
1154 public AltosJson(Object object) {
1155 if (object instanceof Boolean) {
1156 type = type_boolean;
1157 bool = (Boolean) object;
1158 } else if (object instanceof Byte) {
1160 l_number = (Byte) object;
1161 } else if (object instanceof Character) {
1163 l_number = (Character) object;
1164 } else if (object instanceof Integer) {
1166 l_number = (Integer) object;
1167 } else if (object instanceof Long) {
1169 l_number = (Long) object;
1170 } else if (object instanceof Double) {
1172 d_number = (Double) object;
1173 } else if (object instanceof String) {
1175 string = (String) object;
1176 } else if (object.getClass().isArray()) {
1179 Class component_class = object.getClass().getComponentType();
1180 if (component_class == Boolean.TYPE) {
1181 boolean[] array = (boolean[]) object;
1182 for (int i = 0; i < array.length; i++)
1183 put(i, new AltosJson(array[i]));
1184 } else if (component_class == Byte.TYPE) {
1185 byte[] array = (byte[]) object;
1186 for (int i = 0; i < array.length; i++)
1187 put(i, new AltosJson(array[i]));
1188 } else if (component_class == Character.TYPE) {
1189 char[] array = (char[]) object;
1190 for (int i = 0; i < array.length; i++)
1191 put(i, new AltosJson(array[i]));
1192 } else if (component_class == Integer.TYPE) {
1193 int[] array = (int[]) object;
1194 for (int i = 0; i < array.length; i++)
1195 put(i, new AltosJson(array[i]));
1196 } else if (component_class == Long.TYPE) {
1197 long[] array = (long[]) object;
1198 for (int i = 0; i < array.length; i++)
1199 put(i, new AltosJson(array[i]));
1200 } else if (component_class == Double.TYPE) {
1201 double[] array = (double[]) object;
1202 for (int i = 0; i < array.length; i++)
1203 put(i, new AltosJson(array[i]));
1205 Object[] array = (Object[]) object;
1206 for (int i = 0; i < array.length; i++)
1207 put(i, new AltosJson(array[i]));
1211 for (Class c = object.getClass(); c != Object.class; c = c.getSuperclass()) {
1212 for (Field field : c.getDeclaredFields()) {
1213 String fieldName = field.getName();
1215 /* Skip static fields */
1216 if (Modifier.isStatic(field.getModifiers()))
1219 /* Skip synthetic fields. We're assuming
1220 * those are always an inner class reference
1221 * to the outer class object
1223 if (field.isSynthetic())
1226 /* We may need to force the field to be accessible if
1229 field.setAccessible(true);
1230 Object val = field.get(object);
1232 AltosJson json = new AltosJson(val);
1233 put(fieldName, json);
1235 } catch (IllegalAccessException ie) {
1236 System.out.printf("%s:%s %s\n",
1237 c.getName(), fieldName, ie.toString());
1244 /* Array constructors, one for each primitive type, String and Object */
1245 public AltosJson(boolean[] bools) {
1247 for(int i = 0; i < bools.length; i++)
1248 put(i, new AltosJson(bools[i]));
1251 public AltosJson(byte[] numbers) {
1253 for(int i = 0; i < numbers.length; i++)
1254 put(i, new AltosJson(numbers[i]));
1257 public AltosJson(char[] numbers) {
1259 for(int i = 0; i < numbers.length; i++)
1260 put(i, new AltosJson(numbers[i]));
1263 public AltosJson(int[] numbers) {
1265 for(int i = 0; i < numbers.length; i++)
1266 put(i, new AltosJson(numbers[i]));
1269 public AltosJson(long[] numbers) {
1271 for(int i = 0; i < numbers.length; i++)
1272 put(i, new AltosJson(numbers[i]));
1275 public AltosJson(double[] numbers) {
1277 for(int i = 0; i < numbers.length; i++)
1278 put(i, new AltosJson(numbers[i]));
1281 public AltosJson(String[] strings) {
1283 for(int i = 0; i < strings.length; i++)
1284 put(i, new AltosJson(strings[i]));
1287 public AltosJson(AltosJson[] jsons) {
1289 for (int i = 0; i < jsons.length; i++)
1293 public AltosJson(Object[] array) {
1295 for (int i = 0; i < array.length; i++)
1296 put(i, new AltosJson(array[i]));
1299 /* Empty constructor
1301 public AltosJson() {
1305 public AltosJson(JsonHash hash) {
1310 public AltosJson(JsonArray array) {