All Downloads are FREE. Search and download functionalities are using the official Maven repository.

aQute.lib.json.ObjectHandler Maven / Gradle / Ivy

The newest version!
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.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Supplier;

public class ObjectHandler extends Handler {
	@SuppressWarnings("rawtypes")
	final Class		rawClass;
	final Field		fields[];
	final Type		types[];
	final Object	defaults[];
	final Field		extra;
	final Supplier	factory;

	ObjectHandler(JSONCodec codec, Class c) throws Exception {
		rawClass = c;
		factory = newInstanceFunction(c);
		List fields = new ArrayList<>();
		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, (o1, o2) -> 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 = factory.get();

			for (int i = 0; i < this.fields.length; i++) {
				defaults[i] = getField(this.fields[i], template);
			}
		} catch (Exception e) {
			// Ignore
		}
	}

	@Override
	public void encode(Encoder app, Object object, Map visited) throws Exception {
		app.append("{");
		app.indent();
		String del = "";
		for (int i = 0; i < fields.length; i++)
			try {
				Field field = fields[i];
				String actualName = JSONCodec.keyword(field.getName());
				if (actualName.startsWith("__"))
					continue;

				Object value = getField(fields[i], object);
				if (!app.writeDefaults) {
					if (value == defaults[i])
						continue;

					if (value != null && value.equals(defaults[i]))
						continue;
				}

				app.append(del);
				if (!del.isEmpty()) {
					app.linebreak();
				}
				StringHandler.string(app, actualName);
				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 = factory.get();

		int c = r.next();
		while (r.codec.isStartCharacter(c)) {

			// 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");

					setField(f, 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 map = (Map) getField(extra, targetObject);
					if (map == null) {
						map = new LinkedHashMap<>();
						setField(extra, 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;
			}

			if (r.codec.promiscuous && r.isEof()) {
				r.codec.fishy.incrementAndGet();
				return targetObject;
			}

			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 (Field field : fields) {
			if (key.equals(field.getName()))
				return field;
		}
		String fixup = JSONCodec.name(key);
		for (Field field : fields) {
			if (fixup.equals(field.getName()))
				return field;
		}
		return null;
	}

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy