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; either version 2 of the License, or
7 * (at your option) any later version.
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.
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.
19 package org.altusmetrum.altoslib_11;
25 import java.lang.reflect.*;
28 StringBuffer quote(StringBuffer result, String a) {
30 for (int i = 0; i < a.length(); i++) {
36 result.append('\\').append(c);
50 StringBuffer append(StringBuffer result, AltosJson value, int indent, boolean pretty) {
51 value.append(result, indent, pretty);
55 StringBuffer append(StringBuffer result, String string) {
56 result.append(string);
60 StringBuffer indent(StringBuffer result, int indent) {
62 for (int i = 0; i < indent; i++)
66 static NumberFormat get_nf_json() {
67 DecimalFormat nf = (DecimalFormat) NumberFormat.getNumberInstance(Locale.ROOT);
68 nf.setParseIntegerOnly(false);
69 nf.setGroupingUsed(false);
70 nf.setMaximumFractionDigits(17);
71 nf.setMinimumFractionDigits(0);
72 nf.setMinimumIntegerDigits(1);
73 nf.setDecimalSeparatorAlwaysShown(false);
77 static NumberFormat nf_json = get_nf_json();
80 class JsonHash extends JsonUtil {
81 Hashtable<String,AltosJson> hash;
83 void append_hash(StringBuffer result, int indent, boolean pretty) {
88 ArrayList<String> key_list = new ArrayList<String>(hash.keySet());
90 Collections.sort(key_list, new Comparator<String>() {
92 public int compare(String a, String b) { return a.compareTo(b); }
95 for (String key : key_list) {
96 AltosJson value = hash.get(key);
102 indent(result, indent+1);
104 append(result, ": ");
105 append(result, value, indent+1, pretty);
108 indent(result, indent);
112 void put(String key, AltosJson value) {
113 hash.put(key, value);
116 AltosJson get(String key) {
117 return hash.get(key);
121 hash = new Hashtable<String,AltosJson>();
125 class JsonArray extends JsonUtil {
126 ArrayList<AltosJson> array;
128 void append_array(StringBuffer result, int indent, boolean pretty) {
129 boolean first = true;
132 for (int i = 0; i < array.size(); i++) {
133 AltosJson value = array.get(i);
139 indent(result, indent+1);
140 append(result, value, indent+1, pretty);
143 indent(result, indent);
147 void put(int index, AltosJson value) {
148 if (index >= array.size())
149 array.add(index, value);
151 array.set(index, value);
154 AltosJson get(int index) {
155 if (index < 0 || index > array.size())
157 return array.get(index);
165 array = new ArrayList<AltosJson>();
176 static final int _string = 0;
177 static final int _double = 1;
178 static final int _long = 2;
179 static final int _boolean = 3;
180 static final int _oc = 4;
181 static final int _cc = 5;
182 static final int _os = 6;
183 static final int _cs = 7;
184 static final int _comma = 8;
185 static final int _colon = 9;
186 static final int _end = 10;
187 static final int _error = 11;
189 static String token_name(int token) {
220 String token_name() {
221 return token_name(token);
224 JsonToken(int token) {
228 JsonToken(int token, boolean bval) {
233 JsonToken(int token, double dval) {
238 JsonToken(int token, long lval) {
243 JsonToken(int token, String sval) {
248 JsonToken(int token, StringBuffer bval) {
249 this(token, bval.toString());
256 class JsonLexer extends JsonUtil {
260 StringBuffer pending_token;
263 static class keyword {
267 JsonToken match(String value) {
268 if (word.equals(value))
273 keyword(String word, JsonToken token) {
279 /* boolean values are the only keywords in json
281 static keyword[] keywords = {
282 new keyword("true", new JsonToken(JsonToken._boolean, true)),
283 new keyword("false", new JsonToken(JsonToken._boolean, false)),
284 new keyword("NegInfinity", new JsonToken(JsonToken._double, Double.NEGATIVE_INFINITY)),
285 new keyword("Infinity", new JsonToken(JsonToken._double, Double.POSITIVE_INFINITY)),
286 new keyword("NaN", new JsonToken(JsonToken._double, Double.NaN))
289 static JsonToken keyword(String word) {
290 for (int i = 0; i < keywords.length; i++) {
291 JsonToken token = keywords[i].match(word);
298 /* Get the next char (-1 for EOF) */
299 int ch() throws IOException {
307 pending_token.append((char) c);
315 throw new IllegalArgumentException("ungot buffer full");
316 pending_token.deleteCharAt( pending_token.length()-1);
322 String last_token_string() {
323 if (pending_token == null)
326 return pending_token.toString();
329 static boolean is_long_range(double d) {
330 return -9223372036854775808.0 <= d && d <= 9223372036854775807.0;
334 pending_token = new StringBuffer();
342 return new JsonToken(JsonToken._end);
348 return new JsonToken(JsonToken._oc);
350 return new JsonToken(JsonToken._cc);
352 return new JsonToken(JsonToken._os);
354 return new JsonToken(JsonToken._cs);
356 return new JsonToken(JsonToken._comma);
358 return new JsonToken(JsonToken._colon);
359 case '0': case '1': case '2': case '3': case '4':
360 case '5': case '6': case '7': case '8': case '9':
361 case '.': case '-': case '+':
362 StringBuffer dbuf = new StringBuffer();
363 boolean is_double = false;
364 while (Character.isDigit(c) || c == '.' || c == '+' || c == '-' || c == 'e' || c == 'E') {
365 if (c == '.' || c == 'E')
367 dbuf.appendCodePoint(c);
371 String dstr = dbuf.toString();
374 dval = nf_json.parse(dstr).doubleValue();
375 } catch (ParseException pe) {
376 return new JsonToken(JsonToken._error, dstr);
378 if (is_double || !is_long_range(dval))
379 return new JsonToken(JsonToken._double, dval);
381 long lval = Long.parseLong(dstr);
382 return new JsonToken(JsonToken._long, lval);
385 StringBuffer bval = new StringBuffer();
403 bval.appendCodePoint(c);
405 return new JsonToken(JsonToken._string, bval);
407 if (Character.isLetter(c)) {
408 StringBuffer tbuf = new StringBuffer();
410 tbuf.appendCodePoint(c);
412 } while (Character.isLetter(c));
414 JsonToken token = keyword(tbuf.toString());
421 } catch (IOException ie) {
422 return new JsonToken(JsonToken._error, "<EIO>");
430 JsonToken expect(int e) {
433 throw new IllegalArgumentException(String.format("got \"%s\" while expecting \"%s\"",
435 JsonToken.token_name(e)));
440 JsonLexer(String s) {
441 f = new StringReader(s);
448 * Parse a json string into a AltosJson object
453 void parse_error(String format, Object ... arguments) {
454 throw new IllegalArgumentException(String.format("line %d: JSON parse error %s\n",
456 String.format(format, arguments)));
459 /* Hashes are { string: value ... } */
461 JsonHash hash = new JsonHash();
463 /* skip the open brace */
466 /* Allow for empty hashes */
467 if (lexer.token.token == JsonToken._cc) {
473 String key = lexer.expect(JsonToken._string).sval;
474 lexer.expect(JsonToken._colon);
475 AltosJson value = value();
476 hash.put(key, value);
478 switch (lexer.token.token) {
479 case JsonToken._comma:
486 parse_error("got %s expect \",\" or \"}\"", lexer.token.token_name());
492 /* Arrays are [ value ... ] */
494 JsonArray array = new JsonArray();
497 for (int i = 0;; i++) {
498 /* Allow for empty arrays */
499 if (lexer.token.token == JsonToken._cs) {
504 AltosJson value = value();
506 switch (lexer.token.token) {
507 case JsonToken._comma:
514 parse_error("got %s expect \",\" or \"]\"", lexer.token.token_name());
520 /* Json is a simple LL language; one token is sufficient to
521 * identify the next object in the input
524 switch (lexer.token.token) {
526 return new AltosJson(hash());
528 return new AltosJson(array());
529 case JsonToken._double:
530 double dval = lexer.token.dval;
532 return new AltosJson(dval);
533 case JsonToken._long:
534 long lval = lexer.token.lval;
536 return new AltosJson(lval);
537 case JsonToken._string:
538 String sval = lexer.token.sval;
540 return new AltosJson(sval);
541 case JsonToken._boolean:
542 boolean bval = lexer.token.bval;
544 return new AltosJson(bval);
546 parse_error("Unexpected token \"%s\"", lexer.token.token_name());
556 JsonParse(String s) {
557 lexer = new JsonLexer(s);
561 public class AltosJson extends JsonUtil {
562 private static final int type_none = 0;
563 private static final int type_hash = 1;
564 private static final int type_array = 2;
565 private static final int type_double = 3;
566 private static final int type_long = 4;
567 private static final int type_string = 5;
568 private static final int type_boolean = 6;
572 private JsonHash hash;
573 private JsonArray array;
574 private double d_number;
575 private long l_number;
576 private String string;
577 private boolean bool;
579 /* Generate string representation of the value
581 StringBuffer append(StringBuffer result, int indent, boolean pretty) {
584 hash.append_hash(result, indent, pretty);
587 array.append_array(result, indent, pretty);
590 if (Double.isInfinite(d_number)) {
592 result.append("NegInfinity");
594 result.append("Infinity");
595 } else if (Double.isNaN(d_number)) {
596 result.append("NaN");
598 String dval = nf_json.format(d_number);
599 if (dval.equals("-0"))
605 result.append(new Long(l_number).toString());
608 quote(result, string);
611 result.append(bool ? "true" : "false");
617 private String toString(int indent, boolean pretty) {
618 StringBuffer result = new StringBuffer();
619 append(result, indent, pretty);
620 return result.toString();
623 public String toString() {
624 return toString(0, false);
627 public String toPrettyString() {
628 return toString(0, true);
631 /* Parse string representation to a value
634 public static AltosJson fromString(String string) {
635 JsonParse parse = new JsonParse(string);
637 return parse.parse();
638 } catch (IllegalArgumentException ie) {
639 System.out.printf("json:\n%s\n%s\n", string, ie.getMessage());
644 /* Accessor functions
646 private boolean assert_type(boolean setting, int type, int other_type, String error) {
647 if (setting && this.type == type_none) {
651 if (this.type != type && this.type != other_type)
652 throw new IllegalArgumentException(error);
656 private boolean assert_type(boolean setting, int type, String error) {
657 return assert_type(setting, type, type, error);
660 private void assert_hash(boolean setting) {
661 if (!assert_type(setting, type_hash, "not a hash"))
662 hash = new JsonHash();
665 private void assert_array(boolean setting) {
666 if (!assert_type(setting, type_array, "not an array"))
667 array = new JsonArray();
670 private void assert_number() {
671 assert_type(false, type_double, type_long, "not a number");
674 private void assert_double() {
675 assert_type(true, type_double, type_long, "not a number");
678 private void assert_long() {
679 assert_type(true, type_long, type_double, "not a number");
682 private void assert_string(boolean setting) {
683 assert_type(setting, type_string, "not a string");
686 private void assert_boolean(boolean setting) {
687 assert_type(setting, type_boolean, "not a boolean");
690 /* Primitive accessors
692 public double number() {
694 if (type == type_double)
697 return (double) l_number;
700 public long l_number() {
702 if (type == type_double)
703 return (long) d_number;
708 public String string() {
709 assert_string(false);
713 public boolean bool() {
714 assert_boolean(false);
718 public AltosJson get(int index) {
720 return array.get(index);
723 public AltosJson get(String key) {
725 return hash.get(key);
733 /* Typed accessors with defaulting
735 public double get_double(String key, double def) {
736 AltosJson value = get(key);
738 return value.number();
743 public long get_long(String key, long def) {
744 AltosJson value = get(key);
746 return value.l_number();
750 public int get_int(String key, int def) {
751 AltosJson value = get(key);
753 return (int) value.l_number();
757 public String get_string(String key, String def) {
758 AltosJson value = get(key);
760 return value.string();
764 public boolean get_boolean(String key, boolean def) {
765 AltosJson value = get(key);
771 public double get_double(int index, double def) {
772 AltosJson value = get(index);
774 return value.number();
778 public long get_long(int index, long def) {
779 AltosJson value = get(index);
781 return value.l_number();
785 public int get_int(int index, int def) {
786 AltosJson value = get(index);
788 return (int) value.l_number();
792 public String get_string(int index, String def) {
793 AltosJson value = get(index);
795 return value.string();
799 public boolean get_boolean(int index, boolean def) {
800 AltosJson value = get(index);
806 public double[] get_double_array(String key, double[] def) {
807 AltosJson value = get(key);
809 double[] ret = new double[value.size()];
810 for (int i = 0; i < value.size(); i++)
811 ret[i] = value.get_double(i, def == null ? 0 : def[i]);
817 public int[] get_int_array(String key, int[] def) {
818 AltosJson value = get(key);
820 int[] ret = new int[value.size()];
821 for (int i = 0; i < value.size(); i++)
822 ret[i] = value.get_int(i, def == null ? 0 : def[i]);
828 /* Array setter functions
830 public AltosJson put(int index, AltosJson value) {
832 array.put(index, value);
836 public Object put(int index, Object value) {
839 array.put(index, new AltosJson(value));
843 public double put(int index, double value) {
845 array.put(index, new AltosJson(value));
849 public AltosJson put(int index, double[] value) {
852 array.put(index, new AltosJson(value));
857 public int[] put(int index, int[] value) {
860 array.put(index, new AltosJson(value));
865 public String put(int index, String value) {
868 array.put(index, new AltosJson(value));
873 public boolean put(int index, boolean value) {
875 array.put(index, new AltosJson(value));
879 /* Hash setter functions
881 public AltosJson put(String key, AltosJson value) {
883 hash.put(key, value);
887 public Object put(String key, Object value) {
890 hash.put(key, new AltosJson(value));
894 public double put(String key, double value) {
896 hash.put(key, new AltosJson(value));
900 public String put(String key, String value) {
903 hash.put(key, new AltosJson(value));
908 public boolean put(String key, boolean value) {
910 hash.put(key, new AltosJson(value));
914 public AltosJson[] put(String key, AltosJson[] value) {
917 hash.put(key, new AltosJson(value));
922 public double[] put(String key, double[] value) {
925 hash.put(key, new AltosJson(value));
930 public int[] put(String key, int[] value) {
933 hash.put(key, new AltosJson(value));
938 /* Primitive setter functions
940 public double put(double value) {
946 public byte put(byte value) {
952 public char put(char value) {
958 public int put(int value) {
964 public long put(long value) {
970 public String put(String value) {
976 public boolean put(boolean value) {
977 assert_boolean(true);
982 private boolean isInnerClass(Class c) {
983 for (Field field : c.getDeclaredFields())
984 if (field.isSynthetic())
989 /* Construct an object of the specified class from the JSON
992 * This works as long as the structure is non-recursive, and
993 * all inner classes are only members of their immediate outer
996 private Object make(Class c, Class enclosing_class, Object enclosing_object) {
998 if (c == Boolean.TYPE) {
1000 } else if (c == Byte.TYPE) {
1001 ret = (Byte) (byte) l_number();
1002 } else if (c == Character.TYPE) {
1003 ret = (Character) (char) l_number();
1004 } else if (c == Integer.TYPE) {
1005 ret = (Integer) (int) l_number();
1006 } else if (c == Long.TYPE) {
1008 } else if (c == Double.TYPE) {
1010 } else if (c == String.class) {
1012 } else if (c.isArray()) {
1013 assert_array(false);
1015 Class element_class = c.getComponentType();
1016 if (element_class == Boolean.TYPE) {
1017 boolean[] array = (boolean[]) Array.newInstance(element_class, size());
1018 for (int i = 0; i < array.length; i++)
1019 array[i] = (Boolean) get(i).make(element_class);
1021 } else if (element_class == Byte.TYPE) {
1022 byte[] array = (byte[]) Array.newInstance(element_class, size());
1023 for (int i = 0; i < array.length; i++)
1024 array[i] = (Byte) get(i).make(element_class);
1026 } else if (element_class == Character.TYPE) {
1027 char[] array = (char[]) Array.newInstance(element_class, size());
1028 for (int i = 0; i < array.length; i++)
1029 array[i] = (Character) get(i).make(element_class);
1031 } else if (element_class == Integer.TYPE) {
1032 int[] array = (int[]) Array.newInstance(element_class, size());
1033 for (int i = 0; i < array.length; i++)
1034 array[i] = (Integer) get(i).make(element_class);
1036 } else if (element_class == Long.TYPE) {
1037 long[] array = (long[]) Array.newInstance(element_class, size());
1038 for (int i = 0; i < array.length; i++)
1039 array[i] = (Long) get(i).make(element_class);
1041 } else if (element_class == Double.TYPE) {
1042 double[] array = (double[]) Array.newInstance(element_class, size());
1043 for (int i = 0; i < array.length; i++)
1044 array[i] = (Double) get(i).make(element_class);
1047 Object[] array = (Object[]) Array.newInstance(element_class, size());
1048 for (int i = 0; i < array.length; i++)
1049 array[i] = get(i).make(element_class);
1054 Object object = null;
1056 /* Inner classes have a hidden extra parameter
1057 * to the constructor. Assume that the enclosing object is
1058 * of the enclosing class and construct the object
1061 if (enclosing_class != null && isInnerClass(c)) {
1062 Constructor<?> ctor = ((Class<?>)c).getDeclaredConstructor((Class<?>) enclosing_class);
1063 object = ctor.newInstance(enclosing_object);
1065 object = c.newInstance();
1067 for (; c != Object.class; c = c.getSuperclass()) {
1068 for (Field field : c.getDeclaredFields()) {
1069 String fieldName = field.getName();
1070 Class fieldClass = field.getType();
1072 if (Modifier.isStatic(field.getModifiers()))
1074 if (field.isSynthetic())
1077 AltosJson json = get(fieldName);
1079 Object val = json.make(fieldClass, c, object);
1080 field.setAccessible(true);
1081 field.set(object, val);
1083 } catch (IllegalAccessException ie) {
1084 System.out.printf("%s:%s %s\n",
1085 c.getName(), fieldName, ie.toString());
1090 } catch (InvocationTargetException ie) {
1091 System.out.printf("%s: %s\n",
1092 c.getName(), ie.toString());
1094 } catch (NoSuchMethodException ie) {
1095 System.out.printf("%s: %s\n",
1096 c.getName(), ie.toString());
1098 } catch (InstantiationException ie) {
1099 System.out.printf("%s: %s\n",
1100 c.getName(), ie.toString());
1102 } catch (IllegalAccessException ie) {
1103 System.out.printf("%s: %s\n",
1104 c.getName(), ie.toString());
1111 /* This is the public API for the
1112 * above function which doesn't handle
1115 public Object make(Class c) {
1116 return make(c, null, null);
1119 /* Constructors, one for each primitive type, String and Object */
1120 public AltosJson(boolean bool) {
1121 type = type_boolean;
1125 public AltosJson(byte number) {
1127 this.l_number = number;
1130 public AltosJson(char number) {
1132 this.l_number = number;
1135 public AltosJson(int number) {
1137 this.l_number = number;
1140 public AltosJson(long number) {
1142 this.l_number = number;
1145 public AltosJson(double number) {
1147 this.d_number = number;
1150 public AltosJson(String string) {
1152 this.string = string;
1155 public AltosJson(Object object) {
1156 if (object instanceof Boolean) {
1157 type = type_boolean;
1158 bool = (Boolean) object;
1159 } else if (object instanceof Byte) {
1161 l_number = (Byte) object;
1162 } else if (object instanceof Character) {
1164 l_number = (Character) object;
1165 } else if (object instanceof Integer) {
1167 l_number = (Integer) object;
1168 } else if (object instanceof Long) {
1170 l_number = (Long) object;
1171 } else if (object instanceof Double) {
1173 d_number = (Double) object;
1174 } else if (object instanceof String) {
1176 string = (String) object;
1177 } else if (object.getClass().isArray()) {
1180 Class component_class = object.getClass().getComponentType();
1181 if (component_class == Boolean.TYPE) {
1182 boolean[] array = (boolean[]) object;
1183 for (int i = 0; i < array.length; i++)
1184 put(i, new AltosJson(array[i]));
1185 } else if (component_class == Byte.TYPE) {
1186 byte[] array = (byte[]) object;
1187 for (int i = 0; i < array.length; i++)
1188 put(i, new AltosJson(array[i]));
1189 } else if (component_class == Character.TYPE) {
1190 char[] array = (char[]) object;
1191 for (int i = 0; i < array.length; i++)
1192 put(i, new AltosJson(array[i]));
1193 } else if (component_class == Integer.TYPE) {
1194 int[] array = (int[]) object;
1195 for (int i = 0; i < array.length; i++)
1196 put(i, new AltosJson(array[i]));
1197 } else if (component_class == Long.TYPE) {
1198 long[] array = (long[]) object;
1199 for (int i = 0; i < array.length; i++)
1200 put(i, new AltosJson(array[i]));
1201 } else if (component_class == Double.TYPE) {
1202 double[] array = (double[]) object;
1203 for (int i = 0; i < array.length; i++)
1204 put(i, new AltosJson(array[i]));
1206 Object[] array = (Object[]) object;
1207 for (int i = 0; i < array.length; i++)
1208 put(i, new AltosJson(array[i]));
1212 for (Class c = object.getClass(); c != Object.class; c = c.getSuperclass()) {
1213 for (Field field : c.getDeclaredFields()) {
1214 String fieldName = field.getName();
1216 /* Skip static fields */
1217 if (Modifier.isStatic(field.getModifiers()))
1220 /* Skip synthetic fields. We're assuming
1221 * those are always an inner class reference
1222 * to the outer class object
1224 if (field.isSynthetic())
1227 /* We may need to force the field to be accessible if
1230 field.setAccessible(true);
1231 Object val = field.get(object);
1233 AltosJson json = new AltosJson(val);
1234 put(fieldName, json);
1236 } catch (IllegalAccessException ie) {
1237 System.out.printf("%s:%s %s\n",
1238 c.getName(), fieldName, ie.toString());
1245 /* Array constructors, one for each primitive type, String and Object */
1246 public AltosJson(boolean[] bools) {
1248 for(int i = 0; i < bools.length; i++)
1249 put(i, new AltosJson(bools[i]));
1252 public AltosJson(byte[] numbers) {
1254 for(int i = 0; i < numbers.length; i++)
1255 put(i, new AltosJson(numbers[i]));
1258 public AltosJson(char[] numbers) {
1260 for(int i = 0; i < numbers.length; i++)
1261 put(i, new AltosJson(numbers[i]));
1264 public AltosJson(int[] numbers) {
1266 for(int i = 0; i < numbers.length; i++)
1267 put(i, new AltosJson(numbers[i]));
1270 public AltosJson(long[] numbers) {
1272 for(int i = 0; i < numbers.length; i++)
1273 put(i, new AltosJson(numbers[i]));
1276 public AltosJson(double[] numbers) {
1278 for(int i = 0; i < numbers.length; i++)
1279 put(i, new AltosJson(numbers[i]));
1282 public AltosJson(String[] strings) {
1284 for(int i = 0; i < strings.length; i++)
1285 put(i, new AltosJson(strings[i]));
1288 public AltosJson(AltosJson[] jsons) {
1290 for (int i = 0; i < jsons.length; i++)
1294 public AltosJson(Object[] array) {
1296 for (int i = 0; i < array.length; i++)
1297 put(i, new AltosJson(array[i]));
1300 /* Empty constructor
1302 public AltosJson() {
1306 public AltosJson(JsonHash hash) {
1311 public AltosJson(JsonArray array) {