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

io.datakernel.util.gson.GsonAdapters Maven / Gradle / Ivy

package io.datakernel.util.gson;

import com.google.gson.TypeAdapter;
import com.google.gson.internal.Streams;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonWriter;

import java.io.IOException;
import java.io.StringReader;
import java.io.StringWriter;
import java.io.Writer;
import java.lang.reflect.Constructor;
import java.lang.reflect.Modifier;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.time.LocalDate;
import java.util.*;
import java.util.function.Function;

import static io.datakernel.util.Preconditions.checkArgument;
import static io.datakernel.util.Preconditions.checkState;

@SuppressWarnings("unchecked")
public final class GsonAdapters {

	public static final TypeAdapter BOOLEAN_JSON = new TypeAdapter() {
		@Override
		public void write(JsonWriter out, Boolean value) throws IOException {
			out.value(value);
		}

		@Override
		public Boolean read(JsonReader in) throws IOException {
			return in.nextBoolean();
		}
	};

	public static final TypeAdapter CHARACTER_JSON = new TypeAdapter() {
		@Override
		public void write(JsonWriter out, Character value) throws IOException {
			out.value(value);
		}

		@Override
		public Character read(JsonReader in) throws IOException {
			String v = in.nextString();
			checkArgument(v.length() == 1);
			return v.charAt(0);
		}
	};

	public static final TypeAdapter BYTE_JSON = new TypeAdapter() {
		@Override
		public void write(JsonWriter out, Byte value) throws IOException {
			out.value(value);
		}

		@Override
		public Byte read(JsonReader in) throws IOException {
			int v = in.nextInt();
			checkArgument(v >= 0 && v <= 0xFF);
			return (byte) v;
		}
	};

	public static final TypeAdapter SHORT_JSON = new TypeAdapter() {
		@Override
		public void write(JsonWriter out, Short value) throws IOException {
			out.value(value);
		}

		@Override
		public Short read(JsonReader in) throws IOException {
			int v = in.nextInt();
			checkArgument(v >= 0 && v <= 0xFFFF);
			return (short) in.nextInt();
		}
	};

	public static final TypeAdapter INTEGER_JSON = new TypeAdapter() {
		@Override
		public void write(JsonWriter out, Integer value) throws IOException {
			out.value(value);
		}

		@Override
		public Integer read(JsonReader in) throws IOException {
			return in.nextInt();
		}
	};

	public static final TypeAdapter LONG_JSON = new TypeAdapter() {
		@Override
		public void write(JsonWriter out, Long value) throws IOException {
			out.value(value);
		}

		@Override
		public Long read(JsonReader in) throws IOException {
			return in.nextLong();
		}
	};

	public static final TypeAdapter FLOAT_JSON = new TypeAdapter() {
		@Override
		public void write(JsonWriter out, Float value) throws IOException {
			out.value(value);
		}

		@Override
		public Float read(JsonReader in) throws IOException {
			return (float) in.nextDouble();
		}
	};

	public static final TypeAdapter DOUBLE_JSON = new TypeAdapter() {
		@Override
		public void write(JsonWriter out, Double value) throws IOException {
			out.value(value);
		}

		@Override
		public Double read(JsonReader in) throws IOException {
			return in.nextDouble();
		}
	};

	public static final TypeAdapter STRING_JSON = new TypeAdapter() {
		@Override
		public void write(JsonWriter out, String value) throws IOException {
			out.value(value);
		}

		@Override
		public String read(JsonReader in) throws IOException {
			return in.nextString();
		}
	};

	public static final TypeAdapter LOCAL_DATE_JSON = new TypeAdapter() {
		@Override
		public void write(JsonWriter out, LocalDate value) throws IOException {
			out.value(value.toString());
		}

		@Override
		public LocalDate read(JsonReader in) throws IOException {
			return LocalDate.parse(in.nextString());
		}
	};

	public static final TypeAdapter> CLASS_JSON = new TypeAdapter>() {
		@Override
		public void write(JsonWriter out, Class value) throws IOException {
			out.value(value.getName());
		}

		@Override
		public Class read(JsonReader in) throws IOException {
			try {
				return (Class) Class.forName(in.nextString());
			} catch (ClassNotFoundException e) {
				throw new IOException(e);
			}
		}
	};

	public interface TypeAdapterMapping {

		 TypeAdapter getAdapter(Type type);

		default  String toJson(T obj) {
			return GsonAdapters.toJson(getAdapter((Class) obj.getClass()), obj);
		}
	}

	public static class TypeAdapterMappingImpl implements TypeAdapterMapping {

		@FunctionalInterface
		public interface AdapterSupplier {

			TypeAdapter get(Class cls, TypeAdapter[] paramAdapters);
		}

