Added a generic GSON Type Adapter for ConfigurationSerializabale objects

Bukkit has built-in serialization that can be used. Kept legacy
serialization for backwards compatibility and compactness.
This commit is contained in:
tastybento 2019-05-08 18:21:40 -07:00
parent 91b650bdee
commit 779c370b6a
4 changed files with 146 additions and 66 deletions

View File

@ -1,24 +1,11 @@
package world.bentobox.bentobox.database.json;
import org.bukkit.Location;
import org.bukkit.World;
import org.bukkit.inventory.ItemStack;
import org.bukkit.potion.PotionEffectType;
import org.bukkit.util.BoundingBox;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import world.bentobox.bentobox.BentoBox;
import world.bentobox.bentobox.api.flags.Flag;
import world.bentobox.bentobox.database.AbstractDatabaseHandler;
import world.bentobox.bentobox.database.DatabaseConnector;
import world.bentobox.bentobox.database.json.adapters.BoundingBoxTypeAdapter;
import world.bentobox.bentobox.database.json.adapters.FlagTypeAdapter;
import world.bentobox.bentobox.database.json.adapters.ItemStackTypeAdapter;
import world.bentobox.bentobox.database.json.adapters.LocationTypeAdapter;
import world.bentobox.bentobox.database.json.adapters.PotionEffectTypeAdapter;
import world.bentobox.bentobox.database.json.adapters.WorldTypeAdapter;
/**
* Abstract class that handles insert/select-operations into/from a database.
@ -48,13 +35,8 @@ public abstract class AbstractJSONDatabaseHandler<T> extends AbstractDatabaseHan
// excludeFieldsWithoutExposeAnnotation - this means that every field to be stored should use @Expose
// enableComplexMapKeySerialization - forces GSON to use TypeAdapters even for Map keys
GsonBuilder builder = new GsonBuilder().excludeFieldsWithoutExposeAnnotation().enableComplexMapKeySerialization().setPrettyPrinting();
// Register adapters
builder.registerTypeAdapter(Location.class, new LocationTypeAdapter()) ;
builder.registerTypeAdapter(World.class, new WorldTypeAdapter());
builder.registerTypeAdapter(Flag.class, new FlagTypeAdapter(plugin));
builder.registerTypeAdapter(PotionEffectType.class, new PotionEffectTypeAdapter());
builder.registerTypeAdapter(ItemStack.class, new ItemStackTypeAdapter());
builder.registerTypeAdapter(BoundingBox.class, new BoundingBoxTypeAdapter());
// Register adapter factory
builder.registerTypeAdapterFactory(new BentoboxTypeAdapterFactory(plugin));
// Keep null in the database
builder.serializeNulls();
// Allow characters like < or > without escaping them

View File

@ -0,0 +1,71 @@
/**
*
*/
package world.bentobox.bentobox.database.json;
import java.util.Map;
import org.bukkit.Location;
import org.bukkit.World;
import org.bukkit.configuration.serialization.ConfigurationSerializable;
import org.bukkit.inventory.ItemStack;
import org.bukkit.potion.PotionEffectType;
import com.google.gson.Gson;
import com.google.gson.TypeAdapter;
import com.google.gson.TypeAdapterFactory;
import com.google.gson.reflect.TypeToken;
import world.bentobox.bentobox.BentoBox;
import world.bentobox.bentobox.api.flags.Flag;
import world.bentobox.bentobox.database.json.adapters.BukkitObjectTypeAdapter;
import world.bentobox.bentobox.database.json.adapters.FlagTypeAdapter;
import world.bentobox.bentobox.database.json.adapters.ItemStackTypeAdapter;
import world.bentobox.bentobox.database.json.adapters.LocationTypeAdapter;
import world.bentobox.bentobox.database.json.adapters.PotionEffectTypeAdapter;
import world.bentobox.bentobox.database.json.adapters.WorldTypeAdapter;
/**
* Allocates type adapters based on class type.
*
* @author tastybento
*
*/
public class BentoboxTypeAdapterFactory implements TypeAdapterFactory {
BentoBox plugin;
/**
* @param plugin
*/
public BentoboxTypeAdapterFactory(BentoBox plugin) {
this.plugin = plugin;
}
/* (non-Javadoc)
* @see com.google.gson.TypeAdapterFactory#create(com.google.gson.Gson, com.google.gson.reflect.TypeToken)
*/
@SuppressWarnings("unchecked")
@Override
public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {
Class<?> rawType = type.getRawType();
if (Location.class.isAssignableFrom(rawType)) {
// Use our current location adapter for backward compatibility
return (TypeAdapter<T>) new LocationTypeAdapter();
} else if (ItemStack.class.isAssignableFrom(rawType)) {
// Use our current location adapter for backward compatibility
return (TypeAdapter<T>) new ItemStackTypeAdapter();
} else if (Flag.class.isAssignableFrom(rawType)) {
return (TypeAdapter<T>) new FlagTypeAdapter(plugin);
} else if (PotionEffectType.class.isAssignableFrom(rawType)) {
return (TypeAdapter<T>) new PotionEffectTypeAdapter();
} else if (World.class.isAssignableFrom(rawType)) {
return (TypeAdapter<T>) new WorldTypeAdapter();
} else if (ConfigurationSerializable.class.isAssignableFrom(rawType)) {
// This covers a lot of Bukkit objects
return (TypeAdapter<T>) new BukkitObjectTypeAdapter(gson.getAdapter(Map.class));
}
return null;
}
}

