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_12;
25 import java.lang.reflect.*;
28 Writer quote(Writer writer, String a) throws IOException {
30 for (int i = 0; i < a.length(); i++) {
36 writer.append('\\').append(c);
50 Writer append(Writer result, AltosJson value, int indent, boolean pretty) throws IOException {
51 value.append(result, indent, pretty);
55 Writer append(Writer result, String string) throws IOException {
56 result.append(string);
60 Writer indent(Writer result, int indent) throws IOException {
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(Writer result, int indent, boolean pretty) throws IOException {
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(Writer result, int indent, boolean pretty) throws IOException {
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;
188 static final int _none = 12;
190 static String token_name(int token) {
221 String token_name() {
222 return token_name(token);
225 JsonToken(int token) {
229 JsonToken(int token, boolean bval) {
234 JsonToken(int token, double dval) {
239 JsonToken(int token, long lval) {
244 JsonToken(int token, String sval) {
249 JsonToken(int token, Writer bval) {
250 this(token, bval.toString());
257 class JsonLexer extends JsonUtil {
261 StringBuffer pending_token;
262 private JsonToken token;
264 static class keyword {
268 JsonToken match(String value) {
269 if (word.equals(value))
274 keyword(String word, JsonToken token) {
280 /* boolean values are the only keywords in json
282 static keyword[] keywords = {
283 new keyword("true", new JsonToken(JsonToken._boolean, true)),
284 new keyword("false", new JsonToken(JsonToken._boolean, false)),
285 new keyword("NegInfinity", new JsonToken(JsonToken._double, Double.NEGATIVE_INFINITY)),
286 new keyword("Infinity", new JsonToken(JsonToken._double, Double.POSITIVE_INFINITY)),
287 new keyword("NaN", new JsonToken(JsonToken._double, Double.NaN))
290 static JsonToken keyword(String word) {
291 for (int i = 0; i < keywords.length; i++) {
292 JsonToken token = keywords[i].match(word);
299 /* Get the next char (-1 for EOF) */
300 int ch() throws IOException {
308 pending_token.append((char) c);
316 throw new IllegalArgumentException("ungot buffer full");
317 pending_token.deleteCharAt( pending_token.length()-1);
323 String last_token_string() {
324 if (pending_token == null)
327 return pending_token.toString();
330 static boolean is_long_range(double d) {
331 return -9223372036854775808.0 <= d && d <= 9223372036854775807.0;
335 pending_token = new StringBuffer();
343 return new JsonToken(JsonToken._end);
349 return new JsonToken(JsonToken._oc);
351 return new JsonToken(JsonToken._cc);
353 return new JsonToken(JsonToken._os);
355 return new JsonToken(JsonToken._cs);
357 return new JsonToken(JsonToken._comma);
359 return new JsonToken(JsonToken._colon);
360 case '0': case '1': case '2': case '3': case '4':
361 case '5': case '6': case '7': case '8': case '9':
362 case '.': case '-': case '+':
363 StringBuffer dbuf = new StringBuffer();
364 boolean is_double = false;
365 while (Character.isDigit(c) || c == '.' || c == '+' || c == '-' || c == 'e' || c == 'E') {
366 if (c == '.' || c == 'E')
368 dbuf.appendCodePoint(c);
372 String dstr = dbuf.toString();
375 dval = nf_json.parse(dstr).doubleValue();
376 } catch (ParseException pe) {
377 return new JsonToken(JsonToken._error, dstr);
379 if (is_double || !is_long_range(dval))
380 return new JsonToken(JsonToken._double, dval);
382 long lval = Long.parseLong(dstr);
383 return new JsonToken(JsonToken._long, lval);
386 Writer bval = new StringWriter();
406 return new JsonToken(JsonToken._string, bval);
408 if (Character.isLetter(c)) {
409 StringBuffer tbuf = new StringBuffer();
411 tbuf.appendCodePoint(c);
413 } while (Character.isLetter(c));
415 JsonToken token = keyword(tbuf.toString());
422 } catch (IOException ie) {
423 return new JsonToken(JsonToken._error, "<EIO>");
437 JsonToken expect(int e) {
438 JsonToken t = token();
440 throw new IllegalArgumentException(String.format("got \"%s\" while expecting \"%s\"",
442 JsonToken.token_name(e)));
447 JsonLexer(String s) {
448 f = new AltosStringInputStream(s);
453 JsonLexer(InputStream f) {
461 * Parse a json string into a AltosJson object
466 void parse_error(String format, Object ... arguments) {
467 throw new IllegalArgumentException(String.format("line %d: JSON parse error %s\n",
469 String.format(format, arguments)));
472 /* Hashes are { string: value ... } */
474 JsonHash hash = new JsonHash();
476 /* skip the open brace */
479 /* Allow for empty hashes */
480 if (lexer.token().token == JsonToken._cc) {
486 String key = lexer.expect(JsonToken._string).sval;
487 lexer.expect(JsonToken._colon);
488 AltosJson value = value();
489 hash.put(key, value);
491 switch (lexer.token().token) {
492 case JsonToken._comma:
499 parse_error("got %s expect \",\" or \"}\"", lexer.token().token_name());
505 /* Arrays are [ value ... ] */
507 JsonArray array = new JsonArray();
510 for (int i = 0;; i++) {
511 /* Allow for empty arrays */
512 if (lexer.token().token == JsonToken._cs) {
517 AltosJson value = value();
519 switch (lexer.token().token) {
520 case JsonToken._comma:
527 parse_error("got %s expect \",\" or \"]\"", lexer.token().token_name());
533 /* Json is a simple LL language; one token is sufficient to
534 * identify the next object in the input
537 switch (lexer.token().token) {
539 return new AltosJson(hash());
541 return new AltosJson(array());
542 case JsonToken._double:
543 double dval = lexer.token().dval;
545 return new AltosJson(dval);
546 case JsonToken._long:
547 long lval = lexer.token().lval;
549 return new AltosJson(lval);
550 case JsonToken._string:
551 String sval = lexer.token().sval;
553 return new AltosJson(sval);
554 case JsonToken._boolean:
555 boolean bval = lexer.token().bval;
557 return new AltosJson(bval);
559 parse_error("Unexpected token \"%s\"", lexer.token().token_name());
569 JsonParse(String s) {
570 lexer = new JsonLexer(s);
573 JsonParse(InputStream f) {
574 lexer = new JsonLexer(f);
578 public class AltosJson extends JsonUtil {
579 private static final int type_none = 0;
580 private static final int type_hash = 1;
581 private static final int type_array = 2;
582 private static final int type_double = 3;
583 private static final int type_long = 4;
584 private static final int type_string = 5;
585 private static final int type_boolean = 6;
589 private JsonHash hash;
590 private JsonArray array;
591 private double d_number;
592 private long l_number;
593 private String string;
594 private boolean bool;
596 /* Generate string representation of the value
598 Writer append(Writer result, int indent, boolean pretty) throws IOException {
601 hash.append_hash(result, indent, pretty);
604 array.append_array(result, indent, pretty);
607 if (Double.isInfinite(d_number)) {
609 result.append("NegInfinity");
611 result.append("Infinity");
612 } else if (Double.isNaN(d_number)) {
613 result.append("NaN");
615 String dval = nf_json.format(d_number);
616 if (dval.equals("-0"))
622 result.append(new Long(l_number).toString());
625 quote(result, string);
628 result.append(bool ? "true" : "false");
634 private String toString(int indent, boolean pretty) {
636 Writer result = new StringWriter();
637 append(result, indent, pretty);
638 return result.toString();
639 } catch (Exception e) {
644 public String toString() {
645 return toString(0, false);
648 public String toPrettyString() {
649 return toString(0, true);
652 public void write(Writer w, int indent, boolean pretty) throws IOException {
653 append(w, indent, pretty);
656 public void write(Writer w) throws IOException {
660 /* Parse string representation to a value
663 public static AltosJson fromString(String string) {
664 JsonParse parse = new JsonParse(string);
666 return parse.parse();
667 } catch (IllegalArgumentException ie) {
668 System.out.printf("json:\n%s\n%s\n", string, ie.getMessage());
673 public static AltosJson fromInputStream(InputStream f) {
674 JsonParse parse = new JsonParse(f);
676 return parse.parse();
677 } catch (IllegalArgumentException ie) {
678 System.out.printf("json:\n%s\n", ie.getMessage());
683 /* Accessor functions
685 private boolean assert_type(boolean setting, int type, int other_type, String error) {
686 if (setting && this.type == type_none) {
690 if (this.type != type && this.type != other_type)
691 throw new IllegalArgumentException(error);
695 private boolean assert_type(boolean setting, int type, String error) {
696 return assert_type(setting, type, type, error);
699 private void assert_hash(boolean setting) {
700 if (!assert_type(setting, type_hash, "not a hash"))
701 hash = new JsonHash();
704 private void assert_array(boolean setting) {
705 if (!assert_type(setting, type_array, "not an array"))
706 array = new JsonArray();
709 private void assert_number() {
710 assert_type(false, type_double, type_long, "not a number");
713 private void assert_double() {
714 assert_type(true, type_double, type_long, "not a number");
717 private void assert_long() {
718 assert_type(true, type_long, type_double, "not a number");
721 private void assert_string(boolean setting) {
722 assert_type(setting, type_string, "not a string");
725 private void assert_boolean(boolean setting) {
726 assert_type(setting, type_boolean, "not a boolean");
729 /* Primitive accessors
731 public double number() {
733 if (type == type_double)
736 return (double) l_number;
739 public long l_number() {
741 if (type == type_double)
742 return (long) d_number;
747 public String string() {
748 assert_string(false);
752 public boolean bool() {
753 assert_boolean(false);
757 public AltosJson get(int index) {
759 return array.get(index);
762 public AltosJson get(String key) {
764 return hash.get(key);
772 /* Typed accessors with defaulting
774 public double get_double(String key, double def) {
775 AltosJson value = get(key);
777 return value.number();
782 public long get_long(String key, long def) {
783 AltosJson value = get(key);
785 return value.l_number();
789 public int get_int(String key, int def) {
790 AltosJson value = get(key);
792 return (int) value.l_number();
796 public String get_string(String key, String def) {
797 AltosJson value = get(key);
799 return value.string();
803 public boolean get_boolean(String key, boolean def) {
804 AltosJson value = get(key);
810 public double get_double(int index, double def) {
811 AltosJson value = get(index);
813 return value.number();
817 public long get_long(int index, long def) {
818 AltosJson value = get(index);
820 return value.l_number();
824 public int get_int(int index, int def) {
825 AltosJson value = get(index);
827 return (int) value.l_number();
831 public String get_string(int index, String def) {
832 AltosJson value = get(index);
834 return value.string();
838 public boolean get_boolean(int index, boolean def) {
839 AltosJson value = get(index);
845 public double[] get_double_array(String key, double[] def) {
846 AltosJson value = get(key);
848 double[] ret = new double[value.size()];
849 for (int i = 0; i < value.size(); i++)
850 ret[i] = value.get_double(i, def == null ? 0 : def[i]);
856 public int[] get_int_array(String key, int[] def) {
857 AltosJson value = get(key);
859 int[] ret = new int[value.size()];
860 for (int i = 0; i < value.size(); i++)
861 ret[i] = value.get_int(i, def == null ? 0 : def[i]);
867 /* Array setter functions
869 public AltosJson put(int index, AltosJson value) {
871 array.put(index, value);
875 public Object put(int index, Object value) {
878 array.put(index, new AltosJson(value));
882 public double put(int index, double value) {
884 array.put(index, new AltosJson(value));
888 public AltosJson put(int index, double[] value) {
891 array.put(index, new AltosJson(value));
896 public int[] put(int index, int[] value) {
899 array.put(index, new AltosJson(value));
904 public String put(int index, String value) {
907 array.put(index, new AltosJson(value));
912 public boolean put(int index, boolean value) {
914 array.put(index, new AltosJson(value));
918 /* Hash setter functions
920 public AltosJson put(String key, AltosJson value) {
922 hash.put(key, value);
926 public Object put(String key, Object value) {
929 hash.put(key, new AltosJson(value));
933 public double put(String key, double value) {
935 hash.put(key, new AltosJson(value));
939 public String put(String key, String value) {
942 hash.put(key, new AltosJson(value));
947 public boolean put(String key, boolean value) {
949 hash.put(key, new AltosJson(value));
953 public AltosJson[] put(String key, AltosJson[] value) {
956 hash.put(key, new AltosJson(value));
961 public double[] put(String key, double[] value) {
964 hash.put(key, new AltosJson(value));
969 public int[] put(String key, int[] value) {
972 hash.put(key, new AltosJson(value));
977 /* Primitive setter functions
979 public double put(double value) {
985 public byte put(byte value) {
991 public char put(char value) {
997 public int put(int value) {
1003 public long put(long value) {
1009 public String put(String value) {
1010 assert_string(true);
1015 public boolean put(boolean value) {
1016 assert_boolean(true);
1021 private boolean isInnerClass(Class c) {
1022 for (Field field : c.getDeclaredFields())
1023 if (field.isSynthetic())
1028 /* Construct an object of the specified class from the JSON
1031 * This works as long as the structure is non-recursive, and
1032 * all inner classes are only members of their immediate outer
1035 private Object make(Class c, Class enclosing_class, Object enclosing_object) {
1037 if (c == Boolean.TYPE) {
1039 } else if (c == Byte.TYPE) {
1040 ret = (Byte) (byte) l_number();
1041 } else if (c == Character.TYPE) {
1042 ret = (Character) (char) l_number();
1043 } else if (c == Integer.TYPE) {
1044 ret = (Integer) (int) l_number();
1045 } else if (c == Long.TYPE) {
1047 } else if (c == Double.TYPE) {
1049 } else if (c == String.class) {
1051 } else if (c.isArray()) {
1052 assert_array(false);
1054 Class element_class = c.getComponentType();
1055 if (element_class == Boolean.TYPE) {
1056 boolean[] array = (boolean[]) Array.newInstance(element_class, size());
1057 for (int i = 0; i < array.length; i++)
1058 array[i] = (Boolean) get(i).make(element_class);
1060 } else if (element_class == Byte.TYPE) {
1061 byte[] array = (byte[]) Array.newInstance(element_class, size());
1062 for (int i = 0; i < array.length; i++)
1063 array[i] = (Byte) get(i).make(element_class);
1065 } else if (element_class == Character.TYPE) {
1066 char[] array = (char[]) Array.newInstance(element_class, size());
1067 for (int i = 0; i < array.length; i++)
1068 array[i] = (Character) get(i).make(element_class);
1070 } else if (element_class == Integer.TYPE) {
1071 int[] array = (int[]) Array.newInstance(element_class, size());
1072 for (int i = 0; i < array.length; i++)
1073 array[i] = (Integer) get(i).make(element_class);
1075 } else if (element_class == Long.TYPE) {
1076 long[] array = (long[]) Array.newInstance(element_class, size());
1077 for (int i = 0; i < array.length; i++)
1078 array[i] = (Long) get(i).make(element_class);
1080 } else if (element_class == Double.TYPE) {
1081 double[] array = (double[]) Array.newInstance(element_class, size());
1082 for (int i = 0; i < array.length; i++)
1083 array[i] = (Double) get(i).make(element_class);
1086 Object[] array = (Object[]) Array.newInstance(element_class, size());
1087 for (int i = 0; i < array.length; i++)
1088 array[i] = get(i).make(element_class);
1093 Object object = null;
1095 /* Inner classes have a hidden extra parameter
1096 * to the constructor. Assume that the enclosing object is
1097 * of the enclosing class and construct the object
1100 if (enclosing_class != null && isInnerClass(c)) {
1101 Constructor<?> ctor = ((Class<?>)c).getDeclaredConstructor((Class<?>) enclosing_class);
1102 object = ctor.newInstance(enclosing_object);
1104 object = c.newInstance();
1106 for (; c != Object.class; c = c.getSuperclass()) {
1107 for (Field field : c.getDeclaredFields()) {
1108 String fieldName = field.getName();
1109 Class fieldClass = field.getType();
1111 if (Modifier.isStatic(field.getModifiers()))
1113 if (field.isSynthetic())
1116 AltosJson json = get(fieldName);
1118 Object val = json.make(fieldClass, c, object);
1119 field.setAccessible(true);
1120 field.set(object, val);
1122 } catch (IllegalAccessException ie) {
1123 System.out.printf("%s:%s %s\n",
1124 c.getName(), fieldName, ie.toString());
1129 } catch (InvocationTargetException ie) {
1130 System.out.printf("%s: %s\n",
1131 c.getName(), ie.toString());
1133 } catch (NoSuchMethodException ie) {
1134 System.out.printf("%s: %s\n",
1135 c.getName(), ie.toString());
1137 } catch (InstantiationException ie) {
1138 System.out.printf("%s: %s\n",
1139 c.getName(), ie.toString());
1141 } catch (IllegalAccessException ie) {
1142 System.out.printf("%s: %s\n",
1143 c.getName(), ie.toString());
1150 /* This is the public API for the
1151 * above function which doesn't handle
1154 public Object make(Class c) {
1155 return make(c, null, null);
1158 /* Constructors, one for each primitive type, String and Object */
1159 public AltosJson(boolean bool) {
1160 type = type_boolean;
1164 public AltosJson(byte number) {
1166 this.l_number = number;
1169 public AltosJson(char number) {
1171 this.l_number = number;
1174 public AltosJson(int number) {
1176 this.l_number = number;
1179 public AltosJson(long number) {
1181 this.l_number = number;
1184 public AltosJson(double number) {
1186 this.d_number = number;
1189 public AltosJson(String string) {
1191 this.string = string;
1194 public AltosJson(Object object) {
1195 if (object instanceof Boolean) {
1196 type = type_boolean;
1197 bool = (Boolean) object;
1198 } else if (object instanceof Byte) {
1200 l_number = (Byte) object;
1201 } else if (object instanceof Character) {
1203 l_number = (Character) object;
1204 } else if (object instanceof Integer) {
1206 l_number = (Integer) object;
1207 } else if (object instanceof Long) {
1209 l_number = (Long) object;
1210 } else if (object instanceof Double) {
1212 d_number = (Double) object;
1213 } else if (object instanceof String) {
1215 string = (String) object;
1216 } else if (object.getClass().isArray()) {
1219 Class component_class = object.getClass().getComponentType();
1220 if (component_class == Boolean.TYPE) {
1221 boolean[] array = (boolean[]) object;
1222 for (int i = 0; i < array.length; i++)
1223 put(i, new AltosJson(array[i]));
1224 } else if (component_class == Byte.TYPE) {
1225 byte[] array = (byte[]) object;
1226 for (int i = 0; i < array.length; i++)
1227 put(i, new AltosJson(array[i]));
1228 } else if (component_class == Character.TYPE) {
1229 char[] array = (char[]) object;
1230 for (int i = 0; i < array.length; i++)
1231 put(i, new AltosJson(array[i]));
1232 } else if (component_class == Integer.TYPE) {
1233 int[] array = (int[]) object;
1234 for (int i = 0; i < array.length; i++)
1235 put(i, new AltosJson(array[i]));
1236 } else if (component_class == Long.TYPE) {
1237 long[] array = (long[]) object;
1238 for (int i = 0; i < array.length; i++)
1239 put(i, new AltosJson(array[i]));
1240 } else if (component_class == Double.TYPE) {
1241 double[] array = (double[]) object;
1242 for (int i = 0; i < array.length; i++)
1243 put(i, new AltosJson(array[i]));
1245 Object[] array = (Object[]) object;
1246 for (int i = 0; i < array.length; i++)
1247 put(i, new AltosJson(array[i]));
1251 for (Class c = object.getClass(); c != Object.class; c = c.getSuperclass()) {
1252 for (Field field : c.getDeclaredFields()) {
1253 String fieldName = field.getName();
1255 /* XXX hack to allow fields to be not converted */
1256 if (fieldName.startsWith("__"))
1259 /* Skip static fields */
1260 if (Modifier.isStatic(field.getModifiers()))
1263 /* Skip synthetic fields. We're assuming
1264 * those are always an inner class reference
1265 * to the outer class object
1267 if (field.isSynthetic())
1270 /* We may need to force the field to be accessible if
1273 field.setAccessible(true);
1274 Object val = field.get(object);
1276 AltosJson json = new AltosJson(val);
1277 put(fieldName, json);
1279 } catch (IllegalAccessException ie) {
1280 System.out.printf("%s:%s %s\n",
1281 c.getName(), fieldName, ie.toString());
1288 /* Array constructors, one for each primitive type, String and Object */
1289 public AltosJson(boolean[] bools) {
1291 for(int i = 0; i < bools.length; i++)
1292 put(i, new AltosJson(bools[i]));
1295 public AltosJson(byte[] numbers) {
1297 for(int i = 0; i < numbers.length; i++)
1298 put(i, new AltosJson(numbers[i]));
1301 public AltosJson(char[] numbers) {
1303 for(int i = 0; i < numbers.length; i++)
1304 put(i, new AltosJson(numbers[i]));
1307 public AltosJson(int[] numbers) {
1309 for(int i = 0; i < numbers.length; i++)
1310 put(i, new AltosJson(numbers[i]));
1313 public AltosJson(long[] numbers) {
1315 for(int i = 0; i < numbers.length; i++)
1316 put(i, new AltosJson(numbers[i]));
1319 public AltosJson(double[] numbers) {
1321 for(int i = 0; i < numbers.length; i++)
1322 put(i, new AltosJson(numbers[i]));
1325 public AltosJson(String[] strings) {
1327 for(int i = 0; i < strings.length; i++)
1328 put(i, new AltosJson(strings[i]));
1331 public AltosJson(AltosJson[] jsons) {
1333 for (int i = 0; i < jsons.length; i++)
1337 public AltosJson(Object[] array) {
1339 for (int i = 0; i < array.length; i++)
1340 put(i, new AltosJson(array[i]));
1343 /* Empty constructor
1345 public AltosJson() {
1349 public AltosJson(JsonHash hash) {
1354 public AltosJson(JsonArray array) {