/*
 * Copyright 2017 MovingBlocks
 *
 * 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 org.terasology.persistence.typeHandling.gson;

import com.google.gson.Gson;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParseException;
import com.google.gson.JsonPrimitive;
import com.google.gson.TypeAdapter;
import com.google.gson.TypeAdapterFactory;
import com.google.gson.internal.Streams;
import com.google.gson.reflect.TypeToken;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonWriter;

import java.io.IOException;
import java.util.Map;

public class PolymorphicTypeAdapterFactory<T> implements TypeAdapterFactory {
    private static final String TYPE_FIELD_NAME = "@type";

    private final Class<T> baseClass;

    private PolymorphicTypeAdapterFactory(Class<T> baseClass) {
        this.baseClass = baseClass;
    }

    public static <T> PolymorphicTypeAdapterFactory<T> of(Class<T> baseClass) {
        return new PolymorphicTypeAdapterFactory<>(baseClass);
    }

    @Override
    public <R> TypeAdapter<R> create(Gson gson, TypeToken<R> type) {
        if (!baseClass.isAssignableFrom(type.getRawType())) {
            return null;
        }

        return new TypeAdapter<R>() {
            @SuppressWarnings("unchecked")
            @Override
            public void write(JsonWriter out, R value) throws IOException {
                Class<?> valueClass = value.getClass();
                String valueTypeName = valueClass.getName();

                TypeToken<?> valueType = TypeToken.get(valueClass);
                TypeAdapter<R> delegate = (TypeAdapter<R>)
                        gson.getDelegateAdapter(PolymorphicTypeAdapterFactory.this, valueType);

                if (delegate == null) {
                    throw new JsonParseException("Could not serialize " + valueClass.getName());
                }

                JsonElement jsonElement = delegate.toJsonTree(value);

                if (valueClass != baseClass) {
                    JsonObject jsonObject = jsonElement.getAsJsonObject();

                    JsonObject clone = new JsonObject();
                    clone.add(TYPE_FIELD_NAME, new JsonPrimitive(valueTypeName));

                    for (Map.Entry<String, JsonElement> e : jsonObject.entrySet()) {
                        clone.add(e.getKey(), e.getValue());
                    }

                    Streams.write(clone, out);
                } else {
                    Streams.write(jsonElement, out);
                }
            }

            @SuppressWarnings("unchecked")
            @Override
            public R read(JsonReader in) throws IOException {
                JsonElement jsonElement = Streams.parse(in);
                Class<?> valueClass;

                if (jsonElement.isJsonObject()) {
                    JsonElement typeNameJsonElement = jsonElement.getAsJsonObject().remove(TYPE_FIELD_NAME);

                    if (typeNameJsonElement != null) {
                        String typeName = typeNameJsonElement.getAsString();

                        try {
                            valueClass = Class.forName(typeName);
                        } catch (ClassNotFoundException e) {
                            throw new JsonParseException("Could not find class " + typeName);
                        }
                    } else {
                        valueClass = baseClass;
                    }
                } else {
                    valueClass = baseClass;
                }

                TypeToken<?> valueType = TypeToken.get(valueClass);
                TypeAdapter<R> delegate = (TypeAdapter<R>)
                        gson.getDelegateAdapter(PolymorphicTypeAdapterFactory.this, valueType);

                if (delegate == null) {
                    throw new JsonParseException("Could not deserialize " + valueClass.getName());
                }

                return delegate.fromJsonTree(jsonElement);
            }
        };
    }
}

@@ -1124,16 +1124,368 @@
 l.Map;%0A%0A
+/**%0A * A %7B@link TypeAdapterFactory%7D that generates type adapters which read and write type information for%0A * sub-types of the given base type. The type information allows the generated type adapters to dynamically%0A * identify which sub-type instance is being read or written when it is given a base type reference.%0A *%0A * @param %3CT%3E The base type.%0A */%0A
 public c

Document PolymorphicTypeAdapterFactory