package pluginbase.config;
import pluginbase.config.annotation.SerializableAs;
import pluginbase.config.serializers.*;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import pluginbase.config.util.PrimitivesUtil;
import pluginbase.logging.Logging;
import java.util.*;
/**
* A singleton used to serialize and deserialize objects to take advantage of all the features provided by
* Serializable-Config.
* <p/>
* Serialization is often as simple as calling {@link #serialize(Object)} on the object you want to serialize and
* {@link #deserialize(Object)} on the object you want to deserialize. However, some classes may require more
* instruction on how to be serialized in the form of a custom {@link Serializer} which can be added to a class via
* {@link pluginbase.config.annotation.SerializeWith} or added to a custom {@link SerializerSet} and passed utilized
* via {@link #serialize(Object, SerializerSet)} and {@link #deserialize(Object, SerializerSet)}.
* <p/>
* For simple and direct serialization to and from a storage medium, refer to {@link pluginbase.config.datasource}.
*
* @see SerializerSet
* @see pluginbase.config.datasource
*/
public enum SerializableConfig {
;
/**
* The key string used to represent a serialized type within serialized data.
*/
public static final String SERIALIZED_TYPE_KEY = "=$$=";
private static Map<String, Class> serializableAliases = new HashMap<>();
/**
* Serializes the given object using the specified serializer set.
*
* @param object the object to serialize.
* @param serializerSet the serializerSet to look for serializers in.
* @return the serialized object which may be null.
*/
@Nullable
@SuppressWarnings("unchecked")
public static Object serialize(@NotNull Object object, @NotNull SerializerSet serializerSet) {
Class clazz = object.getClass();
clazz = PrimitivesUtil.switchForWrapper(clazz);
return serializerSet.getClassSerializer(clazz).serialize(object, serializerSet);
}
/**
* Deserializes the given data using the specified serializer set.
* <p/>
* To use this method to its fullest potential, data should be in the form of a Map with string keys.
*
* @param data the data to deserialize.
* @param serializerSet the serializerSet to look for serializers in.
* @return the deserialized object which may be null.
*/
@Nullable
public static Object deserialize(@Nullable Object data, @NotNull SerializerSet serializerSet) {
if (data instanceof Map) {
Map map = (Map) data;
Class<?> clazz = getClassFromSerializedData(map);
if (clazz == null) {
return deserializeAs(data, data.getClass(), serializerSet);
//throw new IllegalArgumentException("The given data is not valid for type " + map.get(SERIALIZED_TYPE_KEY) + ". Was the type registered?");
}
return deserializeAs(data, clazz, serializerSet);
} else {
return data != null ? deserializeAs(data, data.getClass(), serializerSet) : null;
}
}
/**
* Deserializes the given data in the form of the specified class using the specified serializer set.
* <p/>
* To use this method to its fullest potential, data should be in the form of a Map with string keys.
*
* @param data the data to deserialize.
* @param clazz the type to deserialize as.
* @param serializerSet the serializerSet to look for serializers in.
* @return the deserialized object which may be null.
*/
@Nullable
@SuppressWarnings("unchecked")
public static <T> T deserializeAs(@NotNull Object data, @NotNull Class<T> clazz, @NotNull SerializerSet serializerSet) {
clazz = PrimitivesUtil.switchForWrapper(clazz);
return (T) serializerSet.getClassSerializer(clazz).deserialize(data, clazz, serializerSet);
}
/**
* Registers the serialization alias for a class annotated with {@link SerializableAs}. This must be called before
* any deserialization occurs or else the annotated class will not be properly deserialized.
*
* @param clazz The class to register the serialization alias for.
*/
public static void registerSerializableAsClass(@NotNull Class<?> clazz) {
if (!clazz.isAnnotationPresent(SerializableAs.class)) {
throw new IllegalArgumentException("The class must be annotated with SerializableAs");
}
SerializableAs serializableAs = clazz.getAnnotation(SerializableAs.class);
Map<String, Class> newCopy = new HashMap<>(serializableAliases);
newCopy.put(serializableAs.value(), clazz);
serializableAliases = newCopy;
}
/**
* Serializes the given object using the {@link SerializerSet#defaultSet()}.
*
* @param object the object to serialize.
* @return the serialized object which may be null.
*/
@Nullable
public static Object serialize(@NotNull Object object) {
return serialize(object, SerializerSet.defaultSet());
}
/**
* Deserializes the given data using the {@link SerializerSet#defaultSet()}.
* <p/>
* To use this method to its fullest potential, data should be in the form of a Map with string keys.
*
* @param data the data to deserialize.
* @return the deserialized object which may be null.
*/
@Nullable
public static Object deserialize(@Nullable Object data) {
return deserialize(data, SerializerSet.defaultSet());
}
/**
* Deserializes the given data in the form of the specified class using the {@link SerializerSet#defaultSet()}.
* <p/>
* To use this method to its fullest potential, data should be in the form of a Map with string keys.
*
* @param data the data to deserialize.
* @param clazz the type to deserialize as.
* @return the deserialized object which may be null.
*/
@Nullable
public static <T> T deserializeAs(@NotNull Object data, @NotNull Class<T> clazz) {
return deserializeAs(data, clazz, SerializerSet.defaultSet());
}
/**
* This method attempts to determine the class type from a given set of data. It looks for the {@link #SERIALIZED_TYPE_KEY}
* to determine what class the data represents. As such, the data should be in the form of a Map with string keys.
*
* @param data the serialized data.
* @return the class type represented by the data or null if it could not be determined.
*/
@Nullable
public static Class getClassFromSerializedData(Map data) {
Object object = data.get(SERIALIZED_TYPE_KEY);
if (object == null) {
return null;
}
if (!(object instanceof String)) {
Logging.warning("Serialized type key found in %s should be a string but is not.", data);
return null;
}
String className = (String) object;
Class clazz = getClassByAlias(className);
if (clazz != null) {
return clazz;
} else {
try {
return Class.forName(className);
} catch (ClassNotFoundException e) {
Logging.warning("Found serialized type key '%s' but could not find an associated class.", className);
return null;
}
}
}
@Nullable
private static Class getClassByAlias(@NotNull String className) {
return serializableAliases.get(className);
}
}