diff --git a/core/src/main/java/io/github/md5sha256/addictiveexperience/implementation/plant/ConfigurateResolver.java b/core/src/main/java/io/github/md5sha256/addictiveexperience/implementation/plant/ConfigurateResolver.java new file mode 100644 index 0000000..1f0b447 --- /dev/null +++ b/core/src/main/java/io/github/md5sha256/addictiveexperience/implementation/plant/ConfigurateResolver.java @@ -0,0 +1,125 @@ +package io.github.md5sha256.addictiveexperience.implementation.plant; + +import com.github.md5sha256.spigotutils.blocks.BlockPosition; +import com.github.md5sha256.spigotutils.blocks.ChunkPosition; +import com.github.md5sha256.spigotutils.timing.VariableStopwatch; +import com.google.inject.assistedinject.Assisted; +import com.google.inject.assistedinject.AssistedInject; +import io.github.md5sha256.addictiveexperience.api.drugs.DrugPlantData; +import io.github.md5sha256.addictiveexperience.api.drugs.DrugPlantMeta; +import io.github.md5sha256.addictiveexperience.api.drugs.DrugRegistry; +import io.github.md5sha256.addictiveexperience.util.configurate.AdventureKeySerializer; +import io.github.md5sha256.addictiveexperience.util.configurate.BlockPositionSerializer; +import io.github.md5sha256.addictiveexperience.util.configurate.DrugPlantDataSerializer; +import io.github.md5sha256.addictiveexperience.util.configurate.DrugPlantMetaSerializer; +import io.github.md5sha256.addictiveexperience.util.configurate.VariableStopwatchSerializer; +import io.github.md5sha256.addictiveexperience.util.configurate.WorldSerializer; +import net.kyori.adventure.key.Key; +import org.bukkit.NamespacedKey; +import org.bukkit.World; +import org.bukkit.persistence.PersistentDataContainer; +import org.bukkit.persistence.PersistentDataType; +import org.bukkit.plugin.Plugin; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.spongepowered.configurate.ConfigurationNode; +import org.spongepowered.configurate.ConfigurationOptions; +import org.spongepowered.configurate.gson.GsonConfigurationLoader; +import org.spongepowered.configurate.loader.ConfigurationLoader; + +import java.io.BufferedReader; +import java.io.BufferedWriter; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.io.OutputStreamWriter; +import java.io.Reader; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public final class ConfigurateResolver { + + private final ConfigurationOptions options; + + public ConfigurateResolver(@NotNull Plugin plugin, @NotNull DrugRegistry drugRegistry) { + this.options = ConfigurationOptions + .defaults() + .serializers(builder -> builder + .register(World.class, new WorldSerializer(plugin.getServer())) + .register(BlockPosition.class, new BlockPositionSerializer()) + .register(DrugPlantMeta.class, new DrugPlantMetaSerializer(drugRegistry)) + .register(DrugPlantData.class, new DrugPlantDataSerializer()) + .register(Key.class, new AdventureKeySerializer()) + .register(VariableStopwatch.class, new VariableStopwatchSerializer())); + } + + public Map fromBytes(byte[] bytes) { + if (bytes == null) { + return Collections.emptyMap(); + } + final ConfigurationLoader loader = loader(bytes); + final Collection data; + try { + final ConfigurationNode root = loader.load(); + data = root.getList(DrugPlantData.class, Collections.emptyList()); + } catch (IOException ex) { + // FIXME log warning + ex.printStackTrace(); + return Collections.emptyMap(); + } + final Map map = new HashMap<>(data.size()); + for (DrugPlantData plantData : data) { + map.put(plantData.position().getPosition(), plantData); + } + return new HashMap<>(map); + } + + public byte @Nullable [] toBytes(@NotNull Collection data) { + + final ByteArrayOutputStream bos = new ByteArrayOutputStream(1024); + final ConfigurationLoader loader = loader(bos); + final ConfigurationNode node = loader.createNode(); + final List dataAsList; + if (data instanceof List) { + dataAsList = (List) data; + } else { + dataAsList = new ArrayList<>(data); + } + try { + node.setList(DrugPlantData.class, dataAsList); + loader.save(node); + } catch (IOException ex) { + // FIXME log warning + ex.printStackTrace(); + // Remove all data + return null; + } + return bos.toByteArray(); + } + + private ConfigurationLoader loader(byte[] raw) { + final Reader reader = new InputStreamReader(new ByteArrayInputStream(raw), + StandardCharsets.UTF_8); + return GsonConfigurationLoader.builder() + .defaultOptions(this.options) + .source(() -> new BufferedReader(reader)) + .lenient(true) + .build(); + } + + private ConfigurationLoader loader(@NotNull OutputStream os) { + final OutputStreamWriter osw = new OutputStreamWriter(os, StandardCharsets.UTF_8); + return GsonConfigurationLoader.builder() + .defaultOptions(this.options) + .sink(() -> new BufferedWriter(osw)) + .lenient(true) + .build(); + } +} diff --git a/core/src/main/java/io/github/md5sha256/addictiveexperience/implementation/plant/PDCResolver.java b/core/src/main/java/io/github/md5sha256/addictiveexperience/implementation/plant/PDCResolver.java index 4b64113..2c551b8 100644 --- a/core/src/main/java/io/github/md5sha256/addictiveexperience/implementation/plant/PDCResolver.java +++ b/core/src/main/java/io/github/md5sha256/addictiveexperience/implementation/plant/PDCResolver.java @@ -43,43 +43,20 @@ final class PDCResolver implements PlantDataResolver { private final NamespacedKey key; - private final ConfigurationOptions options; + private final ConfigurateResolver resolver; @AssistedInject PDCResolver(@NotNull Plugin plugin, @NotNull DrugRegistry drugRegistry, @Assisted @NotNull World world) { this.key = new NamespacedKey(plugin, "drug-plant-data"); - this.options = ConfigurationOptions - .defaults() - .serializers(builder -> builder - .register(DrugPlantMeta.class, new DrugPlantMetaSerializer(drugRegistry)) - .register(DrugPlantData.class, new DrugPlantDataSerializer(world)) - .register(Key.class, new AdventureKeySerializer()) - .register(VariableStopwatch.class, new VariableStopwatchSerializer())); + this.resolver = new ConfigurateResolver(plugin, drugRegistry); } @Override public @NotNull Map loadData(@NotNull final ChunkPosition chunk) { final PersistentDataContainer container = chunk.getChunk().getPersistentDataContainer(); final byte[] raw = container.get(this.key, PersistentDataType.BYTE_ARRAY); - if (raw == null) { - return Collections.emptyMap(); - } - final ConfigurationLoader loader = loader(raw); - final Collection data; - try { - final ConfigurationNode root = loader.load(); - data = root.getList(DrugPlantData.class, Collections.emptyList()); - } catch (IOException ex) { - // FIXME log warning - ex.printStackTrace(); - return Collections.emptyMap(); - } - final Map map = new HashMap<>(data.size()); - for (DrugPlantData plantData : data) { - map.put(plantData.position().getPosition(), plantData); - } - return new HashMap<>(map); + return this.resolver.fromBytes(raw); } @Override @@ -89,49 +66,17 @@ public void saveData(@NotNull final ChunkPosition chunk, clearData(chunk); return; } - final PersistentDataContainer container = chunk.getChunk().getPersistentDataContainer(); - final ByteArrayOutputStream bos = new ByteArrayOutputStream(1024); - final ConfigurationLoader loader = loader(bos); - final ConfigurationNode node = loader.createNode(); - final List dataAsList; - if (data instanceof List) { - dataAsList = (List) data; - } else { - dataAsList = new ArrayList<>(data); - } - try { - node.setList(DrugPlantData.class, dataAsList); - loader.save(node); - } catch (IOException ex) { - // FIXME log warning - ex.printStackTrace(); - // Remove all data - container.remove(this.key); + final byte[] bytes = this.resolver.toBytes(data); + if (bytes == null) { + clearData(chunk); return; } - container.set(this.key, PersistentDataType.BYTE_ARRAY, bos.toByteArray()); + final PersistentDataContainer container = chunk.getChunk().getPersistentDataContainer(); + container.set(this.key, PersistentDataType.BYTE_ARRAY, bytes); } @Override public void clearData(@NotNull final ChunkPosition chunk) { chunk.getChunk().getPersistentDataContainer().remove(this.key); } - - private ConfigurationLoader loader(byte[] raw) { - final Reader reader = new InputStreamReader(new ByteArrayInputStream(raw), StandardCharsets.UTF_8); - return GsonConfigurationLoader.builder() - .defaultOptions(this.options) - .source(() -> new BufferedReader(reader)) - .lenient(true) - .build(); - } - - private ConfigurationLoader loader(@NotNull OutputStream os) { - final OutputStreamWriter osw = new OutputStreamWriter(os, StandardCharsets.UTF_8); - return GsonConfigurationLoader.builder() - .defaultOptions(this.options) - .sink(() -> new BufferedWriter(osw)) - .lenient(true) - .build(); - } } diff --git a/core/src/main/java/io/github/md5sha256/addictiveexperience/util/configurate/BlockPositionSerializer.java b/core/src/main/java/io/github/md5sha256/addictiveexperience/util/configurate/BlockPositionSerializer.java new file mode 100644 index 0000000..01ec2dd --- /dev/null +++ b/core/src/main/java/io/github/md5sha256/addictiveexperience/util/configurate/BlockPositionSerializer.java @@ -0,0 +1,41 @@ +package io.github.md5sha256.addictiveexperience.util.configurate; + +import com.github.md5sha256.spigotutils.blocks.BlockPosition; +import org.bukkit.World; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.spongepowered.configurate.ConfigurationNode; +import org.spongepowered.configurate.serialize.SerializationException; +import org.spongepowered.configurate.serialize.TypeSerializer; + +import java.lang.reflect.Type; +import java.util.Objects; + +public class BlockPositionSerializer implements TypeSerializer { + + private static final String KEY_POSITION = "position"; + private static final String KEY_WORLD = "world"; + + @Override + public BlockPosition deserialize(Type type, + ConfigurationNode node) throws SerializationException { + ConfigurationNode position = node.node(KEY_POSITION); + ConfigurationNode world = node.node(KEY_WORLD); + World worldInstance = Objects.requireNonNull(world.get(World.class)); + return new BlockPosition(worldInstance, position.getLong()); + } + + @Override + public void serialize(Type type, + @Nullable BlockPosition obj, + ConfigurationNode node) throws SerializationException { + if (obj == null) { + node.removeChild(KEY_POSITION); + node.removeChild(KEY_WORLD); + return; + } + node.node(KEY_POSITION).set(obj.getPosition()); + node.node(KEY_WORLD).set(obj.getWorld()); + } + + +} diff --git a/core/src/main/java/io/github/md5sha256/addictiveexperience/util/configurate/DrugPlantDataSerializer.java b/core/src/main/java/io/github/md5sha256/addictiveexperience/util/configurate/DrugPlantDataSerializer.java index ff336d7..86a819f 100644 --- a/core/src/main/java/io/github/md5sha256/addictiveexperience/util/configurate/DrugPlantDataSerializer.java +++ b/core/src/main/java/io/github/md5sha256/addictiveexperience/util/configurate/DrugPlantDataSerializer.java @@ -13,6 +13,7 @@ import org.spongepowered.configurate.serialize.TypeSerializer; import java.lang.reflect.Type; +import java.util.Objects; public class DrugPlantDataSerializer implements TypeSerializer { @@ -21,12 +22,6 @@ public class DrugPlantDataSerializer implements TypeSerializer { private static final String KEY_STOPWATCH = "stopwatch"; private static final String KEY_POSITION = "position"; - private final World world; - - public DrugPlantDataSerializer(@NotNull World world) { - this.world = world; - } - @Override public DrugPlantData deserialize(final Type type, final ConfigurationNode node) throws SerializationException { @@ -44,8 +39,9 @@ public DrugPlantData deserialize(final Type type, if (elapsed == null) { throw new SerializationException("Missing elapsed time!"); } + BlockPosition blockPosition = position.get(BlockPosition.class); builder.meta(plantMeta) - .position(new BlockPosition(world, position.getLong())) + .position(Objects.requireNonNull(blockPosition)) .startTimeEpochMilli(startTime.getLong()) .elapsed(elapsed); try { @@ -73,6 +69,6 @@ public void serialize(final Type type, final ConfigurationNode startTime = node.node(KEY_START_TIME); startTime.set(obj.startTimeEpochMillis()); final ConfigurationNode position = node.node(KEY_POSITION); - position.set(obj.position().getPosition()); + position.set(obj.position()); } } diff --git a/core/src/main/java/io/github/md5sha256/addictiveexperience/util/configurate/WorldSerializer.java b/core/src/main/java/io/github/md5sha256/addictiveexperience/util/configurate/WorldSerializer.java new file mode 100644 index 0000000..9d7fa0b --- /dev/null +++ b/core/src/main/java/io/github/md5sha256/addictiveexperience/util/configurate/WorldSerializer.java @@ -0,0 +1,51 @@ +package io.github.md5sha256.addictiveexperience.util.configurate; + +import org.bukkit.Server; +import org.bukkit.World; +import org.spongepowered.configurate.serialize.ScalarSerializer; +import org.spongepowered.configurate.serialize.SerializationException; + +import java.lang.reflect.Type; +import java.util.UUID; +import java.util.function.Predicate; + +public class WorldSerializer extends ScalarSerializer { + + private final Server server; + + public WorldSerializer(Server server) { + super(World.class); + this.server = server; + } + + private World getOrThrow(UUID uuid) throws SerializationException { + World world = this.server.getWorld(uuid); + if (world == null) { + throw new SerializationException("World not found: " + uuid); + } + return world; + } + + @Override + public World deserialize(Type type, Object obj) throws SerializationException { + if (obj instanceof UUID uuid) { + return getOrThrow(uuid); + } else if (obj instanceof String s) { + try { + return getOrThrow(UUID.fromString(s)); + } catch (IllegalArgumentException ex) { + throw new SerializationException(ex); + } + } + throw new SerializationException("Unsupported type: " + type); + } + + @Override + protected Object serialize(World item, Predicate> typeSupported) { + if (typeSupported.test(UUID.class)) { + return item.getUID(); + } else { + return item.getUID().toString(); + } + } +}