package com.supaham.commons.bukkit.utils;
import com.google.common.base.Preconditions;
import com.supaham.commons.bukkit.serializers.ColorSerializer;
import com.supaham.commons.bukkit.serializers.MaterialDataSerializer;
import com.supaham.commons.minecraft.world.space.Position;
import com.supaham.commons.minecraft.world.space.PositionSerializer;
import com.supaham.commons.minecraft.world.space.Vector;
import com.supaham.commons.minecraft.world.space.VectorSerializer;
import com.supaham.commons.relatives.RelativeDuration;
import com.supaham.commons.relatives.RelativeDurationSerializer;
import com.supaham.commons.relatives.RelativeNumber;
import com.supaham.commons.relatives.RelativeNumberSerializer;
import com.supaham.commons.serializers.DurationSerializer;
import com.supaham.commons.serializers.LocalTimeSerializer;
import org.bukkit.Color;
import org.bukkit.material.MaterialData;
import java.io.File;
import java.io.IOException;
import java.time.Duration;
import java.time.LocalTime;
import java.util.Map;
import java.util.logging.Logger;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import pluginbase.config.SerializableConfig;
import pluginbase.config.datasource.DataSource;
import pluginbase.config.datasource.yaml.YamlDataSource;
import pluginbase.config.serializers.Serializer;
import pluginbase.config.serializers.SerializerSet;
import pluginbase.config.serializers.SerializerSet.Builder;
import pluginbase.messages.PluginBaseException;
/**
* Utility methods for working with PluginBase serialization. This class contains methods such as
* {@link #loadOrCreateProperties(Logger, DataSource, Object, String)}, and more.
*
* @since 0.3.2
*/
public final class SerializationUtils {
public static final SerializerSet SERIALIZER_SET;
static {
Builder builder = SerializerSet.builder(SerializerSet.defaultSet());
_add(builder, Duration.class, new DurationSerializer());
_add(builder, LocalTime.class, new LocalTimeSerializer());
_add(builder, Color.class, new ColorSerializer());
_add(builder, MaterialData.class, new MaterialDataSerializer());
_add(builder, RelativeNumber.class, new RelativeNumberSerializer());
_add(builder, RelativeDuration.class, new RelativeDurationSerializer());
_add(builder, Vector.class, new VectorSerializer());
_add(builder, Position.class, new PositionSerializer());
SERIALIZER_SET = builder.build();
}
private static final <T> Builder _add(Builder builder, Class<T> clazz, Serializer<T> serializer) {
return builder.addSerializer(clazz, () -> serializer);
}
public static YamlDataSource.Builder yaml(File file) throws IOException {
if (!file.exists()) {
file.createNewFile();
}
return YamlDataSource.builder().setFile(file).setIndent(2);
}
/**
* Loads or creates a default properties class built on PluginBase. If the {@code file} does not
* exist, it is created. Then, when loading, comments option is set to true, causing comment
* output in the {@code file}. Then, the file is searched the given {@code defaults}, if it
* does not exist, it is written to file. Finally, the yaml writes the result, whether it be the
* defaults or the newly loaded class, to the config and writes to the {@code file}.
*
* @param logger logger to debug to
* @param dataSource data source to load from
* @param defaults defaults to use
* @param <T> type of properties
*
* @return object of type {@link T}
*/
@Nullable
public static <T> T loadOrCreateProperties(@Nonnull Logger logger,
@Nonnull DataSource dataSource,
@Nonnull T defaults) {
return loadOrCreateProperties(logger, dataSource, defaults, null);
}
/**
* Loads or creates a default properties class built on PluginBase. If the {@code file} does not
* exist, it is created. Then, when loading, comments option is set to true, causing comment
* output in the {@code file}. Then, the file is searched for {@code root} yaml object, if it
* does not exist, it is written to using the {@code defaults}. Finally, the yaml writes the
* result, whether it be the defaults or the newly loaded class, to the config and writes to the
* {@code file}.
*
* @param logger logger to debug to
* @param dataSource data source to load from
* @param defaults defaults to use
* @param root root yaml object in the file, not null
* @param <T> type of properties
*
* @return object of type {@link T}
*/
@Nullable
public static <T> T loadOrCreateProperties(@Nonnull Logger logger,
@Nonnull DataSource dataSource, @Nonnull T defaults,
@Nullable String root) {
return loadOrCreateProperties(logger, dataSource, defaults, root, SERIALIZER_SET);
}
/**
* Loads or creates a default properties class built on PluginBase. If the {@code file} does not
* exist, it is created. Then, when loading, comments option is set to true, causing comment
* output in the {@code file}. Then, the file is searched for {@code root} yaml object, if it
* does not exist, it is written to using the {@code defaults}. Finally, the yaml writes the
* result, whether it be the defaults or the newly loaded class, to the config and writes to the
* {@code file}.
*
* @param logger logger to debug to
* @param dataSource data source to load from
* @param defaults defaults to use
* @param root root yaml object in the file, not null
* @param <T> type of properties
*
* @return object of type {@link T}
*/
@Nullable
public static <T> T loadOrCreateProperties(@Nonnull Logger logger,
@Nonnull DataSource dataSource, @Nonnull T defaults,
@Nullable String root, SerializerSet serializerSet) {
Preconditions.checkNotNull(logger, "logger cannot be null.");
Preconditions.checkNotNull(dataSource, "dataSource cannot be null.");
Preconditions.checkNotNull(defaults, "defaults cannot be null.");
T result = defaults;
try {
final Object loadedObject = dataSource.load();
if (loadedObject != null) {
loadToObject(loadedObject, defaults, serializerSet);
}
} catch (PluginBaseException e) {
e.printStackTrace();
return null;
}
return result;
}
public static <T extends Serializer> T getSerializer(Class<T> serializerClass) {
return SERIALIZER_SET.getSerializerInstance(serializerClass);
}
public static <T> Serializer<T> getClassSerializer(Class<T> serializerClass) {
return SERIALIZER_SET.getClassSerializer(serializerClass);
}
public static <T> Object serialize(T object) {
return SerializableConfig.serialize(object);
}
public static <T> Object serialize(T object,
@Nonnull Class<? extends Serializer<T>> serializerClass) {
return serialize(object, serializerClass, SERIALIZER_SET);
}
public static <T> Object serialize(T object,
@Nonnull Class<? extends Serializer<T>> serializerClass,
@Nonnull SerializerSet serializerSet) {
return getSerializer(serializerClass).serialize(object, serializerSet);
}
public static <T> T deserializeWith(Object serialized,
@Nonnull Class<? extends Serializer<T>> serializerClass) {
return (T) deserializeWith(serialized, serializerClass, SERIALIZER_SET);
}
public static <T> Object deserializeWith(Object serialized,
@Nonnull Class<? extends Serializer<T>> serializerClass,
@Nonnull SerializerSet serializerSet) {
if (serialized == null) {
return null;
}
Class<?> typeClass = null;
if (serialized instanceof Map) {
Map map = (Map) serialized;
typeClass = SerializableConfig.getClassFromSerializedData(map);
}
if (typeClass == null) {
typeClass = serialized.getClass();
}
return getSerializer(serializerClass).deserialize(serialized, typeClass, serializerSet);
}
public static void loadToObject(Object value, Object destination, SerializerSet serializerSet) {
if (value == null) {
return;
}
Preconditions.checkState(value instanceof Map, "value is not map, cannot deserialize.");
// This is where the magic of class (destination) config templates is loaded
serializerSet.getFallbackSerializer().deserializeToObject((Map) value, destination, serializerSet);
}
private SerializationUtils() {
throw new AssertionError("Try Weetabix instead...");
}
}