JsonMagic.java

/***************************************************************************
   Copyright 2013 Emily Estes

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
***************************************************************************/
package net.metanotion.json;


import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;

import org.joda.time.ReadableInstant;

import net.metanotion.util.Dictionary;
import net.metanotion.util.reflect.GetInitializer;
import net.metanotion.util.reflect.Initializer;
import net.metanotion.util.reflect.InitializerIsomorphism;
import net.metanotion.util.reflect.ReflectiveFieldInitializer;
import net.metanotion.util.types.JsonMagicTypeDictionary;
import net.metanotion.util.types.ToJsonMagicP;
import net.metanotion.util.types.Parser;
import net.metanotion.util.types.TypeDictionary;

/** A JSON literal/object to plain Java object transcoder. This class takes a Java class and creates a JSON
marshaller/unmarshaller to convert between JSON representations and Java objects.
	@param <S> The type of the Java object this class transcodes.
*/
public final class JsonMagic<S> {
	private static final Dictionary<Class,Parser> fromJson = new JsonMagicTypeDictionary();
	private static final Dictionary<Class,ToJsonMagicP> toJson = new Dictionary<Class,ToJsonMagicP>() {
		private final Map<Class,ToJsonMagicP> toMap = new HashMap<>();
		private final TypeDictionary td = new TypeDictionary();
		@Override public ToJsonMagicP get(Class c) {
			if(td.get(c) != null) { return null; }
			final ToJsonMagicP p = new ToJsonMagicP(c);
			toMap.put(c, p);
			return p;
		}
	};

	private static Object parseValue(final Object value) {
		if(value instanceof Object[]) {
			final JsonArray ja = new JsonArray();
			for(final Object e: (Object[]) value) {
				final ToJsonMagicP p = toJson.get(e.getClass());
				ja.add((p != null) ? p.parse(e) : e);
			}
			return ja;
		} else if(value instanceof Iterable) {
			final JsonArray ja = new JsonArray();
			for(final Object e: (Iterable) value) {
				final ToJsonMagicP p = toJson.get(e.getClass());
				ja.add((p != null) ? p.parse(e) : e);
			}
			return ja;
		} else if (value instanceof Map) {
			final JsonObject jo = new JsonObject();
			final java.util.Set<Map.Entry> eSet = ((Map) value).entrySet();
			for(final Map.Entry e: eSet) {
				final Object v = e.getValue();
				final ToJsonMagicP p = toJson.get(v.getClass());
				jo.put(e.getKey().toString(), (p != null) ? p.parse(v) : v);
			}
			return jo;
		} else if(value instanceof ReadableInstant) {
			return value.toString();
		} else if(value instanceof Number) {
			if ((value instanceof Long)
				|| (value instanceof Integer)
				|| (value instanceof Double)
				|| (value instanceof Float)) {
					return value;
			} else if (value instanceof AtomicLong) {
				return ((AtomicLong) value).longValue();
			} else if (value instanceof AtomicInteger) {
				return ((AtomicInteger) value).intValue();
			} else if ((value instanceof Byte) || (value instanceof Short)) {
				return ((Number) value).intValue();
			} else {
				return value.toString();
			}
		} else if(value != null) {
			final ToJsonMagicP p = toJson.get(value.getClass());
			return (p != null) ? p.parse(value) : value;
		} else {
			return null;
		}
	}

	private static final class JsonInitializer implements GetInitializer<JsonObject> {
		private final GetInitializer keys;
		public JsonInitializer(final GetInitializer keys) { this.keys = keys; }
		@Override public Iterator<String> iterator() { return keys.iterator(); }
		@Override public Initializer<JsonObject> initializer() {
			return new Initializer<JsonObject>() {
				private final JsonObject o = new JsonObject();
				@Override public void put(String name, Object value) {
					try {
						o.put(name, parseValue(value));
					} catch (Exception e) { throw new RuntimeException(e); }
				}
				@Override public JsonObject instance() { return o; }
			};
		}
		@Override public Dictionary<String,Object> struct(final JsonObject instance) {
			return new Dictionary<String,Object>() {
				@Override public Object get(String key) { return instance.get(key); }
			};
		}
		@Override public String type(final String field) { return keys.type(field); }
	}

	private final InitializerIsomorphism<S,JsonObject> iso;

	/** Create a JSON/"plain Java object" transcoder.
		@param klazz The plain Java object to use as a template for the transcoder.
	*/
	public JsonMagic(final Class<S> klazz) {
		final GetInitializer<S> gi = ReflectiveFieldInitializer.getInitializer(klazz,fromJson);
		final GetInitializer<JsonObject> ji = new JsonInitializer(gi);
		this.iso = new InitializerIsomorphism(gi, ji);
 	}

	/** Transcode a JSON string literal into an instance of a Java object.
		@param json The JSON string literal to unmarshal.
		@return An instance of a Java object equivalent to the JSON literal.
	*/
	public S toStruct(final String json) {
		try {
			return this.toStruct(JsonObject.read(json));
		} catch (Exception e) { throw new RuntimeException(e); }
	}

	/** Transcode a representation of a JSON encoded value into an instance of a Java object.
		@param json The representation of the JSON object to unmarshal.
		@return An instance of a Java object equivalent to the JSON object.
	*/
	public S toStruct(final JsonObject json) { return iso.a(json); }

	/** Transcode a Java object into a JSON object.
		@param struct The Java object to convert.
		@return An object representing the JSON encoded version of the parameter.
	*/
	public JsonObject toJSON(final S struct) { return iso.b(struct); }
}