package aQute.lib.json; import java.lang.reflect.Field; import java.lang.reflect.Modifier; import java.lang.reflect.Type; import java.util.ArrayList; import java.util.Arrays; import java.util.Comparator; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; public class ObjectHandler extends Handler { @SuppressWarnings("rawtypes") final Class rawClass; final Field fields[]; final Type types[]; final Object defaults[]; final Field extra; ObjectHandler(JSONCodec codec, Class< ? > c) throws Exception { rawClass = c; List<Field> fields = new ArrayList<Field>(); for (Field f : c.getFields()) { if (Modifier.isStatic(f.getModifiers())) continue; fields.add(f); } this.fields = fields.toArray(new Field[0]); // Sort the fields so the output is canonical Arrays.sort(this.fields, new Comparator<Field>() { public int compare(Field o1, Field o2) { return o1.getName().compareTo(o2.getName()); } }); types = new Type[this.fields.length]; defaults = new Object[this.fields.length]; Field x = null; for (int i = 0; i < this.fields.length; i++) { if (this.fields[i].getName().equals("__extra")) x = this.fields[i]; types[i] = this.fields[i].getGenericType(); } if (x != null && Map.class.isAssignableFrom(x.getType())) extra = x; else extra = null; try { Object template = c.getConstructor().newInstance(); for (int i = 0; i < this.fields.length; i++) { defaults[i] = this.fields[i].get(template); } } catch (Exception e) { // Ignore } } @Override public void encode(Encoder app, Object object, Map<Object,Type> visited) throws Exception { app.append("{"); app.indent(); String del = ""; for (int i = 0; i < fields.length; i++) try { if (fields[i].getName().startsWith("__")) continue; Object value = fields[i].get(object); if (!app.writeDefaults) { if (value == defaults[i]) continue; if (value != null && value.equals(defaults[i])) continue; } app.append(del); StringHandler.string(app, fields[i].getName()); app.append(":"); app.encode(value, types[i], visited); del = ","; } catch (Exception e) { throw new IllegalArgumentException(fields[i].getName() + ":", e); } app.undent(); app.append("}"); } @Override public Object decodeObject(Decoder r) throws Exception { assert r.current() == '{'; @SuppressWarnings("unchecked") Object targetObject = rawClass.getConstructor().newInstance(); int c = r.next(); while (JSONCodec.START_CHARACTERS.indexOf(c) >= 0) { // Get key String key = r.codec.parseString(r); // Get separator c = r.skipWs(); if (c != ':') throw new IllegalArgumentException("Expected ':' but got " + (char) c); c = r.next(); // Get value Field f = getField(key); if (f != null) { // We have a field and thus a type Object value = r.codec.decode(f.getGenericType(), r); if (value != null || !r.codec.ignorenull) { if (Modifier.isFinal(f.getModifiers())) throw new IllegalArgumentException("Field " + f + " is final"); f.set(targetObject, value); } } else { // No field, but may extra is defined if (extra == null) { if (r.strict) throw new IllegalArgumentException("No such field " + key); Object value = r.codec.decode(null, r); r.getExtra().put(rawClass.getName() + "." + key, value); } else { @SuppressWarnings("unchecked") Map<String,Object> map = (Map<String,Object>) extra.get(targetObject); if (map == null) { map = new LinkedHashMap<String,Object>(); extra.set(targetObject, map); } Object value = r.codec.decode(null, r); map.put(key, value); } } c = r.skipWs(); if (c == '}') break; if (c == ',') { c = r.next(); continue; } throw new IllegalArgumentException( "Invalid character in parsing object, expected } or , but found " + (char) c); } assert r.current() == '}'; r.read(); // skip closing return targetObject; } private Field getField(String key) { for (int i = 0; i < fields.length; i++) { int n = key.compareTo(fields[i].getName()); if (n == 0) return fields[i]; if (n < 0) return null; } return null; } }