From 703e02d6b23165003835692b0213a20f0a627e9d Mon Sep 17 00:00:00 2001 From: Dico Date: Fri, 3 Aug 2018 03:25:52 +0100 Subject: Clean up code for polymorphic options serialization, fix logger configuration --- build.gradle.kts | 3 +- src/main/kotlin/io/dico/parcels2/Options.kt | 104 ------------------ .../kotlin/io/dico/parcels2/ParcelGenerator.kt | 21 +--- src/main/kotlin/io/dico/parcels2/ParcelWorld.kt | 3 +- src/main/kotlin/io/dico/parcels2/ParcelsPlugin.kt | 9 +- .../parcels2/defaultimpl/DefaultParcelGenerator.kt | 25 +---- .../parcels2/defaultimpl/ParcelProviderImpl.kt | 2 +- .../dico/parcels2/defaultimpl/ParcelWorldImpl.kt | 5 +- .../io/dico/parcels2/options/GeneratorOptions.kt | 36 ++++++ .../io/dico/parcels2/options/MigrationOptions.kt | 17 +++ .../kotlin/io/dico/parcels2/options/Options.kt | 51 +++++++++ .../io/dico/parcels2/options/OptionsMapper.kt | 66 +++++++++++ .../io/dico/parcels2/options/PolymorphicOptions.kt | 89 +++++++++++++++ .../io/dico/parcels2/options/StorageOptions.kt | 45 ++++++++ src/main/kotlin/io/dico/parcels2/storage/Hikari.kt | 2 +- .../kotlin/io/dico/parcels2/storage/Jackson.kt | 122 --------------------- .../io/dico/parcels2/storage/StorageFactory.kt | 43 -------- .../parcels2/storage/migration/MigrationFactory.kt | 5 - src/main/resources/logback.xml | 7 +- 19 files changed, 324 insertions(+), 331 deletions(-) delete mode 100644 src/main/kotlin/io/dico/parcels2/Options.kt create mode 100644 src/main/kotlin/io/dico/parcels2/options/GeneratorOptions.kt create mode 100644 src/main/kotlin/io/dico/parcels2/options/MigrationOptions.kt create mode 100644 src/main/kotlin/io/dico/parcels2/options/Options.kt create mode 100644 src/main/kotlin/io/dico/parcels2/options/OptionsMapper.kt create mode 100644 src/main/kotlin/io/dico/parcels2/options/PolymorphicOptions.kt create mode 100644 src/main/kotlin/io/dico/parcels2/options/StorageOptions.kt delete mode 100644 src/main/kotlin/io/dico/parcels2/storage/Jackson.kt delete mode 100644 src/main/kotlin/io/dico/parcels2/storage/StorageFactory.kt delete mode 100644 src/main/kotlin/io/dico/parcels2/storage/migration/MigrationFactory.kt diff --git a/build.gradle.kts b/build.gradle.kts index 11975a1..fa4eb09 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -72,7 +72,8 @@ dependencies { compile("org.jetbrains.exposed:exposed:0.10.3") { isTransitive = false } compile("joda-time:joda-time:2.10") compile("com.zaxxer:HikariCP:3.2.0") - compile("ch.qos.logback:logback-classic:1.2.3") + compile("ch.qos.logback:logback-classic:1.2.3") { isTransitive = false } + compile("ch.qos.logback:logback-core:1.2.3") { isTransitive = false } val jacksonVersion = "2.9.6" compile("com.fasterxml.jackson.core:jackson-core:$jacksonVersion") diff --git a/src/main/kotlin/io/dico/parcels2/Options.kt b/src/main/kotlin/io/dico/parcels2/Options.kt deleted file mode 100644 index aefa9e1..0000000 --- a/src/main/kotlin/io/dico/parcels2/Options.kt +++ /dev/null @@ -1,104 +0,0 @@ -package io.dico.parcels2 - -import com.fasterxml.jackson.annotation.JsonIgnore -import io.dico.parcels2.blockvisitor.TickWorktimeOptions -import io.dico.parcels2.defaultimpl.DefaultGeneratorOptions -import io.dico.parcels2.storage.Storage -import io.dico.parcels2.storage.StorageFactory -import io.dico.parcels2.storage.yamlObjectMapper -import org.bukkit.GameMode -import org.bukkit.Material -import java.io.Reader -import java.io.Writer -import java.util.EnumSet - -class Options { - var worlds: Map = hashMapOf() - private set - var storage: StorageOptions = StorageOptions("postgresql", DataConnectionOptions()) - var tickWorktime: TickWorktimeOptions = TickWorktimeOptions(20, 1) - - fun addWorld(name: String, - generatorOptions: GeneratorOptions? = null, - worldOptions: WorldOptions? = null) { - val optionsHolder = WorldOptionsHolder( - generatorOptions ?: DefaultGeneratorOptions(), - worldOptions ?: WorldOptions() - ) - - (worlds as MutableMap).put(name, optionsHolder) - } - - fun writeTo(writer: Writer) = yamlObjectMapper.writeValue(writer, this) - - fun mergeFrom(reader: Reader) = yamlObjectMapper.readerForUpdating(this).readValue(reader) - - override fun toString(): String = yamlObjectMapper.writeValueAsString(this) - -} - -class WorldOptionsHolder(var generator: GeneratorOptions = DefaultGeneratorOptions(), - var runtime: WorldOptions = WorldOptions()) - -data class WorldOptions(var gameMode: GameMode? = GameMode.CREATIVE, - var dayTime: Boolean = true, - var noWeather: Boolean = true, - var preventWeatherBlockChanges: Boolean = true, - var preventBlockSpread: Boolean = true, // TODO - var dropEntityItems: Boolean = true, - var doTileDrops: Boolean = false, - var disableExplosions: Boolean = true, - var blockPortalCreation: Boolean = true, - var blockMobSpawning: Boolean = true, - var blockedItems: Set = EnumSet.of(Material.FLINT_AND_STEEL, Material.SNOWBALL), - var axisLimit: Int = 10) { - -} - -abstract class GeneratorOptions { - - abstract fun generatorFactory(): GeneratorFactory - - fun newGenerator(worldName: String) = generatorFactory().newParcelGenerator(worldName, this) - -} - -class StorageOptions(val dialect: String, - val options: Any) { - - @get:JsonIgnore - val factory = StorageFactory.getFactory(dialect) - ?: throw IllegalArgumentException("Invalid storage dialect: $dialect") - - fun newStorageInstance(): Storage = factory.newStorageInstance(dialect, options) - -} - -data class DataConnectionOptions(val address: String = "localhost", - val database: String = "parcels", - val username: String = "root", - val password: String = "", - val poolSize: Int = 4) { - - fun splitAddressAndPort(defaultPort: Int = 3306): Pair? { - val idx = address.indexOf(":").takeUnless { it == -1 } ?: return Pair(address, defaultPort) - - val addressName = address.substring(0, idx).takeUnless { it.isBlank() } ?: return null.also { - logger.error("(Invalidly) blank address in data storage options") - } - - val port = address.substring(idx + 1).toIntOrNull() ?: return null.also { - logger.error("Invalid port number in data storage options: $it, using $defaultPort as default") - } - - return Pair(addressName, port) - } - -} - -data class DataFileOptions(val location: String = "/flatfile-storage/") - -class MigrationOptions() { - - -} diff --git a/src/main/kotlin/io/dico/parcels2/ParcelGenerator.kt b/src/main/kotlin/io/dico/parcels2/ParcelGenerator.kt index ff28537..6f504d0 100644 --- a/src/main/kotlin/io/dico/parcels2/ParcelGenerator.kt +++ b/src/main/kotlin/io/dico/parcels2/ParcelGenerator.kt @@ -4,6 +4,7 @@ import io.dico.parcels2.blockvisitor.RegionTraversal import io.dico.parcels2.blockvisitor.Worker import io.dico.parcels2.blockvisitor.WorktimeLimiter import io.dico.parcels2.defaultimpl.DefaultParcelGenerator +import io.dico.parcels2.options.GeneratorOptions import io.dico.parcels2.util.Vec2i import org.bukkit.Chunk import org.bukkit.Location @@ -17,26 +18,6 @@ import java.util.HashMap import java.util.Random import kotlin.reflect.KClass -object GeneratorFactories { - private val map: MutableMap = HashMap() - - fun registerFactory(generator: GeneratorFactory): Boolean = map.putIfAbsent(generator.name, generator) == null - - fun getFactory(name: String): GeneratorFactory? = map.get(name) - - init { - registerFactory(DefaultParcelGenerator.Factory) - } -} - -interface GeneratorFactory { - val name: String - - val optionsClass: KClass - - fun newParcelGenerator(worldName: String, options: GeneratorOptions): ParcelGenerator -} - abstract class ParcelGenerator : ChunkGenerator() { abstract val world: World diff --git a/src/main/kotlin/io/dico/parcels2/ParcelWorld.kt b/src/main/kotlin/io/dico/parcels2/ParcelWorld.kt index 16f108f..0dcdfbd 100644 --- a/src/main/kotlin/io/dico/parcels2/ParcelWorld.kt +++ b/src/main/kotlin/io/dico/parcels2/ParcelWorld.kt @@ -1,5 +1,6 @@ package io.dico.parcels2 +import io.dico.parcels2.options.RuntimeWorldOptions import io.dico.parcels2.storage.Storage import io.dico.parcels2.util.Vec2i import io.dico.parcels2.util.floor @@ -76,7 +77,7 @@ interface ParcelWorld : ParcelLocator, ParcelContainer, ParcelBlockManager { val id: ParcelWorldId val name: String val uid: UUID? - val options: WorldOptions + val options: RuntimeWorldOptions val generator: ParcelGenerator val storage: Storage val container: ParcelContainer diff --git a/src/main/kotlin/io/dico/parcels2/ParcelsPlugin.kt b/src/main/kotlin/io/dico/parcels2/ParcelsPlugin.kt index 6d08d28..ffa11c7 100644 --- a/src/main/kotlin/io/dico/parcels2/ParcelsPlugin.kt +++ b/src/main/kotlin/io/dico/parcels2/ParcelsPlugin.kt @@ -10,8 +10,9 @@ import io.dico.parcels2.defaultimpl.GlobalAddedDataManagerImpl import io.dico.parcels2.defaultimpl.ParcelProviderImpl import io.dico.parcels2.listener.ParcelEntityTracker import io.dico.parcels2.listener.ParcelListeners +import io.dico.parcels2.options.Options +import io.dico.parcels2.options.optionsMapper import io.dico.parcels2.storage.Storage -import io.dico.parcels2.storage.yamlObjectMapper import io.dico.parcels2.util.FunctionHelper import io.dico.parcels2.util.tryCreate import org.bukkit.Bukkit @@ -60,7 +61,7 @@ class ParcelsPlugin : JavaPlugin() { if (!loadOptions()) return false try { - storage = options.storage.newStorageInstance() + storage = options.storage.newInstance() storage.init() } catch (ex: Exception) { plogger.error("Failed to connect to database", ex) @@ -83,11 +84,11 @@ class ParcelsPlugin : JavaPlugin() { fun loadOptions(): Boolean { when { - optionsFile.exists() -> yamlObjectMapper.readerForUpdating(options).readValue(optionsFile) + optionsFile.exists() -> optionsMapper.readerForUpdating(options).readValue(optionsFile) optionsFile.tryCreate() -> { options.addWorld("parcels") try { - yamlObjectMapper.writeValue(optionsFile, options) + optionsMapper.writeValue(optionsFile, options) } catch (ex: Throwable) { optionsFile.delete() throw ex diff --git a/src/main/kotlin/io/dico/parcels2/defaultimpl/DefaultParcelGenerator.kt b/src/main/kotlin/io/dico/parcels2/defaultimpl/DefaultParcelGenerator.kt index 90eb631..7dbcb78 100644 --- a/src/main/kotlin/io/dico/parcels2/defaultimpl/DefaultParcelGenerator.kt +++ b/src/main/kotlin/io/dico/parcels2/defaultimpl/DefaultParcelGenerator.kt @@ -4,6 +4,7 @@ import io.dico.parcels2.* import io.dico.parcels2.blockvisitor.RegionTraversal import io.dico.parcels2.blockvisitor.Worker import io.dico.parcels2.blockvisitor.WorktimeLimiter +import io.dico.parcels2.options.DefaultGeneratorOptions import io.dico.parcels2.util.* import org.bukkit.* import org.bukkit.block.Biome @@ -17,21 +18,6 @@ import java.util.Random private val airType = Bukkit.createBlockData(Material.AIR) -data class DefaultGeneratorOptions(var defaultBiome: Biome = Biome.JUNGLE, - var wallType: BlockData = Bukkit.createBlockData(Material.STONE_SLAB), - var floorType: BlockData = Bukkit.createBlockData(Material.QUARTZ_BLOCK), - var fillType: BlockData = Bukkit.createBlockData(Material.QUARTZ_BLOCK), - var pathMainType: BlockData = Bukkit.createBlockData(Material.SANDSTONE), - var pathAltType: BlockData = Bukkit.createBlockData(Material.REDSTONE_BLOCK), - var parcelSize: Int = 101, - var pathSize: Int = 9, - var floorHeight: Int = 64, - var offsetX: Int = 0, - var offsetZ: Int = 0) : GeneratorOptions() { - - override fun generatorFactory(): GeneratorFactory = DefaultParcelGenerator.Factory -} - class DefaultParcelGenerator(val name: String, private val o: DefaultGeneratorOptions) : ParcelGenerator() { private var _world: World? = null override val world: World @@ -44,15 +30,6 @@ class DefaultParcelGenerator(val name: String, private val o: DefaultGeneratorOp } private var maxHeight = 0 - - companion object Factory : GeneratorFactory { - override val name get() = "default" - override val optionsClass get() = DefaultGeneratorOptions::class - override fun newParcelGenerator(worldName: String, options: GeneratorOptions): ParcelGenerator { - return DefaultParcelGenerator(worldName, options as DefaultGeneratorOptions) - } - } - val sectionSize = o.parcelSize + o.pathSize val pathOffset = (if (o.pathSize % 2 == 0) o.pathSize + 2 else o.pathSize + 1) / 2 val makePathMain = o.pathSize > 2 diff --git a/src/main/kotlin/io/dico/parcels2/defaultimpl/ParcelProviderImpl.kt b/src/main/kotlin/io/dico/parcels2/defaultimpl/ParcelProviderImpl.kt index 52e675d..569d18f 100644 --- a/src/main/kotlin/io/dico/parcels2/defaultimpl/ParcelProviderImpl.kt +++ b/src/main/kotlin/io/dico/parcels2/defaultimpl/ParcelProviderImpl.kt @@ -28,7 +28,7 @@ class ParcelProviderImpl(val plugin: ParcelsPlugin) : ParcelProvider { override fun getWorldGenerator(worldName: String): ParcelGenerator? { return _worlds[worldName]?.generator ?: _generators[worldName] - ?: options.worlds[worldName]?.generator?.newGenerator(worldName)?.also { _generators[worldName] = it } + ?: options.worlds[worldName]?.generator?.newInstance(worldName)?.also { _generators[worldName] = it } } override fun loadWorlds() { diff --git a/src/main/kotlin/io/dico/parcels2/defaultimpl/ParcelWorldImpl.kt b/src/main/kotlin/io/dico/parcels2/defaultimpl/ParcelWorldImpl.kt index 590794d..4a168f5 100644 --- a/src/main/kotlin/io/dico/parcels2/defaultimpl/ParcelWorldImpl.kt +++ b/src/main/kotlin/io/dico/parcels2/defaultimpl/ParcelWorldImpl.kt @@ -4,6 +4,7 @@ package io.dico.parcels2.defaultimpl import io.dico.parcels2.* import io.dico.parcels2.blockvisitor.WorktimeLimiter +import io.dico.parcels2.options.RuntimeWorldOptions import io.dico.parcels2.storage.Storage import org.bukkit.World import java.util.UUID @@ -11,7 +12,7 @@ import java.util.UUID class ParcelWorldImpl private constructor(override val world: World, override val generator: ParcelGenerator, - override var options: WorldOptions, + override var options: RuntimeWorldOptions, override val storage: Storage, override val globalAddedData: GlobalAddedDataManager, containerFactory: ParcelContainerFactory, @@ -62,7 +63,7 @@ constructor(override val world: World, // Use this to be able to delegate blockManager and assign it to a property too, at least. operator fun invoke(world: World, generator: ParcelGenerator, - options: WorldOptions, + options: RuntimeWorldOptions, storage: Storage, globalAddedData: GlobalAddedDataManager, containerFactory: ParcelContainerFactory, diff --git a/src/main/kotlin/io/dico/parcels2/options/GeneratorOptions.kt b/src/main/kotlin/io/dico/parcels2/options/GeneratorOptions.kt new file mode 100644 index 0000000..ac4d975 --- /dev/null +++ b/src/main/kotlin/io/dico/parcels2/options/GeneratorOptions.kt @@ -0,0 +1,36 @@ +package io.dico.parcels2.options + +import io.dico.parcels2.ParcelGenerator +import io.dico.parcels2.defaultimpl.DefaultParcelGenerator +import org.bukkit.Bukkit +import org.bukkit.Material +import org.bukkit.block.Biome +import org.bukkit.block.data.BlockData +import kotlin.reflect.KClass + +object GeneratorOptionsFactories : PolymorphicOptionsFactories("name", GeneratorOptions::class, DefaultGeneratorOptionsFactory()) + +class GeneratorOptions(name: String, options: Any) : PolymorphicOptions(name, options, GeneratorOptionsFactories) { + fun newInstance(worldName: String) = factory.newInstance(key, options, worldName) +} + +private class DefaultGeneratorOptionsFactory : PolymorphicOptionsFactory { + override val supportedKeys: List = listOf("default") + override val optionsClass: KClass get() = DefaultGeneratorOptions::class + + override fun newInstance(key: String, options: Any, vararg extra: Any?): ParcelGenerator { + return DefaultParcelGenerator(extra.first() as String, options as DefaultGeneratorOptions) + } +} + +class DefaultGeneratorOptions(val defaultBiome: Biome = Biome.JUNGLE, + val wallType: BlockData = Bukkit.createBlockData(Material.STONE_SLAB), + val floorType: BlockData = Bukkit.createBlockData(Material.QUARTZ_BLOCK), + val fillType: BlockData = Bukkit.createBlockData(Material.QUARTZ_BLOCK), + val pathMainType: BlockData = Bukkit.createBlockData(Material.SANDSTONE), + val pathAltType: BlockData = Bukkit.createBlockData(Material.REDSTONE_BLOCK), + val parcelSize: Int = 101, + val pathSize: Int = 9, + val floorHeight: Int = 64, + val offsetX: Int = 0, + val offsetZ: Int = 0) \ No newline at end of file diff --git a/src/main/kotlin/io/dico/parcels2/options/MigrationOptions.kt b/src/main/kotlin/io/dico/parcels2/options/MigrationOptions.kt new file mode 100644 index 0000000..bb4e052 --- /dev/null +++ b/src/main/kotlin/io/dico/parcels2/options/MigrationOptions.kt @@ -0,0 +1,17 @@ +package io.dico.parcels2.options + +import io.dico.parcels2.storage.migration.Migration +import kotlin.reflect.KClass + +object MigrationOptionsFactories : PolymorphicOptionsFactories("kind", MigrationOptions::class, PlotmeMigrationFactory()) + +class MigrationOptions(kind: String, options: Any) : SimplePolymorphicOptions(kind, options, MigrationOptionsFactories) + +private class PlotmeMigrationFactory : PolymorphicOptionsFactory { + override val supportedKeys = listOf("plotme-0.17") + override val optionsClass: KClass get() = TODO() + + override fun newInstance(key: String, options: Any, vararg extra: Any?): Migration { + TODO() + } +} diff --git a/src/main/kotlin/io/dico/parcels2/options/Options.kt b/src/main/kotlin/io/dico/parcels2/options/Options.kt new file mode 100644 index 0000000..fb2e4cc --- /dev/null +++ b/src/main/kotlin/io/dico/parcels2/options/Options.kt @@ -0,0 +1,51 @@ +package io.dico.parcels2.options + +import io.dico.parcels2.blockvisitor.TickWorktimeOptions +import org.bukkit.GameMode +import org.bukkit.Material +import java.io.Reader +import java.io.Writer +import java.util.EnumSet + +class Options { + var worlds: Map = hashMapOf() + private set + var storage: StorageOptions = StorageOptions("postgresql", DataConnectionOptions()) + var tickWorktime: TickWorktimeOptions = TickWorktimeOptions(20, 1) + + fun addWorld(name: String, + generatorOptions: GeneratorOptions? = null, + worldOptions: RuntimeWorldOptions? = null) { + val optionsHolder = WorldOptions( + generatorOptions ?: GeneratorOptions("default", DefaultGeneratorOptions()), + worldOptions ?: RuntimeWorldOptions() + ) + + (worlds as MutableMap).put(name, optionsHolder) + } + + fun writeTo(writer: Writer) = optionsMapper.writeValue(writer, this) + + fun mergeFrom(reader: Reader) = optionsMapper.readerForUpdating(this).readValue(reader) + + override fun toString(): String = optionsMapper.writeValueAsString(this) + +} + +class WorldOptions(val generator: GeneratorOptions, + var runtime: RuntimeWorldOptions = RuntimeWorldOptions()) + +class RuntimeWorldOptions(var gameMode: GameMode? = GameMode.CREATIVE, + var dayTime: Boolean = true, + var noWeather: Boolean = true, + var preventWeatherBlockChanges: Boolean = true, + var preventBlockSpread: Boolean = true, // TODO + var dropEntityItems: Boolean = true, + var doTileDrops: Boolean = false, + var disableExplosions: Boolean = true, + var blockPortalCreation: Boolean = true, + var blockMobSpawning: Boolean = true, + var blockedItems: Set = EnumSet.of(Material.FLINT_AND_STEEL, Material.SNOWBALL), + var axisLimit: Int = 10) + +class DataFileOptions(val location: String = "/flatfile-storage/") diff --git a/src/main/kotlin/io/dico/parcels2/options/OptionsMapper.kt b/src/main/kotlin/io/dico/parcels2/options/OptionsMapper.kt new file mode 100644 index 0000000..671a25f --- /dev/null +++ b/src/main/kotlin/io/dico/parcels2/options/OptionsMapper.kt @@ -0,0 +1,66 @@ +package io.dico.parcels2.options + +import com.fasterxml.jackson.core.JsonGenerator +import com.fasterxml.jackson.core.JsonParser +import com.fasterxml.jackson.databind.DeserializationContext +import com.fasterxml.jackson.databind.ObjectMapper +import com.fasterxml.jackson.databind.PropertyNamingStrategy +import com.fasterxml.jackson.databind.SerializerProvider +import com.fasterxml.jackson.databind.deser.std.StdDeserializer +import com.fasterxml.jackson.databind.ser.std.StdSerializer +import com.fasterxml.jackson.dataformat.yaml.YAMLFactory +import com.fasterxml.jackson.module.kotlin.KotlinModule +import org.bukkit.Bukkit +import org.bukkit.block.data.BlockData + +val optionsMapper = ObjectMapper(YAMLFactory()).apply { + propertyNamingStrategy = PropertyNamingStrategy.KEBAB_CASE + + val kotlinModule = KotlinModule() + + with(kotlinModule) { + /* + setSerializerModifier(object : BeanSerializerModifier() { + @Suppress("UNCHECKED_CAST") + override fun modifySerializer(config: SerializationConfig?, beanDesc: BeanDescription, serializer: JsonSerializer<*>): JsonSerializer<*> { + + val newSerializer = if (GeneratorOptions::class.isSuperclassOf(beanDesc.beanClass.kotlin)) { + GeneratorOptionsSerializer(serializer as JsonSerializer) + } else { + serializer + } + + return super.modifySerializer(config, beanDesc, newSerializer) + } + })*/ + + addSerializer(BlockDataSerializer()) + addDeserializer(BlockData::class.java, BlockDataDeserializer()) + + GeneratorOptionsFactories.registerSerialization(this) + StorageOptionsFactories.registerSerialization(this) + MigrationOptionsFactories.registerSerialization(this) + } + + registerModule(kotlinModule) +} + +private class BlockDataSerializer : StdSerializer(BlockData::class.java) { + + override fun serialize(value: BlockData, gen: JsonGenerator, provider: SerializerProvider) { + gen.writeString(value.asString) + } + +} + +private class BlockDataDeserializer : StdDeserializer(BlockData::class.java) { + + override fun deserialize(p: JsonParser, ctxt: DeserializationContext): BlockData? { + try { + return Bukkit.createBlockData(p.valueAsString) + } catch (ex: Exception) { + throw RuntimeException("Exception occurred at ${p.currentLocation}", ex) + } + } + +} diff --git a/src/main/kotlin/io/dico/parcels2/options/PolymorphicOptions.kt b/src/main/kotlin/io/dico/parcels2/options/PolymorphicOptions.kt new file mode 100644 index 0000000..aa60f39 --- /dev/null +++ b/src/main/kotlin/io/dico/parcels2/options/PolymorphicOptions.kt @@ -0,0 +1,89 @@ +package io.dico.parcels2.options + +import com.fasterxml.jackson.core.JsonGenerator +import com.fasterxml.jackson.core.JsonParser +import com.fasterxml.jackson.databind.DeserializationContext +import com.fasterxml.jackson.databind.JsonDeserializer +import com.fasterxml.jackson.databind.JsonNode +import com.fasterxml.jackson.databind.SerializerProvider +import com.fasterxml.jackson.databind.module.SimpleModule +import com.fasterxml.jackson.databind.ser.std.StdSerializer +import io.dico.parcels2.logger +import kotlin.reflect.KClass + +abstract class PolymorphicOptions(val key: String, + val options: Any, + factories: PolymorphicOptionsFactories) { + val factory = factories.getFactory(key)!! +} + +abstract class SimplePolymorphicOptions(key: String, options: Any, factories: PolymorphicOptionsFactories) + : PolymorphicOptions(key, options, factories) { + fun newInstance(): T = factory.newInstance(key, options) +} + +interface PolymorphicOptionsFactory { + val supportedKeys: List + val optionsClass: KClass + fun newInstance(key: String, options: Any, vararg extra: Any?): T +} + +@Suppress("UNCHECKED_CAST") +abstract class PolymorphicOptionsFactories(val serializeKeyAs: String, + rootClass: KClass>, + vararg defaultFactories: PolymorphicOptionsFactory) { + val rootClass = rootClass as KClass> + private val map: MutableMap> = linkedMapOf() + val availableKeys: Collection get() = map.keys + + fun registerFactory(factory: PolymorphicOptionsFactory) = factory.supportedKeys.forEach { map.putIfAbsent(it.toLowerCase(), factory) } + + fun getFactory(key: String): PolymorphicOptionsFactory? = map[key.toLowerCase()] + + fun registerSerialization(module: SimpleModule) { + module.addSerializer(PolymorphicOptionsSerializer(this)) + module.addDeserializer(rootClass.java, PolymorphicOptionsDeserializer(this)) + } + + init { + defaultFactories.forEach { registerFactory(it) } + } +} + + +private class PolymorphicOptionsDeserializer(val factories: PolymorphicOptionsFactories) : JsonDeserializer>() { + + override fun deserialize(p: JsonParser, ctxt: DeserializationContext?): PolymorphicOptions { + val node = p.readValueAsTree() + val key = node.get(factories.serializeKeyAs).asText() + val factory = getFactory(key) + val optionsNode = node.get("options") + val options = p.codec.treeToValue(optionsNode, factory.optionsClass.java) + return factories.rootClass.constructors.first().call(key, options) + } + + private fun getFactory(key: String): PolymorphicOptionsFactory { + factories.getFactory(key)?.let { return it } + + logger.warn("Unknown ${factories.rootClass.simpleName} ${factories.serializeKeyAs}: $key. " + + "\nAvailable options: ${factories.availableKeys}") + + val default = factories.getFactory(factories.availableKeys.first()) + ?: throw IllegalStateException("No default ${factories.rootClass.simpleName} factory registered.") + return default + } + +} + +private class PolymorphicOptionsSerializer(val factories: PolymorphicOptionsFactories) : StdSerializer>(factories.rootClass.java) { + + override fun serialize(value: PolymorphicOptions, gen: JsonGenerator, sp: SerializerProvider?) { + with(gen) { + writeStartObject() + writeStringField(factories.serializeKeyAs, value.key) + writeFieldName("options") + writeObject(value.options) + writeEndObject() + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/io/dico/parcels2/options/StorageOptions.kt b/src/main/kotlin/io/dico/parcels2/options/StorageOptions.kt new file mode 100644 index 0000000..639be9d --- /dev/null +++ b/src/main/kotlin/io/dico/parcels2/options/StorageOptions.kt @@ -0,0 +1,45 @@ +package io.dico.parcels2.options + +import com.zaxxer.hikari.HikariDataSource +import io.dico.parcels2.logger +import io.dico.parcels2.storage.Storage +import io.dico.parcels2.storage.StorageWithCoroutineBacking +import io.dico.parcels2.storage.exposed.ExposedBacking +import io.dico.parcels2.storage.getHikariConfig + +object StorageOptionsFactories : PolymorphicOptionsFactories("dialect", StorageOptions::class, ConnectionStorageFactory()) + +class StorageOptions(dialect: String, options: Any) : SimplePolymorphicOptions(dialect, options, StorageOptionsFactories) + +private class ConnectionStorageFactory : PolymorphicOptionsFactory { + override val optionsClass = DataConnectionOptions::class + override val supportedKeys: List = listOf("postgresql", "mariadb") + + override fun newInstance(key: String, options: Any, vararg extra: Any?): Storage { + val hikariConfig = getHikariConfig(key, options as DataConnectionOptions) + val dataSourceFactory = suspend { HikariDataSource(hikariConfig) } + return StorageWithCoroutineBacking(ExposedBacking(dataSourceFactory)) + } +} + +data class DataConnectionOptions(val address: String = "localhost", + val database: String = "parcels", + val username: String = "root", + val password: String = "", + val poolSize: Int = 4) { + + fun splitAddressAndPort(defaultPort: Int = 3306): Pair? { + val idx = address.indexOf(":").takeUnless { it == -1 } ?: return Pair(address, defaultPort) + + val addressName = address.substring(0, idx).takeUnless { it.isBlank() } ?: return null.also { + logger.error("(Invalidly) blank address in data storage options") + } + + val port = address.substring(idx + 1).toIntOrNull() ?: return null.also { + logger.error("Invalid port number in data storage options: $it, using $defaultPort as default") + } + + return Pair(addressName, port) + } + +} \ No newline at end of file diff --git a/src/main/kotlin/io/dico/parcels2/storage/Hikari.kt b/src/main/kotlin/io/dico/parcels2/storage/Hikari.kt index 2dfa872..480d533 100644 --- a/src/main/kotlin/io/dico/parcels2/storage/Hikari.kt +++ b/src/main/kotlin/io/dico/parcels2/storage/Hikari.kt @@ -1,7 +1,7 @@ package io.dico.parcels2.storage import com.zaxxer.hikari.HikariConfig -import io.dico.parcels2.DataConnectionOptions +import io.dico.parcels2.options.DataConnectionOptions fun getHikariConfig(dialectName: String, dco: DataConnectionOptions): HikariConfig = HikariConfig().apply { diff --git a/src/main/kotlin/io/dico/parcels2/storage/Jackson.kt b/src/main/kotlin/io/dico/parcels2/storage/Jackson.kt deleted file mode 100644 index ede33ae..0000000 --- a/src/main/kotlin/io/dico/parcels2/storage/Jackson.kt +++ /dev/null @@ -1,122 +0,0 @@ -package io.dico.parcels2.storage - -import com.fasterxml.jackson.core.JsonGenerator -import com.fasterxml.jackson.core.JsonParser -import com.fasterxml.jackson.databind.* -import com.fasterxml.jackson.databind.deser.std.StdDeserializer -import com.fasterxml.jackson.databind.ser.BeanSerializerModifier -import com.fasterxml.jackson.databind.ser.std.StdSerializer -import com.fasterxml.jackson.dataformat.yaml.YAMLFactory -import com.fasterxml.jackson.module.kotlin.KotlinModule -import io.dico.parcels2.GeneratorFactories -import io.dico.parcels2.GeneratorOptions -import io.dico.parcels2.StorageOptions -import org.bukkit.Bukkit -import org.bukkit.block.data.BlockData -import kotlin.reflect.full.isSuperclassOf - -val yamlObjectMapper = ObjectMapper(YAMLFactory()).apply { - propertyNamingStrategy = PropertyNamingStrategy.KEBAB_CASE - - val kotlinModule = KotlinModule() - - with(kotlinModule) { - setSerializerModifier(object : BeanSerializerModifier() { - @Suppress("UNCHECKED_CAST") - override fun modifySerializer(config: SerializationConfig?, beanDesc: BeanDescription, serializer: JsonSerializer<*>): JsonSerializer<*> { - - val newSerializer = if (GeneratorOptions::class.isSuperclassOf(beanDesc.beanClass.kotlin)) { - GeneratorOptionsSerializer(serializer as JsonSerializer) - } else { - serializer - } - - return super.modifySerializer(config, beanDesc, newSerializer) - } - }) - - addSerializer(BlockDataSerializer()) - addDeserializer(BlockData::class.java, BlockDataDeserializer()) - - addSerializer(StorageOptionsSerializer()) - addDeserializer(StorageOptions::class.java, StorageOptionsDeserializer()) - - addDeserializer(GeneratorOptions::class.java, GeneratorOptionsDeserializer()) - } - - registerModule(kotlinModule) -} - -private class BlockDataSerializer : StdSerializer(BlockData::class.java) { - - override fun serialize(value: BlockData, gen: JsonGenerator, provider: SerializerProvider) { - gen.writeString(value.asString) - } - -} - -private class BlockDataDeserializer : StdDeserializer(BlockData::class.java) { - - override fun deserialize(p: JsonParser, ctxt: DeserializationContext): BlockData? { - try { - return Bukkit.createBlockData(p.valueAsString) - } catch (ex: Exception) { - throw RuntimeException("Exception occurred at ${p.currentLocation}", ex) - } - } - -} - -class StorageOptionsDeserializer : JsonDeserializer() { - - override fun deserialize(p: JsonParser?, ctxt: DeserializationContext?): StorageOptions { - val node = p!!.readValueAsTree() - val dialect = node.get("dialect").asText() - val optionsNode = node.get("options") - val factory = StorageFactory.getFactory(dialect) ?: throw IllegalStateException("Unknown storage dialect: $dialect") - val options = p.codec.treeToValue(optionsNode, factory.optionsClass.java) - return StorageOptions(dialect, options) - } - -} - -class StorageOptionsSerializer : StdSerializer(StorageOptions::class.java) { - - override fun serialize(value: StorageOptions?, gen: JsonGenerator?, serializers: SerializerProvider?) { - with(gen!!) { - writeStartObject() - writeStringField("dialect", value!!.dialect) - writeFieldName("options") - writeObject(value.options) - writeEndObject() - } - } - -} - -class GeneratorOptionsDeserializer : JsonDeserializer() { - - override fun deserialize(parser: JsonParser?, ctx: DeserializationContext?): GeneratorOptions? { - val node = parser!!.readValueAsTree() - val name = node.get("name").asText() - val optionsNode = node.get("options") - val factory = GeneratorFactories.getFactory(name) ?: throw IllegalStateException("Unknown generator: $name") - - return parser.codec.treeToValue(optionsNode, factory.optionsClass.java) - } - -} - -class GeneratorOptionsSerializer(private val defaultSerializer: JsonSerializer) : JsonSerializer() { - - override fun serialize(input: GeneratorOptions?, generator: JsonGenerator?, provider: SerializerProvider?) { - with(generator!!) { - writeStartObject() - writeStringField("name", input!!.generatorFactory().name) - writeFieldName("options") - defaultSerializer.serialize(input, generator, provider) - writeEndObject() - } - } - -} \ No newline at end of file diff --git a/src/main/kotlin/io/dico/parcels2/storage/StorageFactory.kt b/src/main/kotlin/io/dico/parcels2/storage/StorageFactory.kt deleted file mode 100644 index e798df9..0000000 --- a/src/main/kotlin/io/dico/parcels2/storage/StorageFactory.kt +++ /dev/null @@ -1,43 +0,0 @@ -package io.dico.parcels2.storage - -import com.zaxxer.hikari.HikariDataSource -import io.dico.parcels2.DataConnectionOptions -import io.dico.parcels2.storage.exposed.ExposedBacking -import kotlin.reflect.KClass - -interface StorageFactory { - companion object StorageFactories { - private val map: MutableMap = HashMap() - - fun registerFactory(dialect: String, generator: StorageFactory): Boolean = map.putIfAbsent(dialect.toLowerCase(), generator) == null - - fun getFactory(dialect: String): StorageFactory? = map[dialect.toLowerCase()] - - init { - // have to write the code like this in kotlin. - // This code is absolutely disgusting - ConnectionStorageFactory().register(this) - } - } - - val optionsClass: KClass - - fun newStorageInstance(dialect: String, options: Any): Storage - -} - -class ConnectionStorageFactory : StorageFactory { - override val optionsClass = DataConnectionOptions::class - private val types: List = listOf("postgresql", "mariadb") - - fun register(companion: StorageFactory.StorageFactories) { - types.forEach { companion.registerFactory(it, this) } - } - - override fun newStorageInstance(dialect: String, options: Any): Storage { - val hikariConfig = getHikariConfig(dialect, options as DataConnectionOptions) - val dataSourceFactory = suspend { HikariDataSource(hikariConfig) } - return StorageWithCoroutineBacking(ExposedBacking(dataSourceFactory)) - } - -} diff --git a/src/main/kotlin/io/dico/parcels2/storage/migration/MigrationFactory.kt b/src/main/kotlin/io/dico/parcels2/storage/migration/MigrationFactory.kt deleted file mode 100644 index 4fb3088..0000000 --- a/src/main/kotlin/io/dico/parcels2/storage/migration/MigrationFactory.kt +++ /dev/null @@ -1,5 +0,0 @@ -package io.dico.parcels2.storage.migration - -interface MigrationFactory { - fun getMigration() -} \ No newline at end of file diff --git a/src/main/resources/logback.xml b/src/main/resources/logback.xml index 58426c6..65186e5 100644 --- a/src/main/resources/logback.xml +++ b/src/main/resources/logback.xml @@ -1,11 +1,12 @@ - + - %d{HH:mm:ss.SSS} %magenta(%-8.-8(%thread)) %highlight(%-5level) %boldCyan(%32.-32logger{32}) - %msg + + %magenta(%-8.-8(%thread)) %highlight(%-5level) %boldCyan(%6.-32logger{32}) - %msg - + \ No newline at end of file -- cgit v1.2.3