		private final Map, AdapterSupplier> mapping = new HashMap<>();

		private TypeAdapterMappingImpl() {}

		public static TypeAdapterMappingImpl create() {
			return new TypeAdapterMappingImpl();
		}

		public static TypeAdapterMappingImpl from(TypeAdapterMappingImpl fallback) {
			TypeAdapterMappingImpl tam = new TypeAdapterMappingImpl();
			tam.mapping.putAll(fallback.mapping);
			return tam;
		}

		@Override
		public  TypeAdapter getAdapter(Type type) {
			Class cls;
			TypeAdapter[] paramAdapters;
			if (type instanceof ParameterizedType) {
				ParameterizedType parameterized = (ParameterizedType) type;
				Type[] typeArgs = parameterized.getActualTypeArguments();
				cls = (Class) parameterized.getRawType();
				paramAdapters = new TypeAdapter[typeArgs.length];
				for (int i = 0; i < typeArgs.length; i++) {
					Type arg = typeArgs[i];
					checkState(arg != type, "Mapping does not support recurring generics!");
					paramAdapters[i] = getAdapter(arg);
				}
			} else {
				cls = (Class) type;
				paramAdapters = new TypeAdapter[0];
			}
			AdapterSupplier func = mapping.get(cls);
			if (func != null) {
				return (TypeAdapter) func.get(cls, paramAdapters);
			}
			for (Map.Entry, AdapterSupplier> entry : mapping.entrySet()) {
				if (entry.getKey().isAssignableFrom(cls)) {
					return (TypeAdapter) entry.getValue().get(cls, paramAdapters);
				}
			}
			throw new IllegalArgumentException("Type " + type.getTypeName() + " is not registered for this mapping!");
		}

		public TypeAdapterMappingImpl withAdapter(Class type, TypeAdapter adapter) {
			mapping.put(type, ($1, $2) -> adapter);
			return this;
		}

		public TypeAdapterMappingImpl withAdapter(Class type, AdapterSupplier supplier) {
			mapping.put(type, supplier);
			return this;
		}
	}

	public static final TypeAdapterMappingImpl PRIMITIVES_MAP = TypeAdapterMappingImpl.create()
		.withAdapter(boolean.class, BOOLEAN_JSON).withAdapter(Boolean.class, BOOLEAN_JSON)
		.withAdapter(char.class, CHARACTER_JSON).withAdapter(Character.class, CHARACTER_JSON)
		.withAdapter(byte.class, BYTE_JSON).withAdapter(Byte.class, BYTE_JSON)
		.withAdapter(short.class, SHORT_JSON).withAdapter(Short.class, SHORT_JSON)
		.withAdapter(int.class, INTEGER_JSON).withAdapter(Integer.class, INTEGER_JSON)
		.withAdapter(long.class, LONG_JSON).withAdapter(Long.class, LONG_JSON)
		.withAdapter(float.class, FLOAT_JSON).withAdapter(Float.class, FLOAT_JSON)
		.withAdapter(double.class, DOUBLE_JSON).withAdapter(Double.class, DOUBLE_JSON)
		.withAdapter(String.class, STRING_JSON)
		.withAdapter(Enum.class, (cls, $) -> ofEnum((Class) cls))
		.withAdapter(List.class, ($, paramAdapters) -> {
			checkArgument(paramAdapters.length == 1);
			return ofList(paramAdapters[0]);
		})
		.withAdapter(Set.class, ($, paramAdapters) -> {
			checkArgument(paramAdapters.length == 1);
			return ofSet(paramAdapters[0]);
		})
		.withAdapter(Map.class, ($, paramAdapters) -> {
			checkArgument(paramAdapters.length == 2);
			checkArgument(paramAdapters[0] == STRING_JSON, "Map key type should be string!");
			return ofMap(paramAdapters[1]);
		});

	// copied from GsonSubclassesAdapter
	// makes instantiation of stateless anonymous classes possible
	// yet still is a pretty bad thing to do
	static  T newInstance(String className) {
		try {
			Class cls = Class.forName(className);
			boolean isStatic = (cls.getModifiers() & Modifier.STATIC) != 0;
			Class enclosingClass = cls.getEnclosingClass();
			if (isStatic || enclosingClass == null) {
				Constructor ctor = cls.getDeclaredConstructor();
				ctor.setAccessible(true);
				return (T) ctor.newInstance();
			}
			Constructor ctor = cls.getDeclaredConstructor(enclosingClass);
			ctor.setAccessible(true);
			return (T) ctor.newInstance((Object) null);
		}catch(Exception e) {
			throw new AssertionError(e);
		}
	}

	public static  TypeAdapter stateless() {
		return new TypeAdapter() {
			@Override
			public void write(JsonWriter out, T value) throws IOException {
				out.value(value.getClass().getName());
			}

			@Override
			public T read(JsonReader in) throws IOException {
				return newInstance(in.nextString());
			}
		};
	}

