From be87ca7cc1bb4d3ff791a93ccc0a20e5f68877b8 Mon Sep 17 00:00:00 2001 From: DBotThePony Date: Sun, 22 Jan 2023 21:52:48 +0700 Subject: [PATCH] =?UTF-8?q?=D0=9D=D0=B5=D0=BC=D0=BD=D0=BE=D0=B3=D0=BE=20?= =?UTF-8?q?=D0=BD=D0=B5=20=D1=82=D1=83=D0=B4=D0=B0=20=D0=BF=D0=BE=D1=88?= =?UTF-8?q?=D1=91=D0=BB,=20=D0=BD=D0=BE=20=D1=81=D0=BE=D1=85=D1=80=D0=B0?= =?UTF-8?q?=D0=BD=D0=B8=D1=82=D1=81=D1=8F=20=D0=BD=D0=B0=D0=B4=D0=BE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ru/dbotthepony/kstarbound/Starbound.kt | 13 +- .../defs/item/ArmorItemPrototype.kt | 5 +- .../defs/item/CurrencyItemPrototype.kt | 7 +- .../defs/item/ILeveledStatusEffect.kt | 3 +- .../kstarbound/defs/item/ItemPrototype.kt | 24 +- .../defs/item/LiquidItemPrototype.kt | 1 - .../defs/item/MaterialItemPrototype.kt | 1 - .../defs/liquid/LiquidDefinition.kt | 15 +- .../defs/projectile/Configurable.kt | 10 +- .../kstarbound/defs/tile/MaterialModifier.kt | 10 +- .../kstarbound/defs/tile/RenderParameters.kt | 17 +- .../kstarbound/defs/tile/RenderTemplate.kt | 79 +++--- .../kstarbound/defs/tile/TileDefinition.kt | 33 +-- .../kstarbound/io/json/MappedTypeFactories.kt | 26 ++ .../io/json/builder/BuilderAdapter.kt | 242 ++++++------------ .../io/json/builder/FactoryAdapter.kt | 227 ++++------------ .../kstarbound/io/json/builder/Properties.kt | 117 +++++++++ .../ImmutableCollectionAdapterFactory.kt | 8 +- 18 files changed, 374 insertions(+), 464 deletions(-) create mode 100644 src/main/kotlin/ru/dbotthepony/kstarbound/io/json/MappedTypeFactories.kt create mode 100644 src/main/kotlin/ru/dbotthepony/kstarbound/io/json/builder/Properties.kt diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/Starbound.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/Starbound.kt index b4ad84b1..1ec99501 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/Starbound.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/Starbound.kt @@ -41,6 +41,7 @@ import ru.dbotthepony.kstarbound.defs.world.dungeon.DungeonWorldDef import ru.dbotthepony.kstarbound.io.* import ru.dbotthepony.kstarbound.io.json.AABBTypeAdapter import ru.dbotthepony.kstarbound.io.json.AABBiTypeAdapter +import ru.dbotthepony.kstarbound.io.json.MappedTypeFactories import ru.dbotthepony.kstarbound.io.json.builder.EnumAdapter import ru.dbotthepony.kstarbound.io.json.Vector2dTypeAdapter import ru.dbotthepony.kstarbound.io.json.Vector2fTypeAdapter @@ -175,15 +176,15 @@ object Starbound { .also(SpriteReference::registerGson) .also(AtlasConfiguration::registerGson) - .registerTypeAdapter(LeveledStatusEffect.ADAPTER) + .registerTypeAdapterFactory(LeveledStatusEffect.ADAPTER) .registerTypeAdapter(MaterialReference.Companion) .registerTypeAdapter(ThingDescription.Companion) - .registerTypeAdapter(ItemPrototype.ADAPTER) - .registerTypeAdapter(CurrencyItemPrototype.ADAPTER) - .registerTypeAdapter(ArmorItemPrototype.ADAPTER) - .registerTypeAdapter(MaterialItemPrototype.ADAPTER) - .registerTypeAdapter(LiquidItemPrototype.ADAPTER) + .registerTypeAdapterFactory(ItemPrototype.ADAPTER) + .registerTypeAdapterFactory(CurrencyItemPrototype.ADAPTER) + .registerTypeAdapterFactory(ArmorItemPrototype.ADAPTER) + .registerTypeAdapterFactory(MaterialItemPrototype.ADAPTER) + .registerTypeAdapterFactory(LiquidItemPrototype.ADAPTER) .registerTypeAdapter(IItemDefinition.InventoryIcon.ADAPTER) .registerTypeAdapter(IFossilItemDefinition.FossilSetDescription.ADAPTER) diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/item/ArmorItemPrototype.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/item/ArmorItemPrototype.kt index 30e9407b..4141bdb4 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/item/ArmorItemPrototype.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/item/ArmorItemPrototype.kt @@ -64,9 +64,8 @@ class ArmorItemPrototype : ItemPrototype(), IArmorItemDefinition { .auto(ArmorItemPrototype::maleFrames) .auto(ArmorItemPrototype::femaleFrames) .auto(ArmorItemPrototype::level) - .autoList(ArmorItemPrototype::leveledStatusEffects) - .autoList(ArmorItemPrototype::scripts) + .auto(ArmorItemPrototype::leveledStatusEffects) + .auto(ArmorItemPrototype::scripts) .auto(ArmorItemPrototype::scriptDelta) - .build() } } diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/item/CurrencyItemPrototype.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/item/CurrencyItemPrototype.kt index f9cb391e..6bf524a3 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/item/CurrencyItemPrototype.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/item/CurrencyItemPrototype.kt @@ -51,13 +51,12 @@ class CurrencyItemPrototype : ItemPrototype(), ICurrencyItemDefinition { companion object { val ADAPTER = BuilderAdapter.Builder(::CurrencyItemPrototype) .also { addFields(it as BuilderAdapter.Builder) } // безопасность: свойства родительского класса объявлены как final - .autoList(CurrencyItemPrototype::pickupSoundsSmall) - .autoList(CurrencyItemPrototype::pickupSoundsMedium) - .autoList(CurrencyItemPrototype::pickupSoundsLarge) + .auto(CurrencyItemPrototype::pickupSoundsSmall) + .auto(CurrencyItemPrototype::pickupSoundsMedium) + .auto(CurrencyItemPrototype::pickupSoundsLarge) .auto(CurrencyItemPrototype::smallStackLimit) .auto(CurrencyItemPrototype::mediumStackLimit) .auto(CurrencyItemPrototype::currency) .auto(CurrencyItemPrototype::value) - .build() } } diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/item/ILeveledStatusEffect.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/item/ILeveledStatusEffect.kt index 98238bc1..8805bb71 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/item/ILeveledStatusEffect.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/item/ILeveledStatusEffect.kt @@ -20,7 +20,6 @@ data class LeveledStatusEffect( LeveledStatusEffect::levelFunction, LeveledStatusEffect::stat, LeveledStatusEffect::baseMultiplier, - LeveledStatusEffect::amount, - ).build() + LeveledStatusEffect::amount) } } diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/item/ItemPrototype.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/item/ItemPrototype.kt index ab724c88..014f8225 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/item/ItemPrototype.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/item/ItemPrototype.kt @@ -1,5 +1,6 @@ package ru.dbotthepony.kstarbound.defs.item +import com.google.common.collect.ImmutableList import ru.dbotthepony.kstarbound.defs.ThingDescription import ru.dbotthepony.kstarbound.defs.util.enrollMap import ru.dbotthepony.kstarbound.io.json.builder.BuilderAdapter @@ -13,16 +14,16 @@ open class ItemPrototype : IItemDefinition, INativeJsonHolder { final override var price: Long = 0L final override var rarity: ItemRarity = ItemRarity.COMMON final override var category: String? = null - final override var inventoryIcon: List? = null - final override var itemTags: List = listOf() - final override var learnBlueprintsOnPickup: List = listOf() + final override var inventoryIcon: ArrayList? = null + final override var itemTags: ArrayList = ArrayList() + final override var learnBlueprintsOnPickup: ArrayList = ArrayList() final override var maxStack: Long = 9999L final override var eventCategory: String? = null final override var consumeOnPickup: Boolean = false - final override var pickupQuestTemplates: List = listOf() + final override var pickupQuestTemplates: ArrayList = ArrayList() final override var tooltipKind: ItemTooltipKind = ItemTooltipKind.NORMAL final override var twoHanded: Boolean = false - final override var radioMessagesOnPickup: List = listOf() + final override var radioMessagesOnPickup: ArrayList = ArrayList() final override var fuelAmount: Long? = null var descriptionData: ThingDescription by NotNullVar() @@ -59,7 +60,6 @@ open class ItemPrototype : IItemDefinition, INativeJsonHolder { companion object { val ADAPTER = BuilderAdapter.Builder(::ItemPrototype) .also(::addFields) - .build() fun addFields(builder: BuilderAdapter.Builder) { with(builder) { @@ -69,18 +69,18 @@ open class ItemPrototype : IItemDefinition, INativeJsonHolder { auto(ItemPrototype::price) auto(ItemPrototype::rarity) auto(ItemPrototype::category) - autoNullableList(ItemPrototype::inventoryIcon) - autoList(ItemPrototype::itemTags) - autoList(ItemPrototype::learnBlueprintsOnPickup) + auto(ItemPrototype::inventoryIcon) + auto(ItemPrototype::itemTags) + auto(ItemPrototype::learnBlueprintsOnPickup) auto(ItemPrototype::maxStack) auto(ItemPrototype::eventCategory) auto(ItemPrototype::consumeOnPickup) - autoList(ItemPrototype::pickupQuestTemplates) + auto(ItemPrototype::pickupQuestTemplates) auto(ItemPrototype::tooltipKind) auto(ItemPrototype::twoHanded) - autoList(ItemPrototype::radioMessagesOnPickup) + auto(ItemPrototype::radioMessagesOnPickup) auto(ItemPrototype::fuelAmount) - flat(ItemPrototype::descriptionData) + auto(ItemPrototype::descriptionData, isFlat = true) } } } diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/item/LiquidItemPrototype.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/item/LiquidItemPrototype.kt index 4ba1020f..f7295d21 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/item/LiquidItemPrototype.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/item/LiquidItemPrototype.kt @@ -49,6 +49,5 @@ class LiquidItemPrototype : ItemPrototype() { .auto(LiquidItemPrototype::liquid) .auto(LiquidItemPrototype::liquidId) .auto(LiquidItemPrototype::liquidName) - .build() } } diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/item/MaterialItemPrototype.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/item/MaterialItemPrototype.kt index 4ba586cd..2a337350 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/item/MaterialItemPrototype.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/item/MaterialItemPrototype.kt @@ -49,6 +49,5 @@ class MaterialItemPrototype : ItemPrototype() { .auto(MaterialItemPrototype::material) .auto(MaterialItemPrototype::materialId) .auto(MaterialItemPrototype::materialName) - .build() } } diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/liquid/LiquidDefinition.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/liquid/LiquidDefinition.kt index 84e08d0a..a33178ed 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/liquid/LiquidDefinition.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/liquid/LiquidDefinition.kt @@ -1,5 +1,6 @@ package ru.dbotthepony.kstarbound.defs.liquid +import com.google.common.collect.ImmutableList import com.google.gson.GsonBuilder import ru.dbotthepony.kstarbound.io.json.builder.FactoryAdapter import ru.dbotthepony.kstarbound.registerTypeAdapter @@ -12,8 +13,8 @@ data class LiquidDefinition( val tickDelta: Int = 1, val color: Color, val itemDrop: String? = null, - val statusEffects: List = listOf(), - val interactions: List = listOf(), + val statusEffects: ImmutableList = ImmutableList.of(), + val interactions: ImmutableList = ImmutableList.of(), val texture: String, val bottomLightMix: Color, val textureMovementFactor: Double, @@ -32,22 +33,20 @@ data class LiquidDefinition( .auto(LiquidDefinition::tickDelta) .auto(LiquidDefinition::color) .auto(LiquidDefinition::itemDrop) - .autoList(LiquidDefinition::statusEffects) - .autoList(LiquidDefinition::interactions) + .auto(LiquidDefinition::statusEffects) + .auto(LiquidDefinition::interactions) .auto(LiquidDefinition::texture) .auto(LiquidDefinition::bottomLightMix) .auto(LiquidDefinition::textureMovementFactor) - .build() val INTERACTION_ADAPTER = FactoryAdapter.Builder(Interaction::class) .auto(Interaction::liquid) .auto(Interaction::liquidResult) .auto(Interaction::materialResult) - .build() fun registerGson(gsonBuilder: GsonBuilder) { - gsonBuilder.registerTypeAdapter(ADAPTER) - gsonBuilder.registerTypeAdapter(INTERACTION_ADAPTER) + gsonBuilder.registerTypeAdapterFactory(ADAPTER) + gsonBuilder.registerTypeAdapterFactory(INTERACTION_ADAPTER) } } } diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/projectile/Configurable.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/projectile/Configurable.kt index 8550eeb5..0b9919e6 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/projectile/Configurable.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/projectile/Configurable.kt @@ -37,12 +37,12 @@ class ConfigurableProjectile : RawPrototype = Array(0) { throw IllegalStateException() } + var scripts: ArrayList = ArrayList() var hydrophobic: Boolean = false // we can't have concrete type here, since final class is commanded by `action` property of each entry - var actionOnReap: Array? = null + var actionOnReap: ArrayList? = null var piercing = false @@ -85,7 +85,7 @@ class ConfigurableProjectile : RawPrototype = listOf(), + val miningSounds: ImmutableList = ImmutableList.of(), val miningParticle: String? = null, override val renderTemplate: RenderTemplate, override val renderParameters: RenderParameters @@ -31,14 +32,13 @@ data class MaterialModifier( .auto(MaterialModifier::harvestLevel) .auto(MaterialModifier::breaksWithTile) .auto(MaterialModifier::grass) - .autoList(MaterialModifier::miningSounds) + .auto(MaterialModifier::miningSounds) .auto(MaterialModifier::miningParticle) - .add(MaterialModifier::renderTemplate, RenderTemplate.CACHE) + .auto(MaterialModifier::renderTemplate) .auto(MaterialModifier::renderParameters) - .build() fun registerGson(gsonBuilder: GsonBuilder) { - gsonBuilder.registerTypeAdapter(MaterialModifier::class.java, ADAPTER) + gsonBuilder.registerTypeAdapterFactory(ADAPTER) } } } diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/tile/RenderParameters.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/tile/RenderParameters.kt index 6d1142ed..04ba8d05 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/tile/RenderParameters.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/tile/RenderParameters.kt @@ -28,18 +28,15 @@ data class RenderParameters( companion object { val ADAPTER = FactoryAdapter.Builder(RenderParameters::class) - .auto( - RenderParameters::texture, - RenderParameters::variants, - RenderParameters::multiColored, - RenderParameters::occludesBelow, - RenderParameters::lightTransparent, - RenderParameters::zLevel, - ) - .build() + .auto(RenderParameters::texture) + .auto(RenderParameters::variants) + .auto(RenderParameters::multiColored) + .auto(RenderParameters::occludesBelow) + .auto(RenderParameters::lightTransparent) + .auto(RenderParameters::zLevel) fun registerGson(gsonBuilder: GsonBuilder) { - gsonBuilder.registerTypeAdapter(RenderParameters::class.java, ADAPTER) + gsonBuilder.registerTypeAdapterFactory(ADAPTER) } } } diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/tile/RenderTemplate.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/tile/RenderTemplate.kt index caf83387..203e8ad6 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/tile/RenderTemplate.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/tile/RenderTemplate.kt @@ -1,5 +1,7 @@ package ru.dbotthepony.kstarbound.defs.tile +import com.google.common.collect.ImmutableList +import com.google.common.collect.ImmutableMap import com.google.gson.GsonBuilder import com.google.gson.JsonSyntaxException import com.google.gson.TypeAdapter @@ -28,14 +30,11 @@ data class RenderPiece( ) { companion object { val ADAPTER = FactoryAdapter.Builder(RenderPiece::class) - .auto( - RenderPiece::texture, - RenderPiece::textureSize, - RenderPiece::texturePosition, - RenderPiece::colorStride, - RenderPiece::variantStride, - ) - .build() + .auto(RenderPiece::texture) + .auto(RenderPiece::textureSize) + .auto(RenderPiece::texturePosition) + .auto(RenderPiece::colorStride) + .auto(RenderPiece::variantStride) } } @@ -44,7 +43,7 @@ fun interface EqualityRuleTester { } data class RenderRuleList( - val entries: List, + val entries: ImmutableList, val join: Combination = Combination.ALL ) { enum class Combination { @@ -84,11 +83,9 @@ data class RenderRuleList( private val LOGGED = ObjectArraySet() val ADAPTER = FactoryAdapter.Builder(Entry::class) - .auto( - Entry::type, - Entry::matchHue, - Entry::inverse, - ) + .auto(Entry::type) + .auto(Entry::matchHue) + .auto(Entry::inverse) .build() } } @@ -119,17 +116,17 @@ data class RenderRuleList( companion object { val ADAPTER = FactoryAdapter.Builder(RenderRuleList::class) - .autoList(RenderRuleList::entries) + .auto(RenderRuleList::entries) .auto(RenderRuleList::join) .build() } } data class RenderMatch( - val pieces: List = listOf(), - val matchAllPoints: List = listOf(), - val matchAnyPoints: List = listOf(), - val subMatches: List = listOf(), + val pieces: ImmutableList = ImmutableList.of(), + val matchAllPoints: ImmutableList = ImmutableList.of(), + val matchAnyPoints: ImmutableList = ImmutableList.of(), + val subMatches: ImmutableList = ImmutableList.of(), val haltOnMatch: Boolean = false, val haltOnSubMatch: Boolean = false, ) { @@ -208,31 +205,28 @@ data class RenderMatch( companion object { val ADAPTER = FactoryAdapter.Builder(RenderMatch::class) - .autoList(RenderMatch::pieces) - .autoList(RenderMatch::matchAllPoints) - .autoList(RenderMatch::matchAnyPoints) - .autoList(RenderMatch::subMatches) + .auto(RenderMatch::pieces) + .auto(RenderMatch::matchAllPoints) + .auto(RenderMatch::matchAnyPoints) + .auto(RenderMatch::subMatches) .auto(RenderMatch::haltOnMatch) .auto(RenderMatch::haltOnSubMatch) - .build() val PIECE_ADAPTER = FactoryAdapter.Builder(Piece::class) .auto(Piece::name) .auto(Piece::offset) .inputAsList() - .build() val MATCHER_ADAPTER = FactoryAdapter.Builder(Matcher::class) .auto(Matcher::offset) .auto(Matcher::ruleName) .inputAsList() - .build() } } data class RenderMatchList( val name: String, - val list: List + val list: ImmutableList ) { fun resolve(template: RenderTemplate) { for (value in list) { @@ -243,17 +237,16 @@ data class RenderMatchList( companion object { val ADAPTER = FactoryAdapter.Builder(RenderMatchList::class) .auto(RenderMatchList::name) - .autoList(RenderMatchList::list) + .auto(RenderMatchList::list) .inputAsList() - .build() } } data class RenderTemplate( - val pieces: Map, + val pieces: ImmutableMap, val representativePiece: String, - val matches: List, - val rules: Map, + val matches: ImmutableList, + val rules: ImmutableMap, ) { init { for (value in matches) { @@ -263,25 +256,23 @@ data class RenderTemplate( companion object { val ADAPTER = FactoryAdapter.Builder(RenderTemplate::class) - .mapAsObject(RenderTemplate::pieces) + .auto(RenderTemplate::pieces) .auto(RenderTemplate::representativePiece) - .autoList(RenderTemplate::matches) - .mapAsObject(RenderTemplate::rules) - .build() - - val CACHE = ADAPTER.asReference() + .auto(RenderTemplate::matches) + .auto(RenderTemplate::rules) + .asReference() fun registerGson(gsonBuilder: GsonBuilder) { - gsonBuilder.registerTypeAdapter(RenderPiece.ADAPTER) + gsonBuilder.registerTypeAdapterFactory(RenderPiece.ADAPTER) gsonBuilder.registerTypeAdapter(RenderRuleList.ADAPTER) - gsonBuilder.registerTypeAdapter(RenderMatch.ADAPTER) - gsonBuilder.registerTypeAdapter(RenderMatch.PIECE_ADAPTER) - gsonBuilder.registerTypeAdapter(RenderMatch.MATCHER_ADAPTER) - gsonBuilder.registerTypeAdapter(RenderMatchList.ADAPTER) + gsonBuilder.registerTypeAdapterFactory(RenderMatch.ADAPTER) + gsonBuilder.registerTypeAdapterFactory(RenderMatch.PIECE_ADAPTER) + gsonBuilder.registerTypeAdapterFactory(RenderMatch.MATCHER_ADAPTER) + gsonBuilder.registerTypeAdapterFactory(RenderMatchList.ADAPTER) gsonBuilder.registerTypeAdapter(RenderRuleList.Entry.ADAPTER) - gsonBuilder.registerTypeAdapter(ADAPTER) + gsonBuilder.registerTypeAdapterFactory(ADAPTER) gsonBuilder.registerTypeAdapter(EnumAdapter(RenderRuleList.Combination::class.java)) } diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/tile/TileDefinition.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/tile/TileDefinition.kt index bc1858cd..a3228a53 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/tile/TileDefinition.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/tile/TileDefinition.kt @@ -25,27 +25,22 @@ data class TileDefinition( ) : IRenderableTile { companion object { val ADAPTER = FactoryAdapter.Builder(TileDefinition::class) - .auto( - TileDefinition::materialId, - TileDefinition::materialName, - TileDefinition::particleColor, - TileDefinition::itemDrop, - TileDefinition::description, - TileDefinition::shortdescription, - TileDefinition::footstepSound, - TileDefinition::blocksLiquidFlow, - TileDefinition::soil, - TileDefinition::health, - TileDefinition::category - ) - .add(TileDefinition::renderTemplate, RenderTemplate.CACHE) - .auto( - TileDefinition::renderParameters, - ) - .build() + .auto(TileDefinition::materialId) + .auto(TileDefinition::materialName) + .auto(TileDefinition::particleColor) + .auto(TileDefinition::itemDrop) + .auto(TileDefinition::description) + .auto(TileDefinition::shortdescription) + .auto(TileDefinition::footstepSound) + .auto(TileDefinition::blocksLiquidFlow) + .auto(TileDefinition::soil) + .auto(TileDefinition::health) + .auto(TileDefinition::category) + .auto(TileDefinition::renderTemplate) + .auto(TileDefinition::renderParameters) fun registerGson(gsonBuilder: GsonBuilder) { - gsonBuilder.registerTypeAdapter(ADAPTER) + gsonBuilder.registerTypeAdapterFactory(ADAPTER) } } } diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/io/json/MappedTypeFactories.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/io/json/MappedTypeFactories.kt new file mode 100644 index 00000000..ff94789f --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/io/json/MappedTypeFactories.kt @@ -0,0 +1,26 @@ +package ru.dbotthepony.kstarbound.io.json + +import com.google.gson.Gson +import com.google.gson.TypeAdapter +import com.google.gson.TypeAdapterFactory +import com.google.gson.reflect.TypeToken +import it.unimi.dsi.fastutil.objects.Reference2ObjectOpenHashMap + +class MappedTypeFactories : TypeAdapterFactory { + private val adapters = Reference2ObjectOpenHashMap, TypeAdapterFactory>() + + fun put(clazz: Class, adapter: TypeAdapterFactory): MappedTypeFactories { + check(adapters.put(clazz, adapter) == null) { "Already had type adapter for $clazz" } + return this + } + + override fun create(gson: Gson, type: TypeToken): TypeAdapter? { + val factory = adapters[type.rawType] + + if (factory != null) { + return checkNotNull(factory.create(gson, type)) { "${type.rawType} should have type adapter (factory: $factory)" } + } + + return null + } +} diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/io/json/builder/BuilderAdapter.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/io/json/builder/BuilderAdapter.kt index b099da1e..c2bddd5d 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/io/json/builder/BuilderAdapter.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/io/json/builder/BuilderAdapter.kt @@ -25,6 +25,7 @@ import kotlin.properties.Delegates import kotlin.reflect.KClass import kotlin.reflect.KMutableProperty1 import kotlin.reflect.full.isSubclassOf +import kotlin.reflect.javaType /** * Данный интерфейс имеет один единственный метод: [acceptJson] @@ -72,12 +73,7 @@ class BuilderAdapter private constructor( /** * Свойства объекта [T], которые можно выставлять */ - val properties: ImmutableMap>, - - /** - * Свойства объекта [T], которые можно выставлять - */ - val flatProperties: ImmutableMap>, + val properties: ImmutableMap>, /** * Ключи, которые необходимо игнорировать при чтении JSON @@ -104,13 +100,13 @@ class BuilderAdapter private constructor( @Suppress("name_shadowing") var reader = reader - val missing = ObjectOpenHashSet>() + val missing = ObjectOpenHashSet>() missing.addAll(properties.values) val instance = factory.invoke() var json: JsonObject by Delegates.notNull() - if (instance is IJsonHolder || flatProperties.isNotEmpty()) { + if (instance is IJsonHolder || properties.values.any { it.isFlat }) { json = TypeAdapters.JSON_ELEMENT.read(reader) as JsonObject reader = JsonTreeReader(json) @@ -137,7 +133,7 @@ class BuilderAdapter private constructor( try { val peek = reader.peek() - if (!property.returnType.isMarkedNullable && peek == JsonToken.NULL) { + if (!property.type.isMarkedNullable && peek == JsonToken.NULL) { throw NullPointerException("Property ${property.name} of ${instance::class.qualifiedName} does not accept nulls (JSON contains null)") } else if (peek == JsonToken.NULL) { property.set(instance, null) @@ -145,7 +141,7 @@ class BuilderAdapter private constructor( } else { val readValue = property.adapter.read(reader) - if (!property.returnType.isMarkedNullable && readValue == null) + if (!property.type.isMarkedNullable && readValue == null) throw JsonSyntaxException("Property ${property.name} of ${instance::class.qualifiedName} does not accept nulls (Type provider returned null)") property.set(instance, readValue) @@ -169,11 +165,14 @@ class BuilderAdapter private constructor( reader.endObject() - for (property in flatProperties.values) { + for (property in properties.values) { + if (!property.isFlat || !missing.remove(property)) + continue + try { val read = property.adapter.read(JsonTreeReader(json)) - if (!property.returnType.isMarkedNullable && read == null) { + if (!property.type.isMarkedNullable && read == null) { throw NullPointerException("Property ${property.name} of ${instance::class.qualifiedName} does not accept nulls (flat property adapter returned NULL)") } else if (read == null) { property.set(instance, null) @@ -189,7 +188,7 @@ class BuilderAdapter private constructor( if (property.mustBePresent == true) { throw JsonSyntaxException("${instance::class.qualifiedName} demands for ${property.name} to be present, however, it is missing from JSON structure") } else if (property.mustBePresent == null) { - if (property.returnType.isMarkedNullable) { + if (property.type.isMarkedNullable) { continue } @@ -210,51 +209,53 @@ class BuilderAdapter private constructor( return instance } - data class WrappedProperty( - val property: KMutableProperty1, - val adapter: TypeAdapter, - /** - * @see PropertyConfigurator.mustBePresent - */ - val mustBePresent: Boolean?, - ) { - inline val name get() = property.name - // кеш - val returnType = property.returnType - - // так как дженерики тут немного слабенькие - // Так что вот так... - @Suppress("unchecked_cast") - fun set(receiver: T, value: Any?) { - property.set(receiver, value as V) - } - } - - class PropertyConfigurator( - val property: KMutableProperty1, - val adapter: TypeAdapter, - ) { - /** - * Обязана ли присутствовать эта переменная внутри JSON структуры. - * - * * `true` - всегда кидать исключения - * * `false` - никогда не кидать исключения - * * `null` - кидать исключения на усмотрение реализации (по умолчанию) - */ - var mustBePresent: Boolean? = null - } - class Builder(val factory: () -> T, vararg fields: KMutableProperty1) : TypeAdapterFactory { - private val properties = ArrayList>() - private val flatProperties = ArrayList>() + private val properties = ArrayList>() private val ignoreKeys = ObjectArraySet() var extraPropertiesAreFatal = false var logMisses: Boolean? = null + private val factoryReturnType by lazy { factory::invoke.returnType } + @OptIn(ExperimentalStdlibApi::class) override fun create(gson: Gson, type: TypeToken): TypeAdapter? { + if (type.isAssignableFrom(factoryReturnType.javaType)) { + return build(gson) as TypeAdapter + } + return null } + fun build(gson: Gson): BuilderAdapter { + val map = ImmutableMap.Builder>() + + for (property in properties) + map.put(property.property.name, property.resolve(gson)) + + return BuilderAdapter( + factory = factory, + properties = map.build(), + ignoreKeys = ImmutableSet.copyOf(ignoreKeys), + extraPropertiesAreFatal = extraPropertiesAreFatal, + logMisses = logMisses ?: properties.none { it.isFlat }, + ) + } + + @Deprecated("Используйте как TypeAdapterFactory") + fun build(): BuilderAdapter { + val map = ImmutableMap.Builder>() + + for (property in properties) + map.put(property.property.name, property.resolve(null)) + + return BuilderAdapter( + factory = factory, + properties = map.build(), + ignoreKeys = ImmutableSet.copyOf(ignoreKeys), + extraPropertiesAreFatal = extraPropertiesAreFatal, + logMisses = logMisses ?: properties.none { it.isFlat }, + ) + } + /** * Являются ли "лишние" ключи в JSON структуре ошибкой. * @@ -262,7 +263,7 @@ class BuilderAdapter private constructor( * то [extraPropertiesAreFatal] можно скомбинировать с [ignoreKey]. */ fun extraPropertiesAreFatal(flag: Boolean = true): Builder { - check(flatProperties.isEmpty() || !flag) { "Can't have both flattened properties and extraPropertiesAreFatal" } + check(properties.none { it.isFlat } || !flag) { "Can't have both flattened properties and extraPropertiesAreFatal" } extraPropertiesAreFatal = flag return this } @@ -280,42 +281,10 @@ class BuilderAdapter private constructor( auto(field) } - private fun _add(property: KMutableProperty1, adapter: TypeAdapter, configurator: PropertyConfigurator.() -> Unit): PropertyConfigurator { - if (properties.any { it.property == property }) { - throw IllegalArgumentException("Property $property is defined twice") - } - - if (flatProperties.any { it.property == property }) { - throw IllegalArgumentException("Property $property is defined twice") - } - - ignoreKeys.remove(property.name) - - val config = PropertyConfigurator(property, adapter) - configurator.invoke(config) - return config - } - /** * Добавляет указанное свойство в будущий адаптер с указанным [adapter] - */ - fun add(property: KMutableProperty1, adapter: TypeAdapter, configurator: PropertyConfigurator.() -> Unit = {}): Builder { - val config = _add(property, adapter, configurator) - - properties.add( - WrappedProperty( - property, - adapter, - mustBePresent = config.mustBePresent - ) - ) - - return this - } - - /** - * Добавляет указанное свойство в будущий адаптер, как плоский объект внутри данного на том же уровне, что и сам объект, используя адаптер [adapter] * + * Если указан [isFlat] как true, то данное свойство будет обработано как на одном уровне с данным объектом. * Пример: * ```json * { @@ -328,77 +297,45 @@ class BuilderAdapter private constructor( * В данном случае, можно указать `b` как плоский класс внутри `a`. * * Данный подход позволяет избавиться от постоянного наследования и реализации одного и того же интерфейса во множестве других классов. - * - * Флаг [extraPropertiesAreFatal] не поддерживается с данными свойствами - * - * Если [logMisses] не указан явно, то он будет выставлен на false + * Флаг [extraPropertiesAreFatal] не поддерживается с данными свойствами. + * Если [logMisses] не указан явно, то он будет выставлен на false. */ - fun flat(property: KMutableProperty1, adapter: TypeAdapter, configurator: PropertyConfigurator.() -> Unit = {}): Builder { - val config = _add(property, adapter, configurator) + fun add(property: KMutableProperty1, adapter: TypeAdapter, isFlat: Boolean = false, mustBePresent: Boolean? = null): Builder { + if (properties.any { it.property == property }) { + throw IllegalArgumentException("Property $property is defined twice") + } - check(!extraPropertiesAreFatal) { "Can't have both flattened properties and extraPropertiesAreFatal" } - - flatProperties.add( - WrappedProperty( - property, - adapter, - mustBePresent = config.mustBePresent - ) - ) + ignoreKeys.remove(property.name) + properties.add(ResolvedMutableProperty( + property = property, + adapter = adapter, + mustBePresent = mustBePresent, + isFlat = isFlat, + )) return this } /** * Автоматически определяет тип свойства и необходимый [TypeAdapter] + * + * Для флагов смотрите [add] + * + * @see add */ - fun auto(property: KMutableProperty1, configurator: PropertyConfigurator.() -> Unit = {}): Builder { - val returnType = property.returnType - val classifier = returnType.classifier as? KClass<*> ?: throw ClassCastException("Unable to cast ${returnType.classifier} to KClass of property ${property.name}!") - - if (classifier.isSubclassOf(List::class)) { - throw IllegalArgumentException("${property.name} is a List, please use autoList() or directly specify type adapter method instead") + fun auto(property: KMutableProperty1, isFlat: Boolean = false, mustBePresent: Boolean? = null): Builder { + if (properties.any { it.property == property }) { + throw IllegalArgumentException("Property $property is defined twice") } - if (classifier.isSubclassOf(Map::class)) { - throw IllegalArgumentException("${property.name} is a Map, please use autoMap() or directly specify type adapter method instead") - } + ignoreKeys.remove(property.name) + properties.add(ResolvableMutableProperty( + property = property, + mustBePresent = mustBePresent, + isFlat = isFlat, + )) - @Suppress("unchecked_cast") // classifier.java не имеет обозначенного типа - return add(property, LazyTypeProvider(classifier.java) as TypeAdapter, configurator) - } - - /** - * Автоматически определяет тип свойства и необходимый [TypeAdapter], инкапсулированный в данный типе на одном уровне - */ - fun flat(property: KMutableProperty1, configurator: PropertyConfigurator.() -> Unit = {}): Builder { - val returnType = property.returnType - val classifier = returnType.classifier as? KClass<*> ?: throw ClassCastException("Unable to cast ${returnType.classifier} to KClass of property ${property.name}!") - - if (classifier.isSubclassOf(List::class)) { - throw IllegalArgumentException("${property.name} is a List, please use autoList() or directly specify type adapter method instead") - } - - if (classifier.isSubclassOf(Map::class)) { - throw IllegalArgumentException("${property.name} is a Map, please use autoMap() or directly specify type adapter method instead") - } - - @Suppress("unchecked_cast") // classifier.java не имеет обозначенного типа - return flat(property, LazyTypeProvider(classifier.java) as TypeAdapter, configurator) - } - - /** - * Автоматически создаёт [ListAdapter] для заданного свойства - */ - inline fun autoList(property: KMutableProperty1>, noinline configurator: PropertyConfigurator>.() -> Unit = {}): Builder { - return add(property, ListAdapter(V::class.java), configurator) - } - - /** - * Автоматически создаёт [ListAdapter] для заданного свойства, но в данном случае само свойство может принимать значение null - */ - inline fun autoNullableList(property: KMutableProperty1?>, noinline configurator: PropertyConfigurator?>.() -> Unit = {}): Builder { - return add(property, ListAdapter(V::class.java).nullSafe(), configurator) + return this } fun ignoreKey(name: String): Builder { @@ -409,27 +346,6 @@ class BuilderAdapter private constructor( ignoreKeys.add(name) return this } - - fun build(): BuilderAdapter { - val map = ImmutableMap.Builder>() - - for (property in properties) - map.put(property.property.name, property) - - val map2 = ImmutableMap.Builder>() - - for (property in flatProperties) - map2.put(property.property.name, property) - - return BuilderAdapter( - factory = factory, - properties = map.build(), - flatProperties = map2.build(), - ignoreKeys = ImmutableSet.copyOf(ignoreKeys), - extraPropertiesAreFatal = extraPropertiesAreFatal, - logMisses = logMisses ?: flatProperties.isEmpty(), - ) - } } companion object { diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/io/json/builder/FactoryAdapter.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/io/json/builder/FactoryAdapter.kt index 19a19fe4..b113e4e3 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/io/json/builder/FactoryAdapter.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/io/json/builder/FactoryAdapter.kt @@ -40,20 +40,11 @@ import kotlin.reflect.full.isSupertypeOf */ class FactoryAdapter private constructor( val bound: KClass, - private val types: ImmutableList>, + val types: ImmutableList>, val asJsonArray: Boolean, val storesJson: Boolean, val logMisses: Boolean, ) : TypeAdapter() { - private data class PackedProperty( - val property: KProperty1, - val adapter: TypeAdapter, - val transformer: (T) -> T = { it }, - val isFlat: Boolean - ) { - val returnType = property.returnType - } - private val name2index = Object2IntArrayMap() private val loggedMisses = ObjectArraySet() @@ -83,7 +74,7 @@ class FactoryAdapter private constructor( val nextParam = iterator.next() val a = param.type - val b = nextParam.returnType + val b = nextParam.type if (!a.isSupertypeOf(b) || a.isMarkedNullable != b.isMarkedNullable) { return@first false @@ -114,7 +105,7 @@ class FactoryAdapter private constructor( * Синтетический конструктор класса, который создаётся Kotlin'ном, для создания классов со значениями по умолчанию */ private val syntheticFactory: Constructor? = try { - val typelist = types.map { (it.returnType.classifier as KClass<*>).java }.toMutableList() + val typelist = types.map { (it.type.classifier as KClass<*>).java }.toMutableList() if (storesJson) if (asJsonArray) @@ -223,7 +214,7 @@ class FactoryAdapter private constructor( val (field, adapter) = tuple try { - readValues[fieldId] = (tuple.transformer as (Any?) -> Any?)(adapter.read(reader)) + readValues[fieldId] = adapter.read(reader) presentValues[fieldId] = true } catch(err: Throwable) { throw JsonSyntaxException("Reading field ${field.name} for ${bound.qualifiedName}", err) @@ -275,7 +266,7 @@ class FactoryAdapter private constructor( val (field, adapter) = tuple try { - readValues[fieldId] = (tuple.transformer as (Any?) -> Any?)(adapter.read(reader)) + readValues[fieldId] = adapter.read(reader) presentValues[fieldId] = true } catch(err: Throwable) { throw JsonSyntaxException("Reading field ${field.name} for ${bound.qualifiedName}", err) @@ -312,7 +303,7 @@ class FactoryAdapter private constructor( val (field) = tuple if (readValues[i] == null) { - if (!tuple.returnType.isMarkedNullable) { + if (!tuple.type.isMarkedNullable) { throw JsonSyntaxException("Field ${field.name} of ${bound.qualifiedName} does not accept nulls") } @@ -366,7 +357,7 @@ class FactoryAdapter private constructor( throw JsonSyntaxException("Field ${field.name} of ${bound.qualifiedName} is missing") } - if (tuple.returnType.isMarkedNullable) { + if (tuple.type.isMarkedNullable) { continue } @@ -392,16 +383,51 @@ class FactoryAdapter private constructor( } } - private val types = ArrayList>() + private val types = ArrayList>() override fun create(gson: Gson, type: TypeToken): TypeAdapter? { if (type.rawType == clazz.java) { - return build() as TypeAdapter + return build(gson) as TypeAdapter } return null } + /** + * Собирает этот [FactoryAdapter] с указанным GSON объектом + */ + fun build(gson: Gson): FactoryAdapter { + check(!asList || types.none { it.isFlat }) { "Can't have both flat properties and json data array layout" } + + return FactoryAdapter( + bound = clazz, + types = types.stream().map { it.resolve(gson) }.collect(ImmutableList.toImmutableList()), + asJsonArray = asList, + storesJson = storesJson, + logMisses = logMisses, + ) + } + + /** + * Собирает этот [FactoryAdapter] без GSON объекта + * + * Не рекомендуется использовать, лучше всего использовать как [TypeAdapterFactory] + * + * Несмотря на @Deprecated, данный вариант метода удалён не будет + */ + @Deprecated("Используйте как TypeAdapterFactory") + fun build(): FactoryAdapter { + check(!asList || types.none { it.isFlat }) { "Can't have both flat properties and json data array layout" } + + return FactoryAdapter( + bound = clazz, + types = types.stream().map { it.resolve(null) }.collect(ImmutableList.toImmutableList()), + asJsonArray = asList, + storesJson = storesJson, + logMisses = logMisses, + ) + } + /** * Принимает ли класс *последним* аргументом JSON структуру * @@ -433,160 +459,19 @@ class FactoryAdapter private constructor( /** * Добавляет свойство с определённым [adapter] */ - fun add(field: KProperty1, adapter: TypeAdapter): Builder { - types.add(PackedProperty(field, adapter, isFlat = false)) - return this - } - - /** - * Добавляет свойство с определённым [adapter], которое находится на том же уровне что и данный объект внутри JSON структуры - */ - fun flat(field: KProperty1, adapter: TypeAdapter): Builder { - types.add(PackedProperty(field, adapter, isFlat = true)) - return this - } - - /** - * Добавляет поля без generic типов и без преобразователей - */ - @Suppress("unchecked_cast") - fun auto(vararg fields: KProperty1): Builder { - for (field in fields) - auto(field) - - return this - } - - /** - * Добавляет поля без generic типов и без преобразователей - */ - @Suppress("unchecked_cast") - fun autoFlat(vararg fields: KProperty1): Builder { - for (field in fields) - autoFlat(field) - + fun add(field: KProperty1, adapter: TypeAdapter, isFlat: Boolean = false): Builder { + types.add(ResolvedProperty(field, adapter, isFlat = isFlat)) return this } /** * Автоматически определяет тип поля и необходимый адаптор типа к нему + * + * Можно указать [transform] для изменения определённого адаптера */ @Suppress("unchecked_cast") - fun auto(field: KProperty1, transformer: (In) -> In = { it }): Builder { - val returnType = field.returnType - val classifier = returnType.classifier as? KClass<*> ?: throw ClassCastException("Unable to cast ${returnType.classifier} to KClass of property ${field.name}!") - - if (classifier.isSubclassOf(List::class)) { - throw IllegalArgumentException("${field.name} is a List, please use autoList() method instead") - } - - if (classifier.isSubclassOf(Map::class)) { - throw IllegalArgumentException("${field.name} is a Map, please use autoMap() method instead") - } - - types.add(PackedProperty(field, LazyTypeProvider(classifier.java) as TypeAdapter, transformer = transformer as (Any?) -> Any?, isFlat = false)) - return this - } - - /** - * Автоматически определяет тип поля и необходимый адаптор типа к нему - */ - @Suppress("unchecked_cast") - fun autoFlat(field: KProperty1, transformer: (In) -> In = { it }): Builder { - val returnType = field.returnType - val classifier = returnType.classifier as? KClass<*> ?: throw ClassCastException("Unable to cast ${returnType.classifier} to KClass of property ${field.name}!") - - if (classifier.isSubclassOf(List::class)) { - throw IllegalArgumentException("${field.name} is a List") - } - - if (classifier.isSubclassOf(Map::class)) { - throw IllegalArgumentException("${field.name} is a Map") - } - - types.add(PackedProperty(field, LazyTypeProvider(classifier.java) as TypeAdapter, transformer = transformer as (Any?) -> Any?, isFlat = true)) - return this - } - - /** - * Добавляет поле, которое содержит список значений V (без null). - * - * Список неизменяем (создаётся объект [ImmutableList]) - */ - fun list(field: KProperty1?>, type: Class, transformer: (V) -> V = { it }): Builder { - types.add(PackedProperty(field, ListAdapter(type, transformer).nullSafe(), isFlat = false)) - return this - } - - /** - * Добавляет поле, которое содержит список значений V (без null). - * - * Список неизменяем (создаётся объект [ImmutableList]) - */ - inline fun autoList(field: KProperty1?>, noinline transformer: (V) -> V = { it }): Builder { - return add(field, ListAdapter(V::class.java, transformer).nullSafe()) - } - - /** - * Добавляет поле-таблицу, которое кодируется как [[key, value], [key, value], ...] - * - * Таблица неизменяема (создаётся объект [ImmutableMap]) - */ - fun mapAsArray(field: KProperty1>, keyType: Class, valueType: Class): Builder { - types.add(PackedProperty(field, MapAdapter(keyType, valueType), isFlat = false)) - return this - } - - /** - * Добавляет поле-таблицу, кодирование зависит от контекста: - * * Если [K] является [String], кодирование происходит как Json Object - * * Иначе кодирование происходит как Json Array - * - * Таблица неизменяема (создаётся объект [ImmutableMap]) - */ - inline fun autoMap(field: KProperty1>): Builder { - if (K::class == String::class) - return this.mapAsObject(field as KProperty1?>, V::class.java) - - return this.mapAsArray(field, K::class.java, V::class.java) - } - - /** - * Добавляет поле-таблицу, которое кодируется как [[key, value], [key, value], ...] - * - * Таблица неизменяема (создаётся объект [ImmutableMap]) - */ - fun mapAsArray(field: KProperty1?>, keyType: KClass, valueType: KClass): Builder { - types.add(PackedProperty(field, MapAdapter(keyType.java, valueType.java).nullSafe(), isFlat = false)) - return this - } - - /** - * Добавляет поле-таблицу, которое кодируется как {"a": value, "b": value, ...} - * - * Таблица неизменяема (создаётся объект [ImmutableMap]) - */ - fun mapAsObject(field: KProperty1?>, valueType: Class): Builder { - types.add(PackedProperty(field, String2ObjectAdapter(valueType).nullSafe(), isFlat = false)) - return this - } - - /** - * Добавляет поле-таблицу, которое кодируется как {"a": value, "b": value, ...} - * - * Таблица неизменяема (создаётся объект [ImmutableMap]) - */ - inline fun mapAsObject(field: KProperty1?>): Builder { - return mapAsObject(field, V::class.java) - } - - /** - * Добавляет поле-таблицу, которое кодируется как {"a": value, "b": value, ...} - * - * Таблица неизменяема (создаётся объект [ImmutableMap]) - */ - fun mapAsObject(field: KProperty1?>, valueType: KClass): Builder { - types.add(PackedProperty(field, String2ObjectAdapter(valueType.java).nullSafe(), isFlat = false)) + fun auto(field: KProperty1, isFlat: Boolean = false, transform: (TypeAdapter) -> TypeAdapter = { it }): Builder { + types.add(ResolvableProperty(field, isFlat = isFlat, transform = transform)) return this } @@ -601,18 +486,6 @@ class FactoryAdapter private constructor( asList = true return this } - - fun build(): FactoryAdapter { - check(!asList || types.none { it.isFlat }) { "Can't have both flat properties and json data array layout" } - - return FactoryAdapter( - bound = clazz, - types = ImmutableList.copyOf(types), - asJsonArray = asList, - storesJson = storesJson, - logMisses = logMisses, - ) - } } companion object { diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/io/json/builder/Properties.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/io/json/builder/Properties.kt new file mode 100644 index 00000000..187d6d74 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/io/json/builder/Properties.kt @@ -0,0 +1,117 @@ +package ru.dbotthepony.kstarbound.io.json.builder + +import com.google.gson.Gson +import com.google.gson.TypeAdapter +import com.google.gson.reflect.TypeToken +import kotlin.reflect.KMutableProperty1 +import kotlin.reflect.KProperty1 +import kotlin.reflect.KType +import kotlin.reflect.javaType + +interface IProperty { + val isFlat: Boolean + val name: String + val mustBePresent: Boolean? +} + +interface IReferencedProperty : IProperty { + val property: KProperty1 + override val name: String get() = property.name + override val mustBePresent: Boolean? + get() = null +} + +interface IResolvableProperty : IReferencedProperty { + fun resolve(gson: Gson?): IResolvedProperty +} + +interface IResolvedProperty : IReferencedProperty { + val type: KType + val adapter: TypeAdapter + + operator fun component1() = property + operator fun component2() = adapter +} + +class ResolvedProperty( + override val property: KProperty1, + override val adapter: TypeAdapter, + override val isFlat: Boolean +) : IResolvableProperty, IResolvedProperty { + override val type: KType = property.returnType + + override fun resolve(gson: Gson?): IResolvedProperty { + return this + } +} + +class ResolvableProperty( + override val property: KProperty1, + override val isFlat: Boolean, + val transform: (TypeAdapter) -> TypeAdapter = { it } +) : IResolvableProperty { + @OptIn(ExperimentalStdlibApi::class) + override fun resolve(gson: Gson?): IResolvedProperty { + gson ?: throw NullPointerException("Can not resolve without Gson present") + + return ResolvedProperty( + property = property, + adapter = transform(gson.getAdapter(TypeToken.get(property.returnType.javaType)) as TypeAdapter), + isFlat = isFlat + ) + } +} + +interface IReferencedMutableProperty : IProperty { + val property: KMutableProperty1 + override val name: String get() = property.name +} + +interface IResolvableMutableProperty : IReferencedMutableProperty { + fun resolve(gson: Gson?): IResolvedMutableProperty +} + +interface IResolvedMutableProperty : IResolvableMutableProperty { + val type: KType + val adapter: TypeAdapter + + operator fun component1() = property + operator fun component2() = adapter + + @Suppress("unchecked_cast") + fun set(receiver: T, value: Any?) { + property.set(receiver, value as V) + } +} + +class ResolvedMutableProperty( + override val property: KMutableProperty1, + override val adapter: TypeAdapter, + override val isFlat: Boolean, + override val mustBePresent: Boolean? +) : IResolvableMutableProperty, IResolvedMutableProperty { + override val type: KType = property.returnType + + override fun resolve(gson: Gson?): IResolvedMutableProperty { + return this + } +} + +class ResolvableMutableProperty( + override val property: KMutableProperty1, + override val isFlat: Boolean, + val transform: (TypeAdapter) -> TypeAdapter = { it }, + override val mustBePresent: Boolean? +) : IResolvableMutableProperty { + @OptIn(ExperimentalStdlibApi::class) + override fun resolve(gson: Gson?): IResolvedMutableProperty { + gson ?: throw NullPointerException("Can not resolve without Gson present") + + return ResolvedMutableProperty( + property = property, + adapter = transform(gson.getAdapter(TypeToken.get(property.returnType.javaType)) as TypeAdapter), + isFlat = isFlat, + mustBePresent = mustBePresent, + ) + } +} diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/io/json/factory/ImmutableCollectionAdapterFactory.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/io/json/factory/ImmutableCollectionAdapterFactory.kt index 4a716806..bf2631ae 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/io/json/factory/ImmutableCollectionAdapterFactory.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/io/json/factory/ImmutableCollectionAdapterFactory.kt @@ -15,25 +15,25 @@ object ImmutableCollectionAdapterFactory : TypeAdapterFactory { when (type.rawType) { ImmutableList::class.java -> { val elementType = `$Gson$Types`.getCollectionElementType(type.type, type.rawType) - return ImmutableListTypeAdapter(gson.getAdapter(TypeToken.get(elementType))) as TypeAdapter + return ImmutableListTypeAdapter(gson.getAdapter(TypeToken.get(elementType))).nullSafe() as TypeAdapter } ImmutableSet::class.java -> { val elementType = `$Gson$Types`.getCollectionElementType(type.type, type.rawType) - return ImmutableSetTypeAdapter(gson.getAdapter(TypeToken.get(elementType))) as TypeAdapter + return ImmutableSetTypeAdapter(gson.getAdapter(TypeToken.get(elementType))).nullSafe() as TypeAdapter } ImmutableMap::class.java -> { val (elementType0, elementType1) = `$Gson$Types`.getMapKeyAndValueTypes(type.type, type.rawType) if (`$Gson$Types`.getRawType(elementType0) == String::class.java) { - return ImmutableMapTypeAdapter(gson.getAdapter(TypeToken.get(elementType1))) as TypeAdapter + return ImmutableMapTypeAdapter(gson.getAdapter(TypeToken.get(elementType1))).nullSafe() as TypeAdapter } return ImmutableArrayMapTypeAdapter( gson.getAdapter(TypeToken.get(elementType0)), gson.getAdapter(TypeToken.get(elementType1)) - ) as TypeAdapter + ).nullSafe() as TypeAdapter } else -> return null