/*
* Copyright (C) 2015 SoftIndex LLC.
*
* 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 io.datakernel.serializer;
import com.google.common.collect.BiMap;
import com.google.common.collect.HashBiMap;
import com.google.gson.*;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Modifier;
import java.lang.reflect.Type;
import java.util.HashMap;
import java.util.Map;
public final class GsonSubclassesAdapter<T> implements JsonSerializer<T>, JsonDeserializer<T> {
private final BiMap<String, Class<? extends T>> classTags;
private final Map<String, InstanceCreator<T>> classCreators;
private final BiMap<String, Class<? extends T>> subclassNames;
private final String subclassField;
// region builders
private GsonSubclassesAdapter(BiMap<String, Class<? extends T>> classTags,
Map<String, InstanceCreator<T>> classCreators,
String subclassField, BiMap<String, Class<? extends T>> subclassNames) {
this.classTags = classTags;
this.classCreators = classCreators;
this.subclassField = subclassField;
this.subclassNames = subclassNames;
}
public static <T> GsonSubclassesAdapter<T> create() {
return new GsonSubclassesAdapter<>(
HashBiMap.<String, Class<? extends T>>create(),
new HashMap<String, InstanceCreator<T>>(),
"_type",
HashBiMap.<String, Class<? extends T>>create()
);
}
public GsonSubclassesAdapter<T> withClassTag(String classTag, Class<? extends T> type,
InstanceCreator<T> instanceCreator) {
this.classTags.put(classTag, type);
this.classCreators.put(classTag, instanceCreator);
return this;
}
public GsonSubclassesAdapter<T> withClassTag(String classTag, Class<? extends T> type) {
this.classTags.put(classTag, type);
return this;
}
public GsonSubclassesAdapter<T> withSubclassField(String subclassField) {
return new GsonSubclassesAdapter<>(classTags, classCreators, subclassField, subclassNames);
}
public GsonSubclassesAdapter<T> withSubclass(String subclassName, Class<? extends T> subclass) {
this.subclassNames.put(subclassName, subclass);
return this;
}
// endregion
private static Object newInstance(Class<?> type) throws InstantiationException, IllegalAccessException, NoSuchMethodException, InvocationTargetException {
boolean isStatic = (type.getModifiers() & Modifier.STATIC) != 0;
Class<?> enclosingClass = type.getEnclosingClass();
if (isStatic || enclosingClass == null) {
Constructor<?> ctor = type.getDeclaredConstructor();
ctor.setAccessible(true);
return ctor.newInstance();
}
Object enclosingInstance = newInstance(enclosingClass);
Constructor<?> ctor = type.getDeclaredConstructor(enclosingClass);
ctor.setAccessible(true);
return ctor.newInstance(enclosingInstance);
}
@SuppressWarnings("unchecked")
@Override
public JsonElement serialize(T src, Type typeOfSrc, JsonSerializationContext context) {
Class<? extends T> aClass = (Class<? extends T>) src.getClass();
String classTag = classTags.inverse().get(aClass);
if (classTag != null) {
return new JsonPrimitive(classTag);
}
String subclassName = subclassNames.inverse().get(aClass);
if (subclassName != null) {
JsonObject result = new JsonObject();
result.addProperty(subclassField, subclassName);
JsonObject element = (JsonObject) context.serialize(src, src.getClass());
for (Map.Entry<String, JsonElement> entry : element.entrySet()) {
result.add(entry.getKey(), entry.getValue());
}
return result;
}
return new JsonPrimitive(src.getClass().getName());
}
@SuppressWarnings("unchecked")
@Override
public T deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
if (json.isJsonPrimitive()) {
if (!((JsonPrimitive) json).isString()) {
throw new JsonParseException("Inner class name is expected");
}
String className = json.getAsString();
InstanceCreator<T> creator = classCreators.get(className);
if (creator != null) {
return creator.createInstance(typeOfT);
}
try {
Class<?> aClass = classTags.get(className);
if (aClass == null) {
aClass = Class.forName(className);
}
return (T) newInstance(aClass);
} catch (InvocationTargetException | ClassNotFoundException | InstantiationException | IllegalAccessException | NoSuchMethodException e) {
throw new JsonParseException(e);
}
}
JsonObject object = json.getAsJsonObject();
String subclassName = object.get(subclassField).getAsString();
return context.deserialize(json, subclassNames.get(subclassName));
}
}