	public static  TypeAdapter stateless(T... singletons) {
		Map mapped = new HashMap<>();
		for(T singleton : singletons) {
			mapped.put(singleton.getClass().getName(), singleton);
		}
		return new TypeAdapter() {
			@Override
			public void write(JsonWriter out, T value) throws IOException {
				out.value(value.getClass().getName());
			}

			@Override
			public T read(JsonReader in) throws IOException {
				String name = in.nextString();
				T singleton = mapped.get(name);
				if (singleton != null) {
					return singleton;
				}
				return newInstance(name);
			}
		};
	}

	public static > TypeAdapter ofEnum(Class enumType) {
		return new TypeAdapter() {
			@Override
			public void write(JsonWriter out, E value) throws IOException {
				out.value(value.name());
			}

			@Override
			public E read(JsonReader in) throws IOException {
				return Enum.valueOf(enumType, in.nextString());
			}
		};
	}

/*
	public static TypeAdapter ofBytes() {
		return new TypeAdapter() {
			@Override
			public void write(JsonWriter out, byte[] value) throws IOException {
				out.value(Base64.getEncoder().encodeToString(value));
			}

			@Override
			public byte[] read(JsonReader in) throws IOException {
				String string = in.nextString();
				return Base64.getDecoder().decode(string);
			}
		};
	}
*/

	public interface FunctionIO {

		O convert(I in) throws IOException;
	}

	public static  TypeAdapter transform(TypeAdapter adapter, FunctionIO from, FunctionIO to) {
		return new TypeAdapter() {
			@Override
			public void write(JsonWriter out, O value) throws IOException {
				adapter.write(out, to.convert(value));
			}

			@Override
			public O read(JsonReader in) throws IOException {
				return from.convert(adapter.read(in));
			}
		};
	}

	public static abstract class AbstractCollectionJson, T> extends TypeAdapter {
		private final TypeAdapter elementTypeAdapter;

		public AbstractCollectionJson(TypeAdapter elementTypeAdapter) {
			this.elementTypeAdapter = elementTypeAdapter;
		}

		protected abstract C createCollection();

		@Override
		public C read(JsonReader in) throws IOException {
			C collection = createCollection();
			in.beginArray();
			while (in.hasNext()) {
				T instance = elementTypeAdapter.read(in);
				collection.add(instance);
			}
			in.endArray();
			return collection;
		}

		@Override
		public void write(JsonWriter out, C collection) throws IOException {
			out.beginArray();
			for (T element : collection) {
				elementTypeAdapter.write(out, element);
			}
			out.endArray();
		}
	}

	public static  TypeAdapter> ofList(TypeAdapter elementAdapter) {
		return new AbstractCollectionJson, T>(elementAdapter) {
			@Override
			protected List createCollection() {
				return new ArrayList<>();
			}
		};
	}

	public static  TypeAdapter> ofSet(TypeAdapter elementAdapter) {
		return new AbstractCollectionJson, T>(elementAdapter) {
			@Override
			protected Set createCollection() {
				return new LinkedHashSet<>();
			}
		};
	}

	public static  TypeAdapter ofArray(TypeAdapter elementAdapter) {
		return transform(ofList(elementAdapter), value -> (T[]) value.toArray(), Arrays::asList);
	}

	public static  TypeAdapter> ofMap(TypeAdapter valueAdapter) {
		return new TypeAdapter>() {
			@Override
			public void write(JsonWriter out, Map map) throws IOException {
				out.beginObject();
				for (Map.Entry entry : map.entrySet()) {
					out.name(entry.getKey());
					valueAdapter.write(out, entry.getValue());
				}
				out.endObject();
			}

			@Override
			public Map read(JsonReader in) throws IOException {
				Map map = new LinkedHashMap<>();
				in.beginObject();
				while (in.hasNext()) {
					String key = in.nextName();
					V value = valueAdapter.read(in);
					map.put(key, value);
				}
				in.endObject();
				return map;
			}
		};
	}

	public static  TypeAdapter> ofMap(Function to, Function from, TypeAdapter valueAdapter) {
		return new TypeAdapter>() {
			@Override
			public void write(JsonWriter out, Map map) throws IOException {
				out.beginObject();
				for (Map.Entry entry : map.entrySet()) {
					out.name(to.apply(entry.getKey()));
					valueAdapter.write(out, entry.getValue());
				}
				out.endObject();
			}

			@Override
			public Map read(JsonReader in) throws IOException {
				Map map = new LinkedHashMap<>();
				in.beginObject();
				while (in.hasNext()) {
					String key = in.nextName();
					V value = valueAdapter.read(in);
					map.put(from.apply(key), value);
				}
				in.endObject();
				return map;
			}
		};
	}

