/* Copyright 2008 - 2017 Hochschule Offenburg
 * For a list of authors see README.md
 * This software of HSOAutonomy is released under MIT License (see LICENSE).
 */

package hso.autonomy.util.misc;

import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonDeserializer;
import com.google.gson.JsonPrimitive;
import com.google.gson.JsonSerializer;
import com.google.gson.TypeAdapter;
import com.google.gson.TypeAdapterFactory;
import com.google.gson.reflect.TypeToken;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonToken;
import com.google.gson.stream.JsonWriter;
import java.io.IOException;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.Duration;
import java.util.Optional;

public class GsonUtil
{
	private static Gson gson =
			new GsonBuilder()
					.registerTypeAdapter(Double.class,
							(JsonSerializer<Double>) (src, typeOfSrc, context) -> {
								if (src.isNaN() || src.isInfinite()) {
									return new JsonPrimitive(src.toString());
								}
								return new JsonPrimitive(
										new BigDecimal(src).setScale(3, RoundingMode.HALF_UP).doubleValue());
							})
					.registerTypeAdapter(Float.class,
							(JsonSerializer<Float>) (src, typeOfSrc, context) -> {
								if (src.isNaN() || src.isInfinite()) {
									return new JsonPrimitive(src.toString());
								}
								return new JsonPrimitive(
										new BigDecimal(src).setScale(3, RoundingMode.HALF_UP).floatValue());
							})
					.registerTypeAdapter(Duration.class,
							(JsonSerializer<Duration>) (src, typeOfSrc,
									context) -> new JsonPrimitive(DurationUtil.format(src)))
					.registerTypeAdapter(Duration.class,
							(JsonDeserializer<Duration>) (src, typeOfSrc,
									context) -> DurationUtil.parse(src.getAsString()))
					.registerTypeAdapterFactory(OptionalTypeAdapter.FACTORY)
					.setPrettyPrinting()
					.serializeSpecialFloatingPointValues()
					.create();

	/**
	 * JSON-Serialization with pretty-printing, float / double rounding to 3
	 * decimals and support for special floating point values (NaN, infinity...).
	 */
	public static String toJson(Object src)
	{
		return gson.toJson(src);
	}

	public static <T> T fromJson(String src, Type typeOfT)
	{
		return gson.fromJson(src, typeOfT);
	}

	public static class OptionalTypeAdapter<E> extends TypeAdapter<Optional<E>>
	{
		public static final TypeAdapterFactory FACTORY = new TypeAdapterFactory() {
			@Override
			public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type)
			{
				Class<T> rawType = (Class<T>) type.getRawType();
				if (rawType != Optional.class) {
					return null;
				}
				final ParameterizedType parameterizedType = (ParameterizedType) type.getType();
				final Type actualType = parameterizedType.getActualTypeArguments()[0];
				final TypeAdapter<?> adapter = gson.getAdapter(TypeToken.get(actualType));
				return new OptionalTypeAdapter(adapter);
			}
		};
		private final TypeAdapter<E> adapter;

		public OptionalTypeAdapter(TypeAdapter<E> adapter)
		{
			this.adapter = adapter;
		}

		@Override
		public void write(JsonWriter out, Optional<E> value) throws IOException
		{
			if (value != null && value.isPresent()) {
				adapter.write(out, value.get());
			} else {
				out.nullValue();
			}
		}

		@Override
		public Optional<E> read(JsonReader in) throws IOException
		{
			final JsonToken peek = in.peek();
			if (peek != JsonToken.NULL) {
				return Optional.ofNullable(adapter.read(in));
			}
			return Optional.empty();
		}
	}
}

@@ -193,16 +193,135 @@
 .misc;%0A%0A
+import java.lang.reflect.Type;%0Aimport java.math.BigDecimal;%0Aimport java.math.RoundingMode;%0Aimport java.time.Duration;%0A%0A
 import c
@@ -337,24 +337,24 @@
 .gson.Gson;%0A
-
 import com.g
@@ -498,471 +498,8 @@
 zer;
-%0Aimport com.google.gson.TypeAdapter;%0Aimport com.google.gson.TypeAdapterFactory;%0Aimport com.google.gson.reflect.TypeToken;%0Aimport com.google.gson.stream.JsonReader;%0Aimport com.google.gson.stream.JsonToken;%0Aimport com.google.gson.stream.JsonWriter;%0Aimport java.io.IOException;%0Aimport java.lang.reflect.ParameterizedType;%0Aimport java.lang.reflect.Type;%0Aimport java.math.BigDecimal;%0Aimport java.math.RoundingMode;%0Aimport java.time.Duration;%0Aimport java.util.Optional;
 %0A%0Apu
@@ -1554,70 +1554,8 @@
 )))%0A
-%09%09%09%09%09.registerTypeAdapterFactory(OptionalTypeAdapter.FACTORY)%0A
 %09%09%09%09
@@ -1930,24 +1930,24 @@
 typeOfT)%0A%09%7B%0A
+
 %09%09return gso
@@ -1978,1238 +1978,7 @@
 ;%0A%09%7D
-%0A%0A%09public static class OptionalTypeAdapter%3CE%3E extends TypeAdapter%3COptional%3CE%3E%3E%0A%09%7B%0A%09%09public static final TypeAdapterFactory FACTORY = new TypeAdapterFactory() %7B%0A%09%09%09@Override%0A%09%09%09public %3CT%3E TypeAdapter%3CT%3E create(Gson gson, TypeToken%3CT%3E type)%0A%09%09%09%7B%0A%09%09%09%09Class%3CT%3E rawType = (Class%3CT%3E) type.getRawType();%0A%09%09%09%09if (rawType != Optional.class) %7B%0A%09%09%09%09%09return null;%0A%09%09%09%09%7D%0A%09%09%09%09final ParameterizedType parameterizedType = (ParameterizedType) type.getType();%0A%09%09%09%09final Type actualType = parameterizedType.getActualTypeArguments()%5B0%5D;%0A%09%09%09%09final TypeAdapter%3C?%3E adapter = gson.getAdapter(TypeToken.get(actualType));%0A%09%09%09%09return new OptionalTypeAdapter(adapter);%0A%09%09%09%7D%0A%09%09%7D;%0A%09%09private final TypeAdapter%3CE%3E adapter;%0A%0A%09%09public OptionalTypeAdapter(TypeAdapter%3CE%3E adapter)%0A%09%09%7B%0A%09%09%09this.adapter = adapter;%0A%09%09%7D%0A%0A%09%09@Override%0A%09%09public void write(JsonWriter out, Optional%3CE%3E value) throws IOException%0A%09%09%7B%0A%09%09%09if (value != null && value.isPresent()) %7B%0A%09%09%09%09adapter.write(out, value.get());%0A%09%09%09%7D else %7B%0A%09%09%09%09out.nullValue();%0A%09%09%09%7D%0A%09%09%7D%0A%0A%09%09@Override%0A%09%09public Optional%3CE%3E read(JsonReader in) throws IOException%0A%09%09%7B%0A%09%09%09final JsonToken peek = in.peek();%0A%09%09%09if (peek != JsonToken.NULL) %7B%0A%09%09%09%09return Optional.ofNullable(adapter.read(in));%0A%09%09%09%7D%0A%09%09%09return Optional.empty();%0A%09%09%7D%0A%09%7D
 %0A%7D%0A

Revert "GsonUtil: java.util.Optional handling"