blob: 9b297b7f8a0c64543750e71f68f9f54c0081586f [file] [log] [blame]
Stuart McCullochf3173222012-06-07 21:57:32 +00001package aQute.lib.json;
2
3import java.lang.reflect.*;
4import java.util.*;
5
6public 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 McCulloch669423b2012-06-26 16:34:24 +000014 ObjectHandler(@SuppressWarnings("unused") JSONCodec codec, Class< ? > c) throws Exception {
Stuart McCullochf3173222012-06-07 21:57:32 +000015 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 McCulloch4482c702012-06-15 13:27:53 +000038
Stuart McCullochf3173222012-06-07 21:57:32 +000039 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 McCulloch4482c702012-06-15 13:27:53 +000045 }
46 catch (Exception e) {
Stuart McCullochf3173222012-06-07 21:57:32 +000047 // Ignore
48 }
49 }
50
Stuart McCulloch4482c702012-06-15 13:27:53 +000051 @Override
52 void encode(Encoder app, Object object, Map<Object,Type> visited) throws Exception {
Stuart McCullochf3173222012-06-07 21:57:32 +000053 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 McCulloch4482c702012-06-15 13:27:53 +000077 @Override
78 Object decodeObject(Decoder r) throws Exception {
Stuart McCullochf3173222012-06-07 21:57:32 +000079 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 McCulloch4482c702012-06-15 13:27:53 +0000101 if (value != null || !r.codec.ignorenull)
Stuart McCulloch0b639c62012-06-12 12:41:16 +0000102 f.set(targetObject, value);
Stuart McCullochf3173222012-06-07 21:57:32 +0000103 } 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 McCulloch4482c702012-06-15 13:27:53 +0000112 Map<String,Object> map = (Map<String,Object>) extra.get(targetObject);
Stuart McCullochf3173222012-06-07 21:57:32 +0000113 if (map == null) {
Stuart McCulloch4482c702012-06-15 13:27:53 +0000114 map = new LinkedHashMap<String,Object>();
Stuart McCullochf3173222012-06-07 21:57:32 +0000115 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 McCulloch4482c702012-06-15 13:27:53 +0000132 throw new IllegalArgumentException("Invalid character in parsing object, expected } or , but found "
133 + (char) c);
Stuart McCullochf3173222012-06-07 21:57:32 +0000134 }
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}