Stuart McCulloch | bb01437 | 2012-06-07 21:57:32 +0000 | [diff] [blame] | 1 | package aQute.lib.json; |
| 2 | |
| 3 | import java.lang.reflect.*; |
| 4 | import java.util.*; |
| 5 | |
| 6 | public class ObjectHandler extends Handler { |
| 7 | @SuppressWarnings("rawtypes") |
| 8 | final Class rawClass; |
| 9 | final Field fields[]; |
| 10 | final Type types[]; |
| 11 | final Object defaults[]; |
| 12 | final Field extra; |
| 13 | |
| 14 | ObjectHandler(JSONCodec codec, Class<?> c) throws Exception { |
| 15 | rawClass = c; |
| 16 | fields = c.getFields(); |
| 17 | |
| 18 | // Sort the fields so the output is canonical |
| 19 | Arrays.sort(fields, new Comparator<Field>() { |
| 20 | public int compare(Field o1, Field o2) { |
| 21 | return o1.getName().compareTo(o2.getName()); |
| 22 | } |
| 23 | }); |
| 24 | |
| 25 | types = new Type[fields.length]; |
| 26 | defaults = new Object[fields.length]; |
| 27 | |
| 28 | Field x = null; |
| 29 | for (int i = 0; i < fields.length; i++) { |
| 30 | if (fields[i].getName().equals("__extra")) |
| 31 | x = fields[i]; |
| 32 | types[i] = fields[i].getGenericType(); |
| 33 | } |
| 34 | if (x != null && Map.class.isAssignableFrom(x.getType())) |
| 35 | extra = x; |
| 36 | else |
| 37 | extra = null; |
| 38 | |
| 39 | try { |
| 40 | Object template = c.newInstance(); |
| 41 | |
| 42 | for (int i = 0; i < fields.length; i++) { |
| 43 | defaults[i] = fields[i].get(template); |
| 44 | } |
| 45 | } catch (Exception e) { |
| 46 | // Ignore |
| 47 | } |
| 48 | } |
| 49 | |
| 50 | @Override void encode(Encoder app, Object object, Map<Object, Type> visited) throws Exception { |
| 51 | app.append("{"); |
| 52 | String del = ""; |
| 53 | for (int i = 0; i < fields.length; i++) { |
| 54 | if (fields[i].getName().startsWith("__")) |
| 55 | continue; |
| 56 | |
| 57 | Object value = fields[i].get(object); |
| 58 | if (!app.writeDefaults) { |
| 59 | if (value == defaults[i]) |
| 60 | continue; |
| 61 | |
| 62 | if (value != null && value.equals(defaults[i])) |
| 63 | continue; |
| 64 | } |
| 65 | |
| 66 | app.append(del); |
| 67 | StringHandler.string(app, fields[i].getName()); |
| 68 | app.append(":"); |
| 69 | app.encode(value, types[i], visited); |
| 70 | del = ","; |
| 71 | } |
| 72 | app.append("}"); |
| 73 | } |
| 74 | |
| 75 | @SuppressWarnings("unchecked") @Override Object decodeObject(Decoder r) throws Exception { |
| 76 | assert r.current() == '{'; |
| 77 | Object targetObject = rawClass.newInstance(); |
| 78 | |
| 79 | int c = r.next(); |
| 80 | while (JSONCodec.START_CHARACTERS.indexOf(c) >= 0) { |
| 81 | |
| 82 | // Get key |
| 83 | String key = r.codec.parseString(r); |
| 84 | |
| 85 | // Get separator |
| 86 | c = r.skipWs(); |
| 87 | if (c != ':') |
| 88 | throw new IllegalArgumentException("Expected ':' but got " + (char) c); |
| 89 | |
| 90 | c = r.next(); |
| 91 | |
| 92 | // Get value |
| 93 | |
| 94 | Field f = getField(key); |
| 95 | if (f != null) { |
| 96 | // We have a field and thus a type |
| 97 | Object value = r.codec.decode(f.getGenericType(), r); |
| 98 | f.set(targetObject, value); |
| 99 | } else { |
| 100 | // No field, but may extra is defined |
| 101 | if (extra == null) { |
| 102 | if (r.strict) |
| 103 | throw new IllegalArgumentException("No such field " + key); |
| 104 | Object value = r.codec.decode(null, r); |
| 105 | r.getExtra().put(rawClass.getName() + "." + key, value); |
| 106 | } else { |
| 107 | |
| 108 | Map<String, Object> map = (Map<String, Object>) extra.get(targetObject); |
| 109 | if (map == null) { |
| 110 | map = new LinkedHashMap<String, Object>(); |
| 111 | extra.set(targetObject, map); |
| 112 | } |
| 113 | Object value = r.codec.decode(null, r); |
| 114 | map.put(key, value); |
| 115 | } |
| 116 | } |
| 117 | |
| 118 | c = r.skipWs(); |
| 119 | |
| 120 | if (c == '}') |
| 121 | break; |
| 122 | |
| 123 | if (c == ',') { |
| 124 | c = r.next(); |
| 125 | continue; |
| 126 | } |
| 127 | |
| 128 | throw new IllegalArgumentException( |
| 129 | "Invalid character in parsing object, expected } or , but found " + (char) c); |
| 130 | } |
| 131 | assert r.current() == '}'; |
| 132 | r.read(); // skip closing |
| 133 | return targetObject; |
| 134 | } |
| 135 | |
| 136 | private Field getField(String key) { |
| 137 | for (int i = 0; i < fields.length; i++) { |
| 138 | int n = key.compareTo(fields[i].getName()); |
| 139 | if (n == 0) |
| 140 | return fields[i]; |
| 141 | if (n < 0) |
| 142 | return null; |
| 143 | } |
| 144 | return null; |
| 145 | } |
| 146 | |
| 147 | } |