From 779c370b6a3a53662f49331c0d8cfed808305281 Mon Sep 17 00:00:00 2001 From: tastybento Date: Wed, 8 May 2019 18:21:40 -0700 Subject: [PATCH] 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. --- .../json/AbstractJSONDatabaseHandler.java | 22 +----- .../json/BentoboxTypeAdapterFactory.java | 71 ++++++++++++++++++ .../json/adapters/BoundingBoxTypeAdapter.java | 46 ------------ .../adapters/BukkitObjectTypeAdapter.java | 73 +++++++++++++++++++ 4 files changed, 146 insertions(+), 66 deletions(-) create mode 100644 src/main/java/world/bentobox/bentobox/database/json/BentoboxTypeAdapterFactory.java delete mode 100644 src/main/java/world/bentobox/bentobox/database/json/adapters/BoundingBoxTypeAdapter.java create mode 100644 src/main/java/world/bentobox/bentobox/database/json/adapters/BukkitObjectTypeAdapter.java diff --git a/src/main/java/world/bentobox/bentobox/database/json/AbstractJSONDatabaseHandler.java b/src/main/java/world/bentobox/bentobox/database/json/AbstractJSONDatabaseHandler.java index bfacf13b8..ac57d9b0a 100644 --- a/src/main/java/world/bentobox/bentobox/database/json/AbstractJSONDatabaseHandler.java +++ b/src/main/java/world/bentobox/bentobox/database/json/AbstractJSONDatabaseHandler.java @@ -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 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 diff --git a/src/main/java/world/bentobox/bentobox/database/json/BentoboxTypeAdapterFactory.java b/src/main/java/world/bentobox/bentobox/database/json/BentoboxTypeAdapterFactory.java new file mode 100644 index 000000000..2fdffebf8 --- /dev/null +++ b/src/main/java/world/bentobox/bentobox/database/json/BentoboxTypeAdapterFactory.java @@ -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 TypeAdapter create(Gson gson, TypeToken type) { + Class rawType = type.getRawType(); + if (Location.class.isAssignableFrom(rawType)) { + // Use our current location adapter for backward compatibility + return (TypeAdapter) new LocationTypeAdapter(); + } else if (ItemStack.class.isAssignableFrom(rawType)) { + // Use our current location adapter for backward compatibility + return (TypeAdapter) new ItemStackTypeAdapter(); + } else if (Flag.class.isAssignableFrom(rawType)) { + return (TypeAdapter) new FlagTypeAdapter(plugin); + } else if (PotionEffectType.class.isAssignableFrom(rawType)) { + return (TypeAdapter) new PotionEffectTypeAdapter(); + } else if (World.class.isAssignableFrom(rawType)) { + return (TypeAdapter) new WorldTypeAdapter(); + } else if (ConfigurationSerializable.class.isAssignableFrom(rawType)) { + // This covers a lot of Bukkit objects + return (TypeAdapter) new BukkitObjectTypeAdapter(gson.getAdapter(Map.class)); + } + return null; + } + +} diff --git a/src/main/java/world/bentobox/bentobox/database/json/adapters/BoundingBoxTypeAdapter.java b/src/main/java/world/bentobox/bentobox/database/json/adapters/BoundingBoxTypeAdapter.java deleted file mode 100644 index 4dad71ddd..000000000 --- a/src/main/java/world/bentobox/bentobox/database/json/adapters/BoundingBoxTypeAdapter.java +++ /dev/null @@ -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 { - - @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); - } -} \ No newline at end of file diff --git a/src/main/java/world/bentobox/bentobox/database/json/adapters/BukkitObjectTypeAdapter.java b/src/main/java/world/bentobox/bentobox/database/json/adapters/BukkitObjectTypeAdapter.java new file mode 100644 index 000000000..46128eb7e --- /dev/null +++ b/src/main/java/world/bentobox/bentobox/database/json/adapters/BukkitObjectTypeAdapter.java @@ -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 { + + @SuppressWarnings("rawtypes") + private final TypeAdapter map; + + @SuppressWarnings("rawtypes") + public BukkitObjectTypeAdapter(TypeAdapter mapAdapter) { + this.map = mapAdapter; + } + + public static Map serializeObject(ConfigurationSerializable serializable) { + Map 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 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 nestedMap = (Map)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)); + } +} \ No newline at end of file