View File

@ -1,46 +0,0 @@
package world.bentobox.bentobox.database.json.adapters;
import java.io.IOException;
import org.bukkit.util.BoundingBox;
import com.google.gson.TypeAdapter;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonToken;
import com.google.gson.stream.JsonWriter;
public class BoundingBoxTypeAdapter extends TypeAdapter<BoundingBox> {
@Override
public void write(JsonWriter out, BoundingBox box) throws IOException {
if (box == null) {
out.nullValue();
return;
}
out.beginArray();
out.value(box.getMinX());
out.value(box.getMinY());
out.value(box.getMinZ());
out.value(box.getMaxX());
out.value(box.getMaxY());
out.value(box.getMaxZ());
out.endArray();
}
@Override
public BoundingBox read(JsonReader in) throws IOException {
if (in.peek() == JsonToken.NULL) {
in.nextNull();
return null;
}
in.beginArray();
double minX = in.nextDouble();
double minY = in.nextDouble();
double minZ = in.nextDouble();
double maxX = in.nextDouble();
double maxY = in.nextDouble();
double maxZ = in.nextDouble();
in.endArray();
return new BoundingBox(minX, minY, minZ, maxX, maxY, maxZ);
}
}

View File

@ -0,0 +1,73 @@
package world.bentobox.bentobox.database.json.adapters;
import static org.bukkit.configuration.serialization.ConfigurationSerialization.SERIALIZED_TYPE_KEY;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import org.bukkit.configuration.serialization.ConfigurationSerializable;
import org.bukkit.configuration.serialization.ConfigurationSerialization;
import com.google.gson.TypeAdapter;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonWriter;
/**
* Handles {@link ConfigurationSerializable} types
* @author tastybento
* @since 1.5.0
*/
public class BukkitObjectTypeAdapter extends TypeAdapter<ConfigurationSerializable> {
@SuppressWarnings("rawtypes")
private final TypeAdapter<Map> map;
@SuppressWarnings("rawtypes")
public BukkitObjectTypeAdapter(TypeAdapter<Map> mapAdapter) {
this.map = mapAdapter;
}
public static Map<String, Object> serializeObject(ConfigurationSerializable serializable) {
Map<String, Object> serialized = new HashMap<>();
serialized.putAll(serializable.serialize());
serialized.entrySet().stream()
.filter(e -> e.getValue() instanceof ConfigurationSerializable)
.forEach(e -> serialized.put(e.getKey(), serializeObject((ConfigurationSerializable) e.getValue())));
serialized.put(SERIALIZED_TYPE_KEY, ConfigurationSerialization.getAlias(serializable.getClass()));
return serialized;
}
@SuppressWarnings("unchecked")
public ConfigurationSerializable deserializeObject(Map<String, Object> map) {
if (map == null) {
return null;
}
// Iteratively expand maps until the Bukkit ConfigurationSerialization can deserialize the base object
map.forEach((k,v) -> {
if (v instanceof Map) {
Map<String, Object> nestedMap = (Map<String, Object>)v;
if (nestedMap.containsKey(SERIALIZED_TYPE_KEY)) {
map.put(k, deserializeObject(nestedMap));
}
}
});
return ConfigurationSerialization.deserializeObject(map);
}
@SuppressWarnings("unchecked")
@Override
public ConfigurationSerializable read(JsonReader in) throws IOException {
return deserializeObject(map.read(in));
}
@Override
public void write(JsonWriter out, ConfigurationSerializable value) throws IOException {
if (value == null) {
out.nullValue();
}
map.write(out, serializeObject(value));
}
}