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 | |
Stuart McCulloch | d482610 | 2012-06-26 16:34:24 +0000 | [diff] [blame] | 14 | ObjectHandler(@SuppressWarnings("unused") JSONCodec codec, Class< ? > c) throws Exception { |
Stuart McCulloch | bb01437 | 2012-06-07 21:57:32 +0000 | [diff] [blame] | 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; |
Stuart McCulloch | 2286f23 | 2012-06-15 13:27:53 +0000 | [diff] [blame] | 38 | |
Stuart McCulloch | bb01437 | 2012-06-07 21:57:32 +0000 | [diff] [blame] | 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 | } |
Stuart McCulloch | 2286f23 | 2012-06-15 13:27:53 +0000 | [diff] [blame] | 45 | } |
| 46 | catch (Exception e) { |
Stuart McCulloch | bb01437 | 2012-06-07 21:57:32 +0000 | [diff] [blame] | 47 | // Ignore |
| 48 | } |
| 49 | } |
| 50 | |
Stuart McCulloch | 2286f23 | 2012-06-15 13:27:53 +0000 | [diff] [blame] | 51 | @Override |
| 52 | void encode(Encoder app, Object object, Map<Object,Type> visited) throws Exception { |
Stuart McCulloch | bb01437 | 2012-06-07 21:57:32 +0000 | [diff] [blame] | 53 | app.append("{"); |
| 54 | String del = ""; |
| 55 | for (int i = 0; i < fields.length; i++) { |
| 56 | if (fields[i].getName().startsWith("__")) |
| 57 | continue; |
| 58 | |
| 59 | Object value = fields[i].get(object); |
| 60 | if (!app.writeDefaults) { |
| 61 | if (value == defaults[i]) |
| 62 | continue; |
| 63 | |
| 64 | if (value != null && value.equals(defaults[i])) |
| 65 | continue; |
| 66 | } |
| 67 | |
| 68 | app.append(del); |
| 69 | StringHandler.string(app, fields[i].getName()); |
| 70 | app.append(":"); |
| 71 | app.encode(value, types[i], visited); |
| 72 | del = ","; |
| 73 | } |
| 74 | app.append("}"); |
| 75 | } |
| 76 | |
Stuart McCulloch | 2286f23 | 2012-06-15 13:27:53 +0000 | [diff] [blame] | 77 | @Override |
| 78 | Object decodeObject(Decoder r) throws Exception { |
Stuart McCulloch | bb01437 | 2012-06-07 21:57:32 +0000 | [diff] [blame] | 79 | assert r.current() == '{'; |
| 80 | Object targetObject = rawClass.newInstance(); |
| 81 | |
| 82 | int c = r.next(); |
| 83 | while (JSONCodec.START_CHARACTERS.indexOf(c) >= 0) { |
| 84 | |
| 85 | // Get key |
| 86 | String key = r.codec.parseString(r); |
| 87 | |
| 88 | // Get separator |
| 89 | c = r.skipWs(); |
| 90 | if (c != ':') |
| 91 | throw new IllegalArgumentException("Expected ':' but got " + (char) c); |
| 92 | |
| 93 | c = r.next(); |
| 94 | |
| 95 | // Get value |
| 96 | |
| 97 | Field f = getField(key); |
| 98 | if (f != null) { |
| 99 | // We have a field and thus a type |
| 100 | Object value = r.codec.decode(f.getGenericType(), r); |
Stuart McCulloch | 2286f23 | 2012-06-15 13:27:53 +0000 | [diff] [blame] | 101 | if (value != null || !r.codec.ignorenull) |
Stuart McCulloch | 285034f | 2012-06-12 12:41:16 +0000 | [diff] [blame] | 102 | f.set(targetObject, value); |
Stuart McCulloch | bb01437 | 2012-06-07 21:57:32 +0000 | [diff] [blame] | 103 | } else { |
| 104 | // No field, but may extra is defined |
| 105 | if (extra == null) { |
| 106 | if (r.strict) |
| 107 | throw new IllegalArgumentException("No such field " + key); |
| 108 | Object value = r.codec.decode(null, r); |
| 109 | r.getExtra().put(rawClass.getName() + "." + key, value); |
| 110 | } else { |
| 111 | |
Stuart McCulloch | 2286f23 | 2012-06-15 13:27:53 +0000 | [diff] [blame] | 112 | Map<String,Object> map = (Map<String,Object>) extra.get(targetObject); |
Stuart McCulloch | bb01437 | 2012-06-07 21:57:32 +0000 | [diff] [blame] | 113 | if (map == null) { |
Stuart McCulloch | 2286f23 | 2012-06-15 13:27:53 +0000 | [diff] [blame] | 114 | map = new LinkedHashMap<String,Object>(); |
Stuart McCulloch | bb01437 | 2012-06-07 21:57:32 +0000 | [diff] [blame] | 115 | extra.set(targetObject, map); |
| 116 | } |
| 117 | Object value = r.codec.decode(null, r); |
| 118 | map.put(key, value); |
| 119 | } |
| 120 | } |
| 121 | |
| 122 | c = r.skipWs(); |
| 123 | |
| 124 | if (c == '}') |
| 125 | break; |
| 126 | |
| 127 | if (c == ',') { |
| 128 | c = r.next(); |
| 129 | continue; |
| 130 | } |
| 131 | |
Stuart McCulloch | 2286f23 | 2012-06-15 13:27:53 +0000 | [diff] [blame] | 132 | throw new IllegalArgumentException("Invalid character in parsing object, expected } or , but found " |
| 133 | + (char) c); |
Stuart McCulloch | bb01437 | 2012-06-07 21:57:32 +0000 | [diff] [blame] | 134 | } |
| 135 | assert r.current() == '}'; |
| 136 | r.read(); // skip closing |
| 137 | return targetObject; |
| 138 | } |
| 139 | |
| 140 | private Field getField(String key) { |
| 141 | for (int i = 0; i < fields.length; i++) { |
| 142 | int n = key.compareTo(fields[i].getName()); |
| 143 | if (n == 0) |
| 144 | return fields[i]; |
| 145 | if (n < 0) |
| 146 | return null; |
| 147 | } |
| 148 | return null; |
| 149 | } |
| 150 | |
| 151 | } |