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 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;
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, Writer 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 Writer bval = new StringWriter();
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);
446 JsonLexer(Reader f) {
454 * Parse a json string into a AltosJson object
459 void parse_error(String format, Object ... arguments) {
460 throw new IllegalArgumentException(String.format("line %d: JSON parse error %s\n",
462 String.format(format, arguments)));
465 /* Hashes are { string: value ... } */
467 JsonHash hash = new JsonHash();
469 /* skip the open brace */
472 /* Allow for empty hashes */
473 if (lexer.token.token == JsonToken._cc) {
479 String key = lexer.expect(JsonToken._string).sval;
480 lexer.expect(JsonToken._colon);
481 AltosJson value = value();
482 hash.put(key, value);
484 switch (lexer.token.token) {
485 case JsonToken._comma:
492 parse_error("got %s expect \",\" or \"}\"", lexer.token.token_name());
498 /* Arrays are [ value ... ] */
500 JsonArray array = new JsonArray();
503 for (int i = 0;; i++) {
504 /* Allow for empty arrays */
505 if (lexer.token.token == JsonToken._cs) {
510 AltosJson value = value();
512 switch (lexer.token.token) {
513 case JsonToken._comma:
520 parse_error("got %s expect \",\" or \"]\"", lexer.token.token_name());
526 /* Json is a simple LL language; one token is sufficient to
527 * identify the next object in the input
530 switch (lexer.token.token) {
532 return new AltosJson(hash());
534 return new AltosJson(array());
535 case JsonToken._double:
536 double dval = lexer.token.dval;
538 return new AltosJson(dval);
539 case JsonToken._long:
540 long lval = lexer.token.lval;
542 return new AltosJson(lval);
543 case JsonToken._string:
544 String sval = lexer.token.sval;
546 return new AltosJson(sval);
547 case JsonToken._boolean:
548 boolean bval = lexer.token.bval;
550 return new AltosJson(bval);
552 parse_error("Unexpected token \"%s\"", lexer.token.token_name());
562 JsonParse(String s) {
563 lexer = new JsonLexer(s);
566 JsonParse(Reader f) {
567 lexer = new JsonLexer(f);
571 public class AltosJson extends JsonUtil {
572 private static final int type_none = 0;
573 private static final int type_hash = 1;
574 private static final int type_array = 2;
575 private static final int type_double = 3;
576 private static final int type_long = 4;
577 private static final int type_string = 5;
578 private static final int type_boolean = 6;
582 private JsonHash hash;
583 private JsonArray array;
584 private double d_number;
585 private long l_number;
586 private String string;
587 private boolean bool;
589 /* Generate string representation of the value
591 Writer append(Writer result, int indent, boolean pretty) throws IOException {
594 hash.append_hash(result, indent, pretty);
597 array.append_array(result, indent, pretty);
600 if (Double.isInfinite(d_number)) {
602 result.append("NegInfinity");
604 result.append("Infinity");
605 } else if (Double.isNaN(d_number)) {
606 result.append("NaN");
608 String dval = nf_json.format(d_number);
609 if (dval.equals("-0"))
615 result.append(new Long(l_number).toString());
618 quote(result, string);
621 result.append(bool ? "true" : "false");
627 private String toString(int indent, boolean pretty) {
629 Writer result = new StringWriter();
630 append(result, indent, pretty);
631 return result.toString();
632 } catch (Exception e) {
637 public String toString() {
638 return toString(0, false);
641 public String toPrettyString() {
642 return toString(0, true);
645 public void write(Writer w, int indent, boolean pretty) throws IOException {
646 append(w, indent, pretty);
649 public void write(Writer w) throws IOException {
653 /* Parse string representation to a value
656 public static AltosJson fromString(String string) {
657 JsonParse parse = new JsonParse(string);
659 return parse.parse();
660 } catch (IllegalArgumentException ie) {
661 System.out.printf("json:\n%s\n%s\n", string, ie.getMessage());
666 public static AltosJson fromReader(Reader f) {
667 JsonParse parse = new JsonParse(f);
669 return parse.parse();
670 } catch (IllegalArgumentException ie) {
671 System.out.printf("json:\n%s\n", ie.getMessage());
676 /* Accessor functions
678 private boolean assert_type(boolean setting, int type, int other_type, String error) {
679 if (setting && this.type == type_none) {
683 if (this.type != type && this.type != other_type)
684 throw new IllegalArgumentException(error);
688 private boolean assert_type(boolean setting, int type, String error) {
689 return assert_type(setting, type, type, error);
692 private void assert_hash(boolean setting) {
693 if (!assert_type(setting, type_hash, "not a hash"))
694 hash = new JsonHash();
697 private void assert_array(boolean setting) {
698 if (!assert_type(setting, type_array, "not an array"))
699 array = new JsonArray();
702 private void assert_number() {
703 assert_type(false, type_double, type_long, "not a number");
706 private void assert_double() {
707 assert_type(true, type_double, type_long, "not a number");
710 private void assert_long() {
711 assert_type(true, type_long, type_double, "not a number");
714 private void assert_string(boolean setting) {
715 assert_type(setting, type_string, "not a string");
718 private void assert_boolean(boolean setting) {
719 assert_type(setting, type_boolean, "not a boolean");
722 /* Primitive accessors
724 public double number() {
726 if (type == type_double)
729 return (double) l_number;
732 public long l_number() {
734 if (type == type_double)
735 return (long) d_number;
740 public String string() {
741 assert_string(false);
745 public boolean bool() {
746 assert_boolean(false);
750 public AltosJson get(int index) {
752 return array.get(index);
755 public AltosJson get(String key) {
757 return hash.get(key);
765 /* Typed accessors with defaulting
767 public double get_double(String key, double def) {
768 AltosJson value = get(key);
770 return value.number();
775 public long get_long(String key, long def) {
776 AltosJson value = get(key);
778 return value.l_number();
782 public int get_int(String key, int def) {
783 AltosJson value = get(key);
785 return (int) value.l_number();
789 public String get_string(String key, String def) {
790 AltosJson value = get(key);
792 return value.string();
796 public boolean get_boolean(String key, boolean def) {
797 AltosJson value = get(key);
803 public double get_double(int index, double def) {
804 AltosJson value = get(index);
806 return value.number();
810 public long get_long(int index, long def) {
811 AltosJson value = get(index);
813 return value.l_number();
817 public int get_int(int index, int def) {
818 AltosJson value = get(index);
820 return (int) value.l_number();
824 public String get_string(int index, String def) {
825 AltosJson value = get(index);
827 return value.string();
831 public boolean get_boolean(int index, boolean def) {
832 AltosJson value = get(index);
838 public double[] get_double_array(String key, double[] def) {
839 AltosJson value = get(key);
841 double[] ret = new double[value.size()];
842 for (int i = 0; i < value.size(); i++)
843 ret[i] = value.get_double(i, def == null ? 0 : def[i]);
849 public int[] get_int_array(String key, int[] def) {
850 AltosJson value = get(key);
852 int[] ret = new int[value.size()];
853 for (int i = 0; i < value.size(); i++)
854 ret[i] = value.get_int(i, def == null ? 0 : def[i]);
860 /* Array setter functions
862 public AltosJson put(int index, AltosJson value) {
864 array.put(index, value);
868 public Object put(int index, Object value) {
871 array.put(index, new AltosJson(value));
875 public double put(int index, double value) {
877 array.put(index, new AltosJson(value));
881 public AltosJson put(int index, double[] value) {
884 array.put(index, new AltosJson(value));
889 public int[] put(int index, int[] value) {
892 array.put(index, new AltosJson(value));
897 public String put(int index, String value) {
900 array.put(index, new AltosJson(value));
905 public boolean put(int index, boolean value) {
907 array.put(index, new AltosJson(value));
911 /* Hash setter functions
913 public AltosJson put(String key, AltosJson value) {
915 hash.put(key, value);
919 public Object put(String key, Object value) {
922 hash.put(key, new AltosJson(value));
926 public double put(String key, double value) {
928 hash.put(key, new AltosJson(value));
932 public String put(String key, String value) {
935 hash.put(key, new AltosJson(value));
940 public boolean put(String key, boolean value) {
942 hash.put(key, new AltosJson(value));
946 public AltosJson[] put(String key, AltosJson[] value) {
949 hash.put(key, new AltosJson(value));
954 public double[] put(String key, double[] value) {
957 hash.put(key, new AltosJson(value));
962 public int[] put(String key, int[] value) {
965 hash.put(key, new AltosJson(value));
970 /* Primitive setter functions
972 public double put(double value) {
978 public byte put(byte value) {
984 public char put(char value) {
990 public int put(int value) {
996 public long put(long value) {
1002 public String put(String value) {
1003 assert_string(true);
1008 public boolean put(boolean value) {
1009 assert_boolean(true);
1014 private boolean isInnerClass(Class c) {
1015 for (Field field : c.getDeclaredFields())
1016 if (field.isSynthetic())
1021 /* Construct an object of the specified class from the JSON
1024 * This works as long as the structure is non-recursive, and
1025 * all inner classes are only members of their immediate outer
1028 private Object make(Class c, Class enclosing_class, Object enclosing_object) {
1030 if (c == Boolean.TYPE) {
1032 } else if (c == Byte.TYPE) {
1033 ret = (Byte) (byte) l_number();
1034 } else if (c == Character.TYPE) {
1035 ret = (Character) (char) l_number();
1036 } else if (c == Integer.TYPE) {
1037 ret = (Integer) (int) l_number();
1038 } else if (c == Long.TYPE) {
1040 } else if (c == Double.TYPE) {
1042 } else if (c == String.class) {
1044 } else if (c.isArray()) {
1045 assert_array(false);
1047 Class element_class = c.getComponentType();
1048 if (element_class == Boolean.TYPE) {
1049 boolean[] array = (boolean[]) Array.newInstance(element_class, size());
1050 for (int i = 0; i < array.length; i++)
1051 array[i] = (Boolean) get(i).make(element_class);
1053 } else if (element_class == Byte.TYPE) {
1054 byte[] array = (byte[]) Array.newInstance(element_class, size());
1055 for (int i = 0; i < array.length; i++)
1056 array[i] = (Byte) get(i).make(element_class);
1058 } else if (element_class == Character.TYPE) {
1059 char[] array = (char[]) Array.newInstance(element_class, size());
1060 for (int i = 0; i < array.length; i++)
1061 array[i] = (Character) get(i).make(element_class);
1063 } else if (element_class == Integer.TYPE) {
1064 int[] array = (int[]) Array.newInstance(element_class, size());
1065 for (int i = 0; i < array.length; i++)
1066 array[i] = (Integer) get(i).make(element_class);
1068 } else if (element_class == Long.TYPE) {
1069 long[] array = (long[]) Array.newInstance(element_class, size());
1070 for (int i = 0; i < array.length; i++)
1071 array[i] = (Long) get(i).make(element_class);
1073 } else if (element_class == Double.TYPE) {
1074 double[] array = (double[]) Array.newInstance(element_class, size());
1075 for (int i = 0; i < array.length; i++)
1076 array[i] = (Double) get(i).make(element_class);
1079 Object[] array = (Object[]) Array.newInstance(element_class, size());
1080 for (int i = 0; i < array.length; i++)
1081 array[i] = get(i).make(element_class);
1086 Object object = null;
1088 /* Inner classes have a hidden extra parameter
1089 * to the constructor. Assume that the enclosing object is
1090 * of the enclosing class and construct the object
1093 if (enclosing_class != null && isInnerClass(c)) {
1094 Constructor<?> ctor = ((Class<?>)c).getDeclaredConstructor((Class<?>) enclosing_class);
1095 object = ctor.newInstance(enclosing_object);
1097 object = c.newInstance();
1099 for (; c != Object.class; c = c.getSuperclass()) {
1100 for (Field field : c.getDeclaredFields()) {
1101 String fieldName = field.getName();
1102 Class fieldClass = field.getType();
1104 if (Modifier.isStatic(field.getModifiers()))
1106 if (field.isSynthetic())
1109 AltosJson json = get(fieldName);
1111 Object val = json.make(fieldClass, c, object);
1112 field.setAccessible(true);
1113 field.set(object, val);
1115 } catch (IllegalAccessException ie) {
1116 System.out.printf("%s:%s %s\n",
1117 c.getName(), fieldName, ie.toString());
1122 } catch (InvocationTargetException ie) {
1123 System.out.printf("%s: %s\n",
1124 c.getName(), ie.toString());
1126 } catch (NoSuchMethodException ie) {
1127 System.out.printf("%s: %s\n",
1128 c.getName(), ie.toString());
1130 } catch (InstantiationException ie) {
1131 System.out.printf("%s: %s\n",
1132 c.getName(), ie.toString());
1134 } catch (IllegalAccessException ie) {
1135 System.out.printf("%s: %s\n",
1136 c.getName(), ie.toString());
1143 /* This is the public API for the
1144 * above function which doesn't handle
1147 public Object make(Class c) {
1148 return make(c, null, null);
1151 /* Constructors, one for each primitive type, String and Object */
1152 public AltosJson(boolean bool) {
1153 type = type_boolean;
1157 public AltosJson(byte number) {
1159 this.l_number = number;
1162 public AltosJson(char number) {
1164 this.l_number = number;
1167 public AltosJson(int number) {
1169 this.l_number = number;
1172 public AltosJson(long number) {
1174 this.l_number = number;
1177 public AltosJson(double number) {
1179 this.d_number = number;
1182 public AltosJson(String string) {
1184 this.string = string;
1187 public AltosJson(Object object) {
1188 if (object instanceof Boolean) {
1189 type = type_boolean;
1190 bool = (Boolean) object;
1191 } else if (object instanceof Byte) {
1193 l_number = (Byte) object;
1194 } else if (object instanceof Character) {
1196 l_number = (Character) object;
1197 } else if (object instanceof Integer) {
1199 l_number = (Integer) object;
1200 } else if (object instanceof Long) {
1202 l_number = (Long) object;
1203 } else if (object instanceof Double) {
1205 d_number = (Double) object;
1206 } else if (object instanceof String) {
1208 string = (String) object;
1209 } else if (object.getClass().isArray()) {
1212 Class component_class = object.getClass().getComponentType();
1213 if (component_class == Boolean.TYPE) {
1214 boolean[] array = (boolean[]) object;
1215 for (int i = 0; i < array.length; i++)
1216 put(i, new AltosJson(array[i]));
1217 } else if (component_class == Byte.TYPE) {
1218 byte[] array = (byte[]) object;
1219 for (int i = 0; i < array.length; i++)
1220 put(i, new AltosJson(array[i]));
1221 } else if (component_class == Character.TYPE) {
1222 char[] array = (char[]) object;
1223 for (int i = 0; i < array.length; i++)
1224 put(i, new AltosJson(array[i]));
1225 } else if (component_class == Integer.TYPE) {
1226 int[] array = (int[]) object;
1227 for (int i = 0; i < array.length; i++)
1228 put(i, new AltosJson(array[i]));
1229 } else if (component_class == Long.TYPE) {
1230 long[] array = (long[]) object;
1231 for (int i = 0; i < array.length; i++)
1232 put(i, new AltosJson(array[i]));
1233 } else if (component_class == Double.TYPE) {
1234 double[] array = (double[]) object;
1235 for (int i = 0; i < array.length; i++)
1236 put(i, new AltosJson(array[i]));
1238 Object[] array = (Object[]) object;
1239 for (int i = 0; i < array.length; i++)
1240 put(i, new AltosJson(array[i]));
1244 for (Class c = object.getClass(); c != Object.class; c = c.getSuperclass()) {
1245 for (Field field : c.getDeclaredFields()) {
1246 String fieldName = field.getName();
1248 /* XXX hack to allow fields to be not converted */
1249 if (fieldName.startsWith("__"))
1252 /* Skip static fields */
1253 if (Modifier.isStatic(field.getModifiers()))
1256 /* Skip synthetic fields. We're assuming
1257 * those are always an inner class reference
1258 * to the outer class object
1260 if (field.isSynthetic())
1263 /* We may need to force the field to be accessible if
1266 field.setAccessible(true);
1267 Object val = field.get(object);
1269 AltosJson json = new AltosJson(val);
1270 put(fieldName, json);
1272 } catch (IllegalAccessException ie) {
1273 System.out.printf("%s:%s %s\n",
1274 c.getName(), fieldName, ie.toString());
1281 /* Array constructors, one for each primitive type, String and Object */
1282 public AltosJson(boolean[] bools) {
1284 for(int i = 0; i < bools.length; i++)
1285 put(i, new AltosJson(bools[i]));
1288 public AltosJson(byte[] numbers) {
1290 for(int i = 0; i < numbers.length; i++)
1291 put(i, new AltosJson(numbers[i]));
1294 public AltosJson(char[] numbers) {
1296 for(int i = 0; i < numbers.length; i++)
1297 put(i, new AltosJson(numbers[i]));
1300 public AltosJson(int[] numbers) {
1302 for(int i = 0; i < numbers.length; i++)
1303 put(i, new AltosJson(numbers[i]));
1306 public AltosJson(long[] numbers) {
1308 for(int i = 0; i < numbers.length; i++)
1309 put(i, new AltosJson(numbers[i]));
1312 public AltosJson(double[] numbers) {
1314 for(int i = 0; i < numbers.length; i++)
1315 put(i, new AltosJson(numbers[i]));
1318 public AltosJson(String[] strings) {
1320 for(int i = 0; i < strings.length; i++)
1321 put(i, new AltosJson(strings[i]));
1324 public AltosJson(AltosJson[] jsons) {
1326 for (int i = 0; i < jsons.length; i++)
1330 public AltosJson(Object[] array) {
1332 for (int i = 0; i < array.length; i++)
1333 put(i, new AltosJson(array[i]));
1336 /* Empty constructor
1338 public AltosJson() {
1342 public AltosJson(JsonHash hash) {
1347 public AltosJson(JsonArray array) {