	public static TypeAdapter> ofHeterogeneousMap(Map> valueAdapters) {
		return new TypeAdapter>() {
			@Override
			public void write(JsonWriter out, Map map) throws IOException {
				out.beginObject();
				for (Map.Entry entry : map.entrySet()) {
					String key = entry.getKey();
					TypeAdapter valueAdapter = valueAdapters.get(key);
					out.name(key);
					valueAdapter.write(out, entry.getValue());
				}
				out.endObject();
			}

			@Override
			public Map read(JsonReader in) throws IOException {
				Map map = new LinkedHashMap<>();
				in.beginObject();
				while (in.hasNext()) {
					String key = in.nextName();
					TypeAdapter valueAdapter = valueAdapters.get(key);
					Object value = valueAdapter.read(in);
					map.put(key, value);
				}
				in.endObject();
				return map;
			}
		};
	}

	public static TypeAdapter ofHeterogeneousArray(TypeAdapter[] valueAdapters) {
		return new TypeAdapter() {
			@Override
			public void write(JsonWriter writer, Object[] value) throws IOException {
				writer.beginArray();
				for (int i = 0; i < valueAdapters.length; i++) {
					TypeAdapter adapter = (TypeAdapter) valueAdapters[i];
					adapter.write(writer, value[i]);
				}
				writer.endArray();
			}

			@Override
			public Object[] read(JsonReader reader) throws IOException {
				reader.beginArray();
				Object[] result = new Object[valueAdapters.length];
				for (int i = 0; i < valueAdapters.length; i++) {
					TypeAdapter adapter = valueAdapters[i];
					result[i] = adapter.read(reader);
				}
				reader.endArray();
				return result;
			}
		};
	}

	public static  TypeAdapter oneline(TypeAdapter adapter) {
		return indent(adapter, "");
	}

	public static  TypeAdapter indent(TypeAdapter adapter, String indent) {
		return new TypeAdapter() {
			@Override
			public void write(JsonWriter out, T value) throws IOException {
				JsonWriterEx jsonWriterEx = (JsonWriterEx) out;
				String previousIndent = jsonWriterEx.getIndentEx();
				jsonWriterEx.setIndentEx(indent);
				if (indent.isEmpty()) {
					jsonWriterEx.out.write('\n');
				}
				adapter.write(out, value);
				jsonWriterEx.setIndentEx(previousIndent);
			}

			@Override
			public T read(JsonReader in) throws IOException {
				return adapter.read(in);
			}
		};
	}

	public static TypeAdapter ofHeterogeneousArray(List> valueAdapters) {
		return ofHeterogeneousArray(valueAdapters.toArray(new TypeAdapter[valueAdapters.size()]));
	}

	public static TypeAdapter> ofHeterogeneousList(TypeAdapter[] valueAdapters) {
		return transform(ofHeterogeneousArray(valueAdapters), Arrays::asList, List::toArray);
	}

	public static TypeAdapter> ofHeterogeneousList(List> valueAdapters) {
		return ofHeterogeneousList(valueAdapters.toArray(new TypeAdapter[valueAdapters.size()]));
	}

	public static  T fromJson(TypeAdapter typeAdapter, String string) throws IOException {
		StringReader reader = new StringReader(string);
		JsonReader jsonReader = new JsonReader(reader);
		return typeAdapter.read(jsonReader);
	}

	private static final class JsonWriterEx extends JsonWriter {
		final Writer out;

		public JsonWriterEx(Writer out) {
			super(out);
			this.out = out;
		}

		private String indentEx;

		public final void setIndentEx(String indent) {
			this.indentEx = indent;
			super.setIndent(indent);
		}

		@Override
		public JsonWriter name(String name) throws IOException {
			return super.name(name);
		}

		public final String getIndentEx() {
			return indentEx;
		}
	}

	private static  void toJson(TypeAdapter adapter, T value, Writer writer) throws IOException {
		JsonWriterEx jsonWriter = new JsonWriterEx(writer);
		jsonWriter.setLenient(true);
		jsonWriter.setIndentEx("");
		jsonWriter.setHtmlSafe(false);
		jsonWriter.setSerializeNulls(false);
		adapter.write(jsonWriter, value);
	}

	public static  String toJson(TypeAdapter adapter, T value) {
		try {
			StringWriter writer = new StringWriter();
			toJson(adapter, value, writer);
			return writer.toString();
		} catch (IOException e) {
			throw new AssertionError(e); // no I/O with StringWriter
		}
	}

	public static  void toJson(TypeAdapter adapter, T obj, Appendable appendable) throws IOException {
		toJson(adapter, obj, Streams.writerForAppendable(appendable));
	}
}