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)),
285 static JsonToken keyword(String word) {
286 for (int i = 0; i < keywords.length; i++) {
287 JsonToken token = keywords[i].match(word);
294 /* Get the next char (-1 for EOF) */
295 int ch() throws IOException {
303 pending_token.append((char) c);
311 throw new IllegalArgumentException("ungot buffer full");
312 pending_token.deleteCharAt( pending_token.length()-1);
318 String last_token_string() {
319 if (pending_token == null)
322 return pending_token.toString();
325 static boolean is_long_range(double d) {
326 return -9223372036854775808.0 <= d && d <= 9223372036854775807.0;
330 pending_token = new StringBuffer();
338 return new JsonToken(JsonToken._end);
344 return new JsonToken(JsonToken._oc);
346 return new JsonToken(JsonToken._cc);
348 return new JsonToken(JsonToken._os);
350 return new JsonToken(JsonToken._cs);
352 return new JsonToken(JsonToken._comma);
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')
363 dbuf.appendCodePoint(c);
367 String dstr = dbuf.toString();
370 dval = nf_json.parse(dstr).doubleValue();
371 } catch (ParseException pe) {
372 return new JsonToken(JsonToken._error, dstr);
374 if (is_double || !is_long_range(dval))
375 return new JsonToken(JsonToken._double, dval);
377 long lval = Long.parseLong(dstr);
378 return new JsonToken(JsonToken._long, lval);
381 StringBuffer bval = new StringBuffer();
399 bval.appendCodePoint(c);
401 return new JsonToken(JsonToken._string, bval);
403 if (Character.isLetter(c)) {
404 StringBuffer tbuf = new StringBuffer();
406 tbuf.appendCodePoint(c);
408 } while (Character.isLetter(c));
410 JsonToken token = keyword(tbuf.toString());
417 } catch (IOException ie) {
418 return new JsonToken(JsonToken._error, "<EIO>");
426 JsonToken expect(int e) {
429 throw new IllegalArgumentException(String.format("got \"%s\" while expecting \"%s\"",
431 JsonToken.token_name(e)));
436 JsonLexer(String s) {
437 f = new StringReader(s);
444 * Parse a json string into a AltosJson object
449 void parse_error(String format, Object ... arguments) {
450 throw new IllegalArgumentException(String.format("line %d: JSON parse error %s\n",
452 String.format(format, arguments)));
455 /* Hashes are { string: value ... } */
457 JsonHash hash = new JsonHash();
459 /* skip the open brace */
462 /* Allow for empty hashes */
463 if (lexer.token.token == JsonToken._cc) {
469 String key = lexer.expect(JsonToken._string).sval;
470 lexer.expect(JsonToken._colon);
471 AltosJson value = value();
472 hash.put(key, value);
474 switch (lexer.token.token) {
475 case JsonToken._comma:
482 parse_error("got %s expect \",\" or \"}\"", lexer.token.token_name());
488 /* Arrays are [ value ... ] */
490 JsonArray array = new JsonArray();
493 for (int i = 0;; i++) {
494 /* Allow for empty arrays */
495 if (lexer.token.token == JsonToken._cs) {
500 AltosJson value = value();
502 switch (lexer.token.token) {
503 case JsonToken._comma:
510 parse_error("got %s expect \",\" or \"]\"", lexer.token.token_name());
516 /* Json is a simple LL language; one token is sufficient to
517 * identify the next object in the input
520 switch (lexer.token.token) {
522 return new AltosJson(hash());
524 return new AltosJson(array());
525 case JsonToken._double:
526 double dval = lexer.token.dval;
528 return new AltosJson(dval);
529 case JsonToken._long:
530 long lval = lexer.token.lval;
532 return new AltosJson(lval);
533 case JsonToken._string:
534 String sval = lexer.token.sval;
536 return new AltosJson(sval);
537 case JsonToken._boolean:
538 boolean bval = lexer.token.bval;
540 return new AltosJson(bval);
542 parse_error("Unexpected token \"%s\"", lexer.token.token_name());
552 JsonParse(String s) {
553 lexer = new JsonLexer(s);
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;
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;
575 /* Generate string representation of the value
577 StringBuffer append(StringBuffer result, int indent, boolean pretty) {
580 hash.append_hash(result, indent, pretty);
583 array.append_array(result, indent, pretty);
586 String dval = nf_json.format(d_number);
587 if (dval.equals("-0"))
592 result.append(new Long(l_number).toString());
595 quote(result, string);
598 result.append(bool ? "true" : "false");
604 private String toString(int indent, boolean pretty) {
605 StringBuffer result = new StringBuffer();
606 append(result, indent, pretty);
607 return result.toString();
610 public String toString() {
611 return toString(0, false);
614 public String toPrettyString() {
615 return toString(0, true);
618 /* Parse string representation to a value
621 public static AltosJson fromString(String string) {
622 JsonParse parse = new JsonParse(string);
624 return parse.parse();
625 } catch (IllegalArgumentException ie) {
626 System.out.printf("json:\n%s\n%s\n", string, ie.getMessage());
631 /* Accessor functions
633 private boolean assert_type(boolean setting, int type, int other_type, String error) {
634 if (setting && this.type == type_none) {
638 if (this.type != type && this.type != other_type)
639 throw new IllegalArgumentException(error);
643 private boolean assert_type(boolean setting, int type, String error) {
644 return assert_type(setting, type, type, error);
647 private void assert_hash(boolean setting) {
648 if (!assert_type(setting, type_hash, "not a hash"))
649 hash = new JsonHash();
652 private void assert_array(boolean setting) {
653 if (!assert_type(setting, type_array, "not an array"))
654 array = new JsonArray();
657 private void assert_number() {
658 assert_type(false, type_double, type_long, "not a number");
661 private void assert_double() {
662 assert_type(true, type_double, type_long, "not a number");
665 private void assert_long() {
666 assert_type(true, type_long, type_double, "not a number");
669 private void assert_string(boolean setting) {
670 assert_type(setting, type_string, "not a string");
673 private void assert_boolean(boolean setting) {
674 assert_type(setting, type_boolean, "not a boolean");
677 /* Primitive accessors
679 public double number() {
681 if (type == type_double)
684 return (double) l_number;
687 public long l_number() {
689 if (type == type_double)
690 return (long) d_number;
695 public String string() {
696 assert_string(false);
700 public boolean bool() {
701 assert_boolean(false);
705 public AltosJson get(int index) {
707 return array.get(index);
710 public AltosJson get(String key) {
712 return hash.get(key);
720 /* Typed accessors with defaulting
722 public double get_double(String key, double def) {
723 AltosJson value = get(key);
725 return value.number();
730 public long get_long(String key, long def) {
731 AltosJson value = get(key);
733 return value.l_number();
737 public int get_int(String key, int def) {
738 AltosJson value = get(key);
740 return (int) value.l_number();
744 public String get_string(String key, String def) {
745 AltosJson value = get(key);
747 return value.string();
751 public boolean get_boolean(String key, boolean def) {
752 AltosJson value = get(key);
758 public double get_double(int index, double def) {
759 AltosJson value = get(index);
761 return value.number();
765 public long get_long(int index, long def) {
766 AltosJson value = get(index);
768 return value.l_number();
772 public int get_int(int index, int def) {
773 AltosJson value = get(index);
775 return (int) value.l_number();
779 public String get_string(int index, String def) {
780 AltosJson value = get(index);
782 return value.string();
786 public boolean get_boolean(int index, boolean def) {
787 AltosJson value = get(index);
793 public double[] get_double_array(String key, double[] def) {
794 AltosJson value = get(key);
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]);
804 public int[] get_int_array(String key, int[] def) {
805 AltosJson value = get(key);
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]);
815 /* Array setter functions
817 public AltosJson put(int index, AltosJson value) {
819 array.put(index, value);
823 public Object put(int index, Object value) {
826 array.put(index, new AltosJson(value));
830 public double put(int index, double value) {
832 array.put(index, new AltosJson(value));
836 public AltosJson put(int index, double[] value) {
839 array.put(index, new AltosJson(value));
844 public int[] put(int index, int[] value) {
847 array.put(index, new AltosJson(value));
852 public String put(int index, String value) {
855 array.put(index, new AltosJson(value));
860 public boolean put(int index, boolean value) {
862 array.put(index, new AltosJson(value));
866 /* Hash setter functions
868 public AltosJson put(String key, AltosJson value) {
870 hash.put(key, value);
874 public Object put(String key, Object value) {
877 hash.put(key, new AltosJson(value));
881 public double put(String key, double value) {
883 hash.put(key, new AltosJson(value));
887 public String put(String key, String value) {
890 hash.put(key, new AltosJson(value));
895 public boolean put(String key, boolean value) {
897 hash.put(key, new AltosJson(value));
901 public AltosJson[] put(String key, AltosJson[] value) {
904 hash.put(key, new AltosJson(value));
909 public double[] put(String key, double[] value) {
912 hash.put(key, new AltosJson(value));
917 public int[] put(String key, int[] value) {
920 hash.put(key, new AltosJson(value));
925 /* Primitive setter functions
927 public double put(double value) {
933 public byte put(byte value) {
939 public char put(char value) {
945 public int put(int value) {
951 public long put(long value) {
957 public String put(String value) {
963 public boolean put(boolean value) {
964 assert_boolean(true);
969 private boolean isInnerClass(Class c) {
970 for (Field field : c.getDeclaredFields())
971 if (field.isSynthetic())
976 /* Construct an object of the specified class from the JSON
979 * This works as long as the structure is non-recursive, and
980 * all inner classes are only members of their immediate outer
983 private Object make(Class c, Class enclosing_class, Object enclosing_object) {
985 if (c == Boolean.TYPE) {
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) {
995 } else if (c == Double.TYPE) {
997 } else if (c == String.class) {
999 } else if (c.isArray()) {
1000 assert_array(false);
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);
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);
1016 Object object = null;
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
1023 if (enclosing_class != null && isInnerClass(c)) {
1024 Constructor<?> ctor = ((Class<?>)c).getDeclaredConstructor((Class<?>) enclosing_class);
1025 object = ctor.newInstance(enclosing_object);
1027 object = c.newInstance();
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();
1035 if (Modifier.isStatic(field.getModifiers()))
1037 if (field.isSynthetic())
1040 AltosJson json = get(fieldName);
1042 Object val = json.make(fieldClass, c, object);
1043 field.setAccessible(true);
1044 field.set(object, val);
1046 } catch (IllegalAccessException ie) {
1051 } catch (InvocationTargetException ie) {
1053 } catch (NoSuchMethodException ie) {
1055 } catch (InstantiationException ie) {
1057 } catch (IllegalAccessException ie) {
1064 /* This is the public API for the
1065 * above function which doesn't handle
1068 public Object make(Class c) {
1069 return make(c, null, null);
1072 /* Constructors, one for each primitive type, String and Object */
1073 public AltosJson(boolean bool) {
1074 type = type_boolean;
1078 public AltosJson(byte number) {
1080 this.l_number = number;
1083 public AltosJson(char number) {
1085 this.l_number = number;
1088 public AltosJson(int number) {
1090 this.l_number = number;
1093 public AltosJson(long number) {
1095 this.l_number = number;
1098 public AltosJson(double number) {
1100 this.d_number = number;
1103 public AltosJson(String string) {
1105 this.string = string;
1108 public AltosJson(Object object) {
1109 if (object instanceof Boolean) {
1110 type = type_boolean;
1111 bool = (Boolean) object;
1112 } else if (object instanceof Byte) {
1114 l_number = (Byte) object;
1115 } else if (object instanceof Character) {
1117 l_number = (Character) object;
1118 } else if (object instanceof Integer) {
1120 l_number = (Integer) object;
1121 } else if (object instanceof Long) {
1123 l_number = (Long) object;
1124 } else if (object instanceof Double) {
1126 d_number = (Double) object;
1127 } else if (object instanceof String) {
1129 string = (String) object;
1130 } else if (object.getClass().isArray()) {
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]));
1159 Object[] array = (Object[]) object;
1160 for (int i = 0; i < array.length; i++)
1161 put(i, new AltosJson(array[i]));
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();
1171 /* Skip static fields */
1172 if (Modifier.isStatic(field.getModifiers()))
1175 /* Skip synthetic fields. We're assuming
1176 * those are always an inner class reference
1177 * to the outer class object
1179 if (field.isSynthetic())
1182 /* We may need to force the field to be accessible if
1185 field.setAccessible(true);
1186 Object val = field.get(object);
1188 AltosJson json = new AltosJson(val);
1189 put(fieldName, json);
1191 } catch (IllegalAccessException ie) {
1198 /* Array constructors, one for each primitive type, String and Object */
1199 public AltosJson(boolean[] bools) {
1201 for(int i = 0; i < bools.length; i++)
1202 put(i, new AltosJson(bools[i]));
1205 public AltosJson(byte[] numbers) {
1207 for(int i = 0; i < numbers.length; i++)
1208 put(i, new AltosJson(numbers[i]));
1211 public AltosJson(char[] numbers) {
1213 for(int i = 0; i < numbers.length; i++)
1214 put(i, new AltosJson(numbers[i]));
1217 public AltosJson(int[] numbers) {
1219 for(int i = 0; i < numbers.length; i++)
1220 put(i, new AltosJson(numbers[i]));
1223 public AltosJson(long[] numbers) {
1225 for(int i = 0; i < numbers.length; i++)
1226 put(i, new AltosJson(numbers[i]));
1229 public AltosJson(double[] numbers) {
1231 for(int i = 0; i < numbers.length; i++)
1232 put(i, new AltosJson(numbers[i]));
1235 public AltosJson(String[] strings) {
1237 for(int i = 0; i < strings.length; i++)
1238 put(i, new AltosJson(strings[i]));
1241 public AltosJson(AltosJson[] jsons) {
1243 for (int i = 0; i < jsons.length; i++)
1247 public AltosJson(Object[] array) {
1249 for (int i = 0; i < array.length; i++)
1250 put(i, new AltosJson(array[i]));
1253 /* Empty constructor
1255 public AltosJson() {
1259 public AltosJson(JsonHash hash) {
1264 public AltosJson(JsonArray array) {