diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/Starbound.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/Starbound.kt index 1fd3643c..2c828e01 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/Starbound.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/Starbound.kt @@ -19,20 +19,20 @@ import ru.dbotthepony.kstarbound.api.PhysicalFile import ru.dbotthepony.kstarbound.defs.* import ru.dbotthepony.kstarbound.defs.image.AtlasConfiguration import ru.dbotthepony.kstarbound.defs.image.ImageReference -import ru.dbotthepony.kstarbound.defs.item.impl.BackArmorItemPrototype -import ru.dbotthepony.kstarbound.defs.item.impl.ChestArmorItemPrototype -import ru.dbotthepony.kstarbound.defs.item.impl.CurrencyItemPrototype -import ru.dbotthepony.kstarbound.defs.item.impl.FlashlightPrototype +import ru.dbotthepony.kstarbound.defs.item.impl.BackArmorItemDefinition +import ru.dbotthepony.kstarbound.defs.item.impl.ChestArmorItemDefinition +import ru.dbotthepony.kstarbound.defs.item.impl.CurrencyItemDefinition +import ru.dbotthepony.kstarbound.defs.item.impl.FlashlightDefinition import ru.dbotthepony.kstarbound.defs.item.impl.HarvestingToolPrototype -import ru.dbotthepony.kstarbound.defs.item.impl.HeadArmorItemPrototype +import ru.dbotthepony.kstarbound.defs.item.impl.HeadArmorItemDefinition import ru.dbotthepony.kstarbound.defs.item.api.IArmorItemDefinition import ru.dbotthepony.kstarbound.defs.item.api.IItemDefinition import ru.dbotthepony.kstarbound.defs.item.InventoryIcon import ru.dbotthepony.kstarbound.defs.item.TreasurePoolDefinition -import ru.dbotthepony.kstarbound.defs.item.impl.ItemPrototype -import ru.dbotthepony.kstarbound.defs.item.impl.LegsArmorItemPrototype -import ru.dbotthepony.kstarbound.defs.item.impl.LiquidItemPrototype -import ru.dbotthepony.kstarbound.defs.item.impl.MaterialItemPrototype +import ru.dbotthepony.kstarbound.defs.item.impl.ItemDefinition +import ru.dbotthepony.kstarbound.defs.item.impl.LegsArmorItemDefinition +import ru.dbotthepony.kstarbound.defs.item.impl.LiquidItemDefinition +import ru.dbotthepony.kstarbound.defs.item.impl.MaterialItemDefinition import ru.dbotthepony.kstarbound.defs.monster.MonsterSkillDefinition import ru.dbotthepony.kstarbound.defs.monster.MonsterTypeDefinition import ru.dbotthepony.kstarbound.defs.npc.NpcTypeDefinition @@ -85,7 +85,6 @@ import java.lang.ref.ReferenceQueue import java.lang.ref.WeakReference import java.text.DateFormat import java.time.Duration -import java.util.* import java.util.function.BiConsumer import java.util.function.BinaryOperator import java.util.function.Function @@ -1056,16 +1055,16 @@ class Starbound : ISBFileLocator { private fun loadItemDefinitions(callback: (String) -> Unit, files: Map>) { val fileMap = mapOf( - "item" to ItemPrototype::class.java, - "currency" to CurrencyItemPrototype::class.java, - "liqitem" to LiquidItemPrototype::class.java, - "matitem" to MaterialItemPrototype::class.java, - "flashlight" to FlashlightPrototype::class.java, + "item" to ItemDefinition::class.java, + "currency" to CurrencyItemDefinition::class.java, + "liqitem" to LiquidItemDefinition::class.java, + "matitem" to MaterialItemDefinition::class.java, + "flashlight" to FlashlightDefinition::class.java, "harvestingtool" to HarvestingToolPrototype::class.java, - "head" to HeadArmorItemPrototype::class.java, - "chest" to ChestArmorItemPrototype::class.java, - "legs" to LegsArmorItemPrototype::class.java, - "back" to BackArmorItemPrototype::class.java, + "head" to HeadArmorItemDefinition::class.java, + "chest" to ChestArmorItemDefinition::class.java, + "legs" to LegsArmorItemDefinition::class.java, + "back" to BackArmorItemDefinition::class.java, ) for ((ext, clazz) in fileMap) { @@ -1075,7 +1074,7 @@ class Starbound : ISBFileLocator { try { callback("Loading $listedFile") val json = gson.fromJson(listedFile.reader(), JsonObject::class.java) - val def: ItemPrototype = pathStack(listedFile.computeDirectory()) { gson.fromJson(JsonTreeReader(json), clazz) } + val def: IItemDefinition = pathStack(listedFile.computeDirectory()) { gson.fromJson(JsonTreeReader(json), clazz) } _items.add(def, json, listedFile, gson, pathStack) } catch (err: Throwable) { logger.error("Loading item definition file $listedFile", err) diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/FreezableDefintionBuilder.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/FreezableDefintionBuilder.kt deleted file mode 100644 index 8f11addd..00000000 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/FreezableDefintionBuilder.kt +++ /dev/null @@ -1,108 +0,0 @@ -package ru.dbotthepony.kstarbound.defs - -import ru.dbotthepony.kstarbound.io.json.builder.JsonIgnoreProperty -import ru.dbotthepony.kstarbound.util.INotNullDelegate -import kotlin.properties.ReadWriteProperty -import kotlin.reflect.KProperty - -abstract class FreezableDefintionBuilder { - interface IProperty { - fun checkAndThrow() - } - - inner class Nullable(private var value: V? = null) : ReadWriteProperty, IProperty { - override fun getValue(thisRef: Any?, property: KProperty<*>): V? { - return value - } - - override fun setValue(thisRef: Any?, property: KProperty<*>, value: V?) { - if (isFrozen) throw IllegalStateException("$thisRef is frozen!") - this.value = value - } - - override fun checkAndThrow() { - // no op - } - - override fun equals(other: Any?): Boolean { - return other is Nullable<*> && other.value == value - } - - override fun hashCode(): Int { - return value?.hashCode() ?: 0 - } - - override fun toString(): String { - return "Nullable[$value]" - } - } - - inner class NotNull(private var value: V? = null) : ReadWriteProperty, IProperty, INotNullDelegate { - override val isPresent: Boolean - get() = value != null - - fun getNullable(): V? { - return value - } - - override fun getValue(thisRef: Any?, property: KProperty<*>): V { - return checkNotNull(value) {"${property.name} was not initialized"} - } - - override fun setValue(thisRef: Any?, property: KProperty<*>, value: V) { - if (isFrozen) throw IllegalStateException("$thisRef is frozen!") - this.value = value - } - - override fun checkAndThrow() { - if (value == null) { - throw NullPointerException("Value was not initialized") - } - } - - override fun equals(other: Any?): Boolean { - return other is NotNull<*> && other.value == value - } - - override fun hashCode(): Int { - return value?.hashCode() ?: 0 - } - - override fun toString(): String { - return "NotNull[$value]" - } - } - - @JsonIgnoreProperty - var isFrozen = false - private set - - @JsonIgnoreProperty - private val properties = ArrayList() - - override fun equals(other: Any?): Boolean { - if (other is FreezableDefintionBuilder && other.javaClass === this.javaClass) - return other.properties == properties - - return false - } - - override fun hashCode(): Int { - return properties.hashCode() - } - - override fun toString(): String { - return "${this::class.simpleName}[frozen=$isFrozen,values={${properties.joinToString(",")}}]" - } - - protected open fun onFreeze() { - - } - - fun freeze() { - if (isFrozen) return - onFreeze() - properties.forEach { it.checkAndThrow() } - isFrozen = true - } -} diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/IScriptable.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/IScriptable.kt index d95efd3c..362782c0 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/IScriptable.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/IScriptable.kt @@ -1,5 +1,10 @@ package ru.dbotthepony.kstarbound.defs +import com.google.common.collect.ImmutableList +import ru.dbotthepony.kstarbound.io.json.builder.JsonFactory +import ru.dbotthepony.kstarbound.io.json.builder.JsonImplementation + +@JsonImplementation(IScriptable.Impl::class) interface IScriptable { /** * Lua скрипты для выполнения @@ -10,4 +15,10 @@ interface IScriptable { * Через какое количество тиков вызывать обновления скриптов */ val scriptDelta: Int + + @JsonFactory + data class Impl( + override val scripts: ImmutableList = ImmutableList.of(), + override val scriptDelta: Int = 1 + ) : IScriptable } diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/IThingWithDescription.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/IThingWithDescription.kt index 8398dda7..4b79c32c 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/IThingWithDescription.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/IThingWithDescription.kt @@ -125,11 +125,15 @@ data class ThingDescription( "shortdescription" -> shortdescription = `in`.nextString() "description" -> description = `in`.nextString() else -> { - if (name.endsWith("shortdescription") || name.endsWith("shortDescription") || name.endsWith("Shortdescription") || name.endsWith("ShortDescription")) { - racialShort.put(interner.intern(name.substring(0, name.length - "shortdescription".length)), interner.intern(`in`.nextString())) - } else if (name.endsWith("description") || name.endsWith("Description")) { - racial.put(interner.intern(name.substring(0, name.length - "description".length)), interner.intern(`in`.nextString())) - } else { + try { + if (name.endsWith("shortdescription") || name.endsWith("shortDescription") || name.endsWith("Shortdescription") || name.endsWith("ShortDescription")) { + racialShort.put(interner.intern(name.substring(0, name.length - "shortdescription".length)), interner.intern(`in`.nextString())) + } else if (name.endsWith("description") || name.endsWith("Description")) { + racial.put(interner.intern(name.substring(0, name.length - "description".length)), interner.intern(`in`.nextString())) + } else { + `in`.skipValue() + } + } catch (_: IllegalStateException) { `in`.skipValue() } } diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/ItemReference.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/ItemReference.kt index 3ee9e8a1..1700aa6c 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/ItemReference.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/ItemReference.kt @@ -31,8 +31,8 @@ data class ItemReference( override fun create(gson: Gson, type: TypeToken): TypeAdapter? { if (type.rawType == ItemReference::class.java) { return object : TypeAdapter() { - private val regularObject = FactoryAdapter.createFor(ItemReference::class, JsonFactory(storesJson = false, logMisses = true, asList = false), gson, stringInterner) - private val regularList = FactoryAdapter.createFor(ItemReference::class, JsonFactory(storesJson = false, logMisses = true, asList = true), gson, stringInterner) + private val regularObject = FactoryAdapter.createFor(ItemReference::class, JsonFactory(storesJson = false, asList = false), gson, stringInterner) + private val regularList = FactoryAdapter.createFor(ItemReference::class, JsonFactory(storesJson = false, asList = true), gson, stringInterner) private val references = gson.getAdapter(TypeToken.getParameterized(RegistryReference::class.java, IItemDefinition::class.java)) as TypeAdapter> override fun write(out: JsonWriter, value: ItemReference?) { diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/JumpProfile.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/JumpProfile.kt index 157222b1..e0897281 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/JumpProfile.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/JumpProfile.kt @@ -4,7 +4,7 @@ import ru.dbotthepony.kstarbound.io.json.builder.JsonFactory @JsonFactory data class JumpProfile( - val jumpSpeed: Double, - val jumpInitialPercentage: Double, - val jumpHoldTime: Double, + val jumpSpeed: Double = 0.0, + val jumpInitialPercentage: Double = 0.0, + val jumpHoldTime: Double = 0.0, ) diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/item/api/IItemDefinition.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/item/api/IItemDefinition.kt index 49b97fb4..1a062f8a 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/item/api/IItemDefinition.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/item/api/IItemDefinition.kt @@ -4,7 +4,10 @@ import ru.dbotthepony.kstarbound.defs.IThingWithDescription import ru.dbotthepony.kstarbound.defs.RegistryReference import ru.dbotthepony.kstarbound.defs.item.IInventoryIcon import ru.dbotthepony.kstarbound.defs.item.ItemRarity +import ru.dbotthepony.kstarbound.defs.item.impl.ItemDefinition +import ru.dbotthepony.kstarbound.io.json.builder.JsonImplementation +@JsonImplementation(ItemDefinition::class) interface IItemDefinition : IThingWithDescription { /** * Внутреннее имя предмета (ID). diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/item/api/ILiquidItem.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/item/api/ILiquidItem.kt index 0faf105a..f6757add 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/item/api/ILiquidItem.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/item/api/ILiquidItem.kt @@ -1,6 +1,5 @@ package ru.dbotthepony.kstarbound.defs.item.api -import ru.dbotthepony.kstarbound.defs.item.api.IItemDefinition import ru.dbotthepony.kstarbound.util.Either interface ILiquidItem : IItemDefinition { diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/item/impl/ArmorItemDefinition.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/item/impl/ArmorItemDefinition.kt new file mode 100644 index 00000000..d8aa7a79 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/item/impl/ArmorItemDefinition.kt @@ -0,0 +1,51 @@ +package ru.dbotthepony.kstarbound.defs.item.impl + +import com.google.common.collect.ImmutableList +import ru.dbotthepony.kstarbound.defs.DirectAssetReference +import ru.dbotthepony.kstarbound.defs.IScriptable +import ru.dbotthepony.kstarbound.defs.item.api.IArmorItemDefinition +import ru.dbotthepony.kstarbound.defs.item.LeveledStatusEffect +import ru.dbotthepony.kstarbound.defs.item.api.IItemDefinition +import ru.dbotthepony.kstarbound.io.json.builder.JsonBuilder +import ru.dbotthepony.kstarbound.io.json.builder.JsonFactory +import ru.dbotthepony.kstarbound.io.json.builder.JsonFlat + +@JsonFactory +data class ArmorItemDefinition( + @JsonFlat + val parent: IItemDefinition, + @JsonFlat + val script: IScriptable.Impl, + + override val maxStack: Long = 1L, + + override val colorOptions: ImmutableList> = ImmutableList.of(), + override val maleFrames: IArmorItemDefinition.Frames, + override val femaleFrames: IArmorItemDefinition.Frames, + override val level: Double = 1.0, + override val leveledStatusEffects: ImmutableList = ImmutableList.of(), +) : IArmorItemDefinition, IItemDefinition by parent, IScriptable by script + +@JsonFactory +class HeadArmorItemDefinition(@JsonFlat val parent: ArmorItemDefinition) : IArmorItemDefinition by parent { + override val itemType: String + get() = "headarmor" +} + +@JsonFactory +class ChestArmorItemDefinition(@JsonFlat val parent: ArmorItemDefinition) : IArmorItemDefinition by parent { + override val itemType: String + get() = "chestarmor" +} + +@JsonFactory +class LegsArmorItemDefinition(@JsonFlat val parent: ArmorItemDefinition) : IArmorItemDefinition by parent { + override val itemType: String + get() = "legsarmor" +} + +@JsonFactory +class BackArmorItemDefinition(@JsonFlat val parent: ArmorItemDefinition) : IArmorItemDefinition by parent { + override val itemType: String + get() = "backarmor" +} diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/item/impl/ArmorItemPrototype.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/item/impl/ArmorItemPrototype.kt deleted file mode 100644 index 1e8ab957..00000000 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/item/impl/ArmorItemPrototype.kt +++ /dev/null @@ -1,47 +0,0 @@ -package ru.dbotthepony.kstarbound.defs.item.impl - -import com.google.common.collect.ImmutableList -import ru.dbotthepony.kstarbound.defs.DirectAssetReference -import ru.dbotthepony.kstarbound.defs.item.api.IArmorItemDefinition -import ru.dbotthepony.kstarbound.defs.item.LeveledStatusEffect -import ru.dbotthepony.kstarbound.io.json.builder.JsonBuilder - -@JsonBuilder -abstract class ArmorItemPrototype : ItemPrototype(), IArmorItemDefinition { - final override var colorOptions: ImmutableList> by NotNull(ImmutableList.of()) - final override var maleFrames: IArmorItemDefinition.Frames by NotNull() - final override var femaleFrames: IArmorItemDefinition.Frames by NotNull() - final override var level: Double by NotNull(1.0) - final override var leveledStatusEffects: ImmutableList by NotNull(ImmutableList.of()) - - final override var scripts: ImmutableList by NotNull(ImmutableList.of()) - final override var scriptDelta: Int by NotNull(1) - - init { - maxStack = 1L - } -} - -@JsonBuilder -class HeadArmorItemPrototype : ArmorItemPrototype() { - override val itemType: String - get() = "headarmor" -} - -@JsonBuilder -class ChestArmorItemPrototype : ArmorItemPrototype() { - override val itemType: String - get() = "chestarmor" -} - -@JsonBuilder -class LegsArmorItemPrototype : ArmorItemPrototype() { - override val itemType: String - get() = "legsarmor" -} - -@JsonBuilder -class BackArmorItemPrototype : ArmorItemPrototype() { - override val itemType: String - get() = "backarmor" -} diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/item/impl/CurrencyItemDefinition.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/item/impl/CurrencyItemDefinition.kt new file mode 100644 index 00000000..2a8d2965 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/item/impl/CurrencyItemDefinition.kt @@ -0,0 +1,19 @@ +package ru.dbotthepony.kstarbound.defs.item.impl + +import ru.dbotthepony.kstarbound.defs.item.api.ICurrencyItemDefinition +import ru.dbotthepony.kstarbound.defs.item.api.IItemDefinition +import ru.dbotthepony.kstarbound.io.json.builder.JsonBuilder +import ru.dbotthepony.kstarbound.io.json.builder.JsonFactory +import ru.dbotthepony.kstarbound.io.json.builder.JsonFlat + +@JsonFactory +data class CurrencyItemDefinition( + @JsonFlat + val parent: IItemDefinition, + override val maxStack: Long = 16777216L, + override var currency: String, + override var value: Long, +) : ICurrencyItemDefinition, IItemDefinition by parent { + override val itemType: String + get() = super.itemType +} diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/item/impl/CurrencyItemPrototype.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/item/impl/CurrencyItemPrototype.kt deleted file mode 100644 index b4169faf..00000000 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/item/impl/CurrencyItemPrototype.kt +++ /dev/null @@ -1,14 +0,0 @@ -package ru.dbotthepony.kstarbound.defs.item.impl - -import ru.dbotthepony.kstarbound.defs.item.api.ICurrencyItemDefinition -import ru.dbotthepony.kstarbound.io.json.builder.JsonBuilder - -@JsonBuilder -class CurrencyItemPrototype : ItemPrototype(), ICurrencyItemDefinition { - override var currency: String by NotNull() - override var value: Long by NotNull() - - init { - maxStack = 16777216L - } -} diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/item/impl/FlashlightDefinition.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/item/impl/FlashlightDefinition.kt new file mode 100644 index 00000000..a0da2b3c --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/item/impl/FlashlightDefinition.kt @@ -0,0 +1,21 @@ +package ru.dbotthepony.kstarbound.defs.item.impl + +import ru.dbotthepony.kstarbound.defs.item.api.IFlashlightDefinition +import ru.dbotthepony.kstarbound.defs.item.api.IItemDefinition +import ru.dbotthepony.kstarbound.io.json.builder.JsonFactory +import ru.dbotthepony.kstarbound.io.json.builder.JsonFlat +import ru.dbotthepony.kvector.vector.Color +import ru.dbotthepony.kvector.vector.ndouble.Vector2d + +@JsonFactory +class FlashlightDefinition( + @JsonFlat + val parent: IItemDefinition, + + override val lightPosition: Vector2d, + override val lightColor: Color, + override val beamLevel: Int, + override val beamAmbience: Double, + override val handPosition: Vector2d, + override val maxStack: Long = 1L, +) : IItemDefinition by parent, IFlashlightDefinition diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/item/impl/FlashlightPrototype.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/item/impl/FlashlightPrototype.kt deleted file mode 100644 index 1d935b44..00000000 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/item/impl/FlashlightPrototype.kt +++ /dev/null @@ -1,19 +0,0 @@ -package ru.dbotthepony.kstarbound.defs.item.impl - -import ru.dbotthepony.kstarbound.defs.item.api.IFlashlightDefinition -import ru.dbotthepony.kstarbound.io.json.builder.JsonBuilder -import ru.dbotthepony.kvector.vector.Color -import ru.dbotthepony.kvector.vector.ndouble.Vector2d - -@JsonBuilder -class FlashlightPrototype : ItemPrototype(), IFlashlightDefinition { - override var lightPosition: Vector2d by NotNull() - override var lightColor: Color by NotNull() - override var beamLevel: Int by NotNull() - override var beamAmbience: Double by NotNull() - override var handPosition: Vector2d by NotNull() - - init { - maxStack = 1L - } -} diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/item/impl/HarvestingToolPrototype.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/item/impl/HarvestingToolPrototype.kt index 90d5ee42..8c44f5c2 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/item/impl/HarvestingToolPrototype.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/item/impl/HarvestingToolPrototype.kt @@ -2,21 +2,23 @@ package ru.dbotthepony.kstarbound.defs.item.impl import com.google.common.collect.ImmutableList import ru.dbotthepony.kstarbound.defs.item.api.IHarvestingToolDefinition +import ru.dbotthepony.kstarbound.defs.item.api.IItemDefinition import ru.dbotthepony.kstarbound.io.json.builder.JsonBuilder +import ru.dbotthepony.kstarbound.io.json.builder.JsonFactory +import ru.dbotthepony.kstarbound.io.json.builder.JsonFlat import ru.dbotthepony.kvector.vector.ndouble.Vector2d -@JsonBuilder -class HarvestingToolPrototype : ItemPrototype(), IHarvestingToolDefinition { - override var frames: Int by NotNull() - override var animationCycle: Double by NotNull() - override var blockRadius: Int by NotNull() - override var altBlockRadius: Int by NotNull(0) - override var idleSound: ImmutableList by NotNull(ImmutableList.of()) - override var strikeSounds: ImmutableList by NotNull(ImmutableList.of()) - override var handPosition: Vector2d by NotNull() - override var fireTime: Double by NotNull() - - init { - maxStack = 1L - } -} +@JsonFactory +class HarvestingToolPrototype( + @JsonFlat + val parent: IItemDefinition, + override val frames: Int, + override val animationCycle: Double, + override val blockRadius: Int, + override val altBlockRadius: Int = 0, + override val idleSound: ImmutableList = ImmutableList.of(), + override val strikeSounds: ImmutableList = ImmutableList.of(), + override val handPosition: Vector2d, + override val fireTime: Double, + override val maxStack: Long = 1L, +) : IItemDefinition by parent, IHarvestingToolDefinition diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/item/impl/ItemDefinition.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/item/impl/ItemDefinition.kt new file mode 100644 index 00000000..873fc0e4 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/item/impl/ItemDefinition.kt @@ -0,0 +1,39 @@ +package ru.dbotthepony.kstarbound.defs.item.impl + +import com.google.common.collect.ImmutableList +import ru.dbotthepony.kstarbound.defs.IThingWithDescription +import ru.dbotthepony.kstarbound.defs.RegistryReference +import ru.dbotthepony.kstarbound.defs.ThingDescription +import ru.dbotthepony.kstarbound.defs.item.IInventoryIcon +import ru.dbotthepony.kstarbound.defs.item.api.IItemDefinition +import ru.dbotthepony.kstarbound.defs.item.InventoryIcon +import ru.dbotthepony.kstarbound.defs.item.ItemRarity +import ru.dbotthepony.kstarbound.io.json.builder.JsonFactory +import ru.dbotthepony.kstarbound.io.json.builder.JsonFlat + +@JsonFactory +data class ItemDefinition( + @JsonFlat + val descriptionData: ThingDescription, + + override var itemName: String, + override var price: Long = 0, + override var rarity: ItemRarity = ItemRarity.COMMON, + override var category: String, + override var inventoryIcon: ImmutableList? = null, + override var itemTags: ImmutableList = ImmutableList.of(), + override var learnBlueprintsOnPickup: ImmutableList> = ImmutableList.of(), + override var maxStack: Long = 9999L, + override var eventCategory: String? = null, + override var consumeOnPickup: Boolean = false, + override var pickupQuestTemplates: ImmutableList = ImmutableList.of(), + override var tooltipKind: String = "normal", + override var twoHanded: Boolean = false, + override var radioMessagesOnPickup: ImmutableList = ImmutableList.of(), + override var fuelAmount: Long = 0, + override var pickupSoundsSmall: ImmutableList = ImmutableList.of(), + override var pickupSoundsMedium: ImmutableList = ImmutableList.of(), + override var pickupSoundsLarge: ImmutableList = ImmutableList.of(), + override var smallStackLimit: Long? = null, + override var mediumStackLimit: Long? = null, +) : IItemDefinition, IThingWithDescription by descriptionData diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/item/impl/ItemPrototype.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/item/impl/ItemPrototype.kt deleted file mode 100644 index b533fd8b..00000000 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/item/impl/ItemPrototype.kt +++ /dev/null @@ -1,56 +0,0 @@ -package ru.dbotthepony.kstarbound.defs.item.impl - -import com.google.common.collect.ImmutableList -import ru.dbotthepony.kstarbound.defs.FreezableDefintionBuilder -import ru.dbotthepony.kstarbound.defs.RegistryReference -import ru.dbotthepony.kstarbound.defs.ThingDescription -import ru.dbotthepony.kstarbound.defs.item.api.IItemDefinition -import ru.dbotthepony.kstarbound.defs.item.InventoryIcon -import ru.dbotthepony.kstarbound.defs.item.ItemRarity -import ru.dbotthepony.kstarbound.io.json.builder.JsonBuilder -import ru.dbotthepony.kstarbound.io.json.builder.JsonPropertyConfig -import ru.dbotthepony.kstarbound.io.json.builder.JsonIgnoreProperty - -@JsonBuilder -open class ItemPrototype : FreezableDefintionBuilder(), IItemDefinition { - @JsonIgnoreProperty - final override val shortdescription: String - get() = descriptionData.shortdescription - - @JsonIgnoreProperty - final override val description: String - get() = descriptionData.description - - @JsonIgnoreProperty - final override val racialDescription: Map - get() = descriptionData.racialDescription - - @JsonIgnoreProperty - final override val racialShortDescription: Map - get() = descriptionData.racialShortDescription - - @JsonPropertyConfig(isFlat = true) - var descriptionData: ThingDescription by NotNull() - - final override var itemName: String by NotNull() - final override var price: Long by NotNull(0L) - final override var rarity: ItemRarity by NotNull(ItemRarity.COMMON) - final override var category: String by NotNull() - final override var inventoryIcon: ImmutableList? by Nullable() - final override var itemTags: ImmutableList by NotNull(ImmutableList.of()) - final override var learnBlueprintsOnPickup: ImmutableList> by NotNull(ImmutableList.of()) - final override var maxStack: Long by NotNull(9999L) - final override var eventCategory: String? by Nullable() - final override var consumeOnPickup: Boolean by NotNull(false) - final override var pickupQuestTemplates: ImmutableList by NotNull(ImmutableList.of()) - final override var tooltipKind: String by NotNull("normal") - final override var twoHanded: Boolean by NotNull(false) - final override var radioMessagesOnPickup: ImmutableList by NotNull(ImmutableList.of()) - final override var fuelAmount: Long by NotNull(0L) - - final override var pickupSoundsSmall: ImmutableList by NotNull(ImmutableList.of()) - final override var pickupSoundsMedium: ImmutableList by NotNull(ImmutableList.of()) - final override var pickupSoundsLarge: ImmutableList by NotNull(ImmutableList.of()) - final override var smallStackLimit: Long? by Nullable() - final override var mediumStackLimit: Long? by Nullable() -} diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/item/impl/LiquidItemDefinition.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/item/impl/LiquidItemDefinition.kt new file mode 100644 index 00000000..0f65ad44 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/item/impl/LiquidItemDefinition.kt @@ -0,0 +1,20 @@ +package ru.dbotthepony.kstarbound.defs.item.impl + +import ru.dbotthepony.kstarbound.defs.item.api.IItemDefinition +import ru.dbotthepony.kstarbound.defs.item.api.ILiquidItem +import ru.dbotthepony.kstarbound.io.json.builder.JsonAlias +import ru.dbotthepony.kstarbound.io.json.builder.JsonBuilder +import ru.dbotthepony.kstarbound.io.json.builder.JsonFactory +import ru.dbotthepony.kstarbound.io.json.builder.JsonFlat +import ru.dbotthepony.kstarbound.util.Either + +@JsonFactory +data class LiquidItemDefinition( + @JsonFlat + val parent: IItemDefinition, + @JsonAlias("liquidId", "liquidName") + override val liquid: Either, +) : IItemDefinition by parent, ILiquidItem { + override val itemType: String + get() = super.itemType +} diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/item/impl/LiquidItemPrototype.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/item/impl/LiquidItemPrototype.kt deleted file mode 100644 index 615a84fb..00000000 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/item/impl/LiquidItemPrototype.kt +++ /dev/null @@ -1,25 +0,0 @@ -package ru.dbotthepony.kstarbound.defs.item.impl - -import ru.dbotthepony.kstarbound.defs.item.api.ILiquidItem -import ru.dbotthepony.kstarbound.io.json.builder.JsonBuilder -import ru.dbotthepony.kstarbound.util.Either - -@JsonBuilder -class LiquidItemPrototype : ItemPrototype(), ILiquidItem { - private val liquidDelegate = NotNull>() - override var liquid: Either by liquidDelegate - - var liquidId: Int? - get() = liquidDelegate.getNullable()?.left - set(value) { if (liquidDelegate.getNullable() == null) liquid = Either.left(value!!) } - - var liquidName: String? - get() = liquidDelegate.getNullable()?.right - set(value) { liquid = Either.right(value!!) } - - override fun onFreeze() { - if (liquidDelegate.getNullable() == null) { - throw NullPointerException("no liquidId nor liquidName was defined") - } - } -} diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/item/impl/MaterialItemDefinition.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/item/impl/MaterialItemDefinition.kt new file mode 100644 index 00000000..7a207d9d --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/item/impl/MaterialItemDefinition.kt @@ -0,0 +1,21 @@ +package ru.dbotthepony.kstarbound.defs.item.impl + +import ru.dbotthepony.kstarbound.defs.item.api.IItemDefinition +import ru.dbotthepony.kstarbound.defs.item.api.ILiquidItem +import ru.dbotthepony.kstarbound.defs.item.api.IMaterialItem +import ru.dbotthepony.kstarbound.io.json.builder.JsonAlias +import ru.dbotthepony.kstarbound.io.json.builder.JsonBuilder +import ru.dbotthepony.kstarbound.io.json.builder.JsonFactory +import ru.dbotthepony.kstarbound.io.json.builder.JsonFlat +import ru.dbotthepony.kstarbound.util.Either + +@JsonFactory +data class MaterialItemDefinition( + @JsonFlat + val parent: IItemDefinition, + @JsonAlias("materialId", "materialName") + override val material: Either +) : IMaterialItem, IItemDefinition by parent { + override val itemType: String + get() = super.itemType +} diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/item/impl/MaterialItemPrototype.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/item/impl/MaterialItemPrototype.kt deleted file mode 100644 index 1efa833f..00000000 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/item/impl/MaterialItemPrototype.kt +++ /dev/null @@ -1,25 +0,0 @@ -package ru.dbotthepony.kstarbound.defs.item.impl - -import ru.dbotthepony.kstarbound.defs.item.api.IMaterialItem -import ru.dbotthepony.kstarbound.io.json.builder.JsonBuilder -import ru.dbotthepony.kstarbound.util.Either - -@JsonBuilder -class MaterialItemPrototype : ItemPrototype(), IMaterialItem { - private val materialDelegate = NotNull>() - override var material: Either by materialDelegate - - var materialId: Int? - get() = materialDelegate.getNullable()?.left - set(value) { if (materialDelegate.getNullable() == null) material = Either.left(value!!) } - - var materialName: String? - get() = materialDelegate.getNullable()?.right - set(value) { material = Either.right(value!!) } - - override fun onFreeze() { - if (material == null) { - throw NullPointerException("no materialId nor materialName was defined") - } - } -} diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/monster/MonsterTypeDefinition.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/monster/MonsterTypeDefinition.kt index b7393d94..0e705852 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/monster/MonsterTypeDefinition.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/monster/MonsterTypeDefinition.kt @@ -12,23 +12,26 @@ import ru.dbotthepony.kstarbound.defs.RegistryReference import ru.dbotthepony.kstarbound.defs.animation.AnimationDefinition import ru.dbotthepony.kstarbound.defs.item.TreasurePoolDefinition import ru.dbotthepony.kstarbound.io.json.builder.JsonFactory +import ru.dbotthepony.kstarbound.io.json.builder.JsonFlat +import ru.dbotthepony.kstarbound.util.Either @JsonFactory data class MonsterTypeDefinition( val type: String, - override val shortdescription: String, - override val description: String, + @JsonFlat + val desc: IThingWithDescription, val categories: ImmutableSet = ImmutableSet.of(), val parts: ImmutableSet = ImmutableSet.of(), val animation: AssetReference, // [ { "default" : "poptopTreasure", "bow" : "poptopHunting" } ], - val dropPools: ImmutableList>>, + // "dropPools" : [ "smallRobotTreasure" ], + val dropPools: Either>>, ImmutableList>>, val baseParameters: BaseParameters -) : IThingWithDescription { +) : IThingWithDescription by desc { @JsonFactory data class BaseParameters( val movementSettings: MovementParameters? = null, - override val scriptDelta: Int = 1, - override val scripts: ImmutableList - ) : IScriptable + @JsonFlat + val script: IScriptable, + ) : IScriptable by script } diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/tile/MaterialModifier.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/tile/MaterialModifier.kt index 2746e84a..9b1b083a 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/tile/MaterialModifier.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/tile/MaterialModifier.kt @@ -1,13 +1,11 @@ package ru.dbotthepony.kstarbound.defs.tile import com.google.common.collect.ImmutableList -import com.google.gson.GsonBuilder import ru.dbotthepony.kstarbound.defs.AssetReference import ru.dbotthepony.kstarbound.defs.IThingWithDescription import ru.dbotthepony.kstarbound.defs.ThingDescription -import ru.dbotthepony.kstarbound.io.json.builder.FactoryAdapter import ru.dbotthepony.kstarbound.io.json.builder.JsonFactory -import ru.dbotthepony.kstarbound.io.json.builder.JsonPropertyConfig +import ru.dbotthepony.kstarbound.io.json.builder.JsonFlat @JsonFactory data class MaterialModifier( @@ -23,7 +21,7 @@ data class MaterialModifier( val footstepSound: String? = null, val miningSounds: ImmutableList = ImmutableList.of(), - @JsonPropertyConfig(isFlat = true) + @JsonFlat val descriptionData: ThingDescription, override val renderTemplate: AssetReference, 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 7b50472e..c1fe36a3 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/tile/TileDefinition.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/tile/TileDefinition.kt @@ -1,14 +1,11 @@ package ru.dbotthepony.kstarbound.defs.tile import com.google.common.collect.ImmutableList -import com.google.gson.GsonBuilder import ru.dbotthepony.kstarbound.defs.AssetReference import ru.dbotthepony.kstarbound.defs.IThingWithDescription import ru.dbotthepony.kstarbound.defs.ThingDescription -import ru.dbotthepony.kstarbound.io.json.builder.FactoryAdapter import ru.dbotthepony.kstarbound.io.json.builder.JsonFactory -import ru.dbotthepony.kstarbound.io.json.builder.JsonPropertyConfig -import ru.dbotthepony.kstarbound.registerTypeAdapter +import ru.dbotthepony.kstarbound.io.json.builder.JsonFlat import ru.dbotthepony.kvector.vector.Color @JsonFactory @@ -26,7 +23,7 @@ data class TileDefinition( val health: Double = 0.0, val category: String, - @JsonPropertyConfig(isFlat = true) + @JsonFlat val descriptionData: ThingDescription, override val renderTemplate: AssetReference, diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/util/ImmutableEnroller.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/util/ImmutableEnroller.kt index 4ce79ce8..6d417784 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/util/ImmutableEnroller.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/util/ImmutableEnroller.kt @@ -1,19 +1,20 @@ package ru.dbotthepony.kstarbound.defs.util +import com.github.benmanes.caffeine.cache.Interner import com.google.common.collect.ImmutableList import com.google.common.collect.ImmutableMap /** * Возвращает глубокую, неизменяемую копию [input] примитивов/List'ов/Map'ов */ -fun enrollList(input: List, interner: (String) -> String = String::intern): ImmutableList { +fun enrollList(input: List, interner: Interner = Interner { it }): ImmutableList { val builder = ImmutableList.builder() for (v in input) { when (v) { is Map<*, *> -> builder.add(enrollMap(v as Map, interner)) is List<*> -> builder.add(enrollList(v as List, interner)) - else -> builder.add((v as? String)?.let(interner) ?: v) + else -> builder.add((v as? String)?.let(interner::intern) ?: v) } } @@ -23,14 +24,14 @@ fun enrollList(input: List, interner: (String) -> String = String::intern): /** * Возвращает глубокую, неизменяемую копию [input] примитивов/List'ов/Map'ов */ -fun enrollMap(input: Map, interner: (String) -> String = String::intern): ImmutableMap { +fun enrollMap(input: Map, interner: Interner = Interner { it }): ImmutableMap { val builder = ImmutableMap.builder() for ((k, v) in input) { when (v) { - is Map<*, *> -> builder.put(interner(k), enrollMap(v as Map, interner)) - is List<*> -> builder.put(interner(k), enrollList(v as List, interner)) - else -> builder.put(interner(k), (v as? String)?.let(interner) ?: v) + is Map<*, *> -> builder.put(interner.intern(k), enrollMap(v as Map, interner)) + is List<*> -> builder.put(interner.intern(k), enrollList(v as List, interner)) + else -> builder.put(interner.intern(k), (v as? String)?.let(interner::intern) ?: v) } } diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/util/JsonFlattener.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/util/JsonFlattener.kt index 3a8323d1..57360f64 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/util/JsonFlattener.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/util/JsonFlattener.kt @@ -1,5 +1,6 @@ package ru.dbotthepony.kstarbound.defs.util +import com.github.benmanes.caffeine.cache.Interner import com.google.gson.JsonArray import com.google.gson.JsonElement import com.google.gson.JsonNull @@ -7,17 +8,17 @@ import com.google.gson.JsonObject import com.google.gson.JsonPrimitive import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap -private fun flattenJsonPrimitive(input: JsonPrimitive, interner: (String) -> String = String::intern): Any { +private fun flattenJsonPrimitive(input: JsonPrimitive, interner: Interner = Interner { it }): Any { if (input.isNumber) { return input.asNumber } else if (input.isString) { - return interner(input.asString) + return interner.intern(input.asString) } else { return input.asBoolean } } -private fun flattenJsonArray(input: JsonArray, interner: (String) -> String = String::intern): ArrayList { +private fun flattenJsonArray(input: JsonArray, interner: Interner = Interner { it }): ArrayList { val flattened = ArrayList(input.size()) for (v in input) { @@ -32,7 +33,7 @@ private fun flattenJsonArray(input: JsonArray, interner: (String) -> String = St return flattened } -private fun flattenJsonObject(input: JsonObject, interner: (String) -> String = String::intern): MutableMap { +private fun flattenJsonObject(input: JsonObject, interner: Interner = Interner { it }): MutableMap { val flattened = Object2ObjectOpenHashMap() for ((k, v) in input.entrySet()) { @@ -46,7 +47,7 @@ private fun flattenJsonObject(input: JsonObject, interner: (String) -> String = return flattened } -fun flattenJsonElement(input: JsonElement, interner: (String) -> String = String::intern): Any? { +fun flattenJsonElement(input: JsonElement, interner: Interner = Interner { it }): Any? { return when (input) { is JsonObject -> flattenJsonObject(input, interner) is JsonArray -> flattenJsonArray(input, interner) @@ -56,6 +57,6 @@ fun flattenJsonElement(input: JsonElement, interner: (String) -> String = String } } -fun flattenJsonElement(input: JsonObject, interner: (String) -> String = String::intern) = flattenJsonObject(input, interner) -fun flattenJsonElement(input: JsonArray, interner: (String) -> String = String::intern) = flattenJsonArray(input, interner) -fun flattenJsonElement(input: JsonPrimitive, interner: (String) -> String = String::intern) = flattenJsonPrimitive(input, interner) +fun flattenJsonElement(input: JsonObject, interner: Interner = Interner { it }) = flattenJsonObject(input, interner) +fun flattenJsonElement(input: JsonArray, interner: Interner = Interner { it }) = flattenJsonArray(input, interner) +fun flattenJsonElement(input: JsonPrimitive, interner: Interner = Interner { it }) = flattenJsonPrimitive(input, interner) diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/io/json/EitherTypeAdapter.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/io/json/EitherTypeAdapter.kt index ca6eb7bd..79f83c19 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/io/json/EitherTypeAdapter.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/io/json/EitherTypeAdapter.kt @@ -1,9 +1,11 @@ package ru.dbotthepony.kstarbound.io.json import com.google.gson.Gson +import com.google.gson.JsonElement import com.google.gson.JsonSyntaxException import com.google.gson.TypeAdapter import com.google.gson.TypeAdapterFactory +import com.google.gson.internal.bind.JsonTreeReader import com.google.gson.reflect.TypeToken import com.google.gson.stream.JsonReader import com.google.gson.stream.JsonToken @@ -24,6 +26,7 @@ object EitherTypeAdapter : TypeAdapterFactory { return object : TypeAdapter>() { private val leftAdapter = gson.getAdapter(TypeToken.get(left)) as TypeAdapter private val rightAdapter = gson.getAdapter(TypeToken.get(right)) as TypeAdapter + private val elemAdapter = gson.getAdapter(JsonElement::class.java) override fun write(out: JsonWriter, value: Either?) { if (value == null) @@ -36,11 +39,13 @@ object EitherTypeAdapter : TypeAdapterFactory { if (`in`.peek() == JsonToken.NULL) return null + val elem = elemAdapter.read(`in`) + return try { - Either.left(leftAdapter.read(`in`) ?: throw NullPointerException("left was empty")) + Either.left(leftAdapter.read(JsonTreeReader(elem)) ?: throw NullPointerException("left was empty")) } catch(leftError: Throwable) { try { - Either.right(rightAdapter.read(`in`) ?: throw NullPointerException("right was empty")) + Either.right(rightAdapter.read(JsonTreeReader(elem)) ?: throw NullPointerException("right was empty")) } catch(rightError: Throwable) { val error = JsonSyntaxException("Can't read Either of values (left is $left, right is $right)") error.addSuppressed(leftError) diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/io/json/Ext.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/io/json/Ext.kt index a4902f21..1adec39f 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/io/json/Ext.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/io/json/Ext.kt @@ -1,6 +1,11 @@ package ru.dbotthepony.kstarbound.io.json import com.google.gson.Gson +import com.google.gson.JsonArray +import com.google.gson.JsonElement +import com.google.gson.JsonNull +import com.google.gson.JsonObject +import com.google.gson.JsonPrimitive import com.google.gson.JsonSyntaxException import com.google.gson.TypeAdapter import com.google.gson.TypeAdapterFactory @@ -93,3 +98,44 @@ fun JsonReader.consumeNull(): Boolean { return false } + +fun JsonWriter.value(element: JsonPrimitive) { + if (element.isBoolean) { + value(element.asBoolean) + } else if (element.isNumber) { + value(element.asNumber) + } else if (element.isString) { + value(element.asString) + } else { + throw IllegalArgumentException(element.toString()) + } +} + +fun JsonWriter.value(element: JsonNull) { + nullValue() +} + +fun JsonWriter.value(element: JsonArray) { + beginArray() + for (v in element) value(v) + endArray() +} + +fun JsonWriter.value(element: JsonObject) { + beginObject() + for ((k, v) in element.entrySet()) { + name(k) + value(v) + } + endObject() +} + +fun JsonWriter.value(element: JsonElement) { + when (element) { + is JsonPrimitive -> value(element) + is JsonNull -> value(element) + is JsonArray -> value(element) + is JsonObject -> value(element) + else -> throw IllegalArgumentException(element.toString()) + } +} diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/io/json/builder/Annotations.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/io/json/builder/Annotations.kt index 622421b9..f942e35d 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/io/json/builder/Annotations.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/io/json/builder/Annotations.kt @@ -6,79 +6,54 @@ import com.google.gson.TypeAdapterFactory import com.google.gson.reflect.TypeToken import kotlin.reflect.KClass -private fun Int.toBool() = if (this == 0) null else this > 0 - -/** - * Указывает, что для данного класса можно автоматически создать [BuilderAdapter] для всех его свойств, - * которые не указаны как [JsonIgnoreProperty] - * - * @see JsonIgnoreProperty - * @see JsonPropertyConfig - */ -@Target(AnnotationTarget.CLASS) -@Retention(AnnotationRetention.RUNTIME) -annotation class JsonBuilder( - /** - * @see BuilderAdapter.Builder.extraPropertiesAreFatal - */ - val extraPropertiesAreFatal: Boolean = false, - - /** - * @see BuilderAdapter.Builder.ignoreKey - */ - val ignoreKeys: Array = [], - - /** - * 0 = null - * -1 = false - * 1 = true - * - * @see BuilderAdapter.Builder.logMisses - */ - val logMisses: Int = 0, - - /** - * Включать ли свойства родительского класса в данный [BuilderAdapter] - */ - val includeSuperclassProperties: Boolean = true, -) - -val JsonBuilder.realLogMisses get() = logMisses.toBool() - /** * Заставляет указанное свойство быть проигнорированным при автоматическом создании [BuilderAdapter] */ @Target(AnnotationTarget.PROPERTY) @Retention(AnnotationRetention.RUNTIME) -annotation class JsonIgnoreProperty +annotation class JsonIgnore /** - * Выставляет флаги данному свойству при автоматическом создании [BuilderAdapter] + * Указывает, что данное свойство или аргумент является компонующим, а не потомственным значением. * - * @see BuilderAdapter.Builder.add + * Заставляет адаптеры распаковать данное свойство или аргумент на том же уровне, что и родителя */ @Target(AnnotationTarget.PROPERTY) @Retention(AnnotationRetention.RUNTIME) -annotation class JsonPropertyConfig( - val isFlat: Boolean = false, +annotation class JsonFlat - val write: Boolean = true, +@Target(AnnotationTarget.PROPERTY) +@Retention(AnnotationRetention.RUNTIME) +annotation class JsonAlias(vararg val aliases: String) - val mustBePresent: Int = 0, -) +/** + * Указывает, что данное свойство может отсутствовать в структуре + */ +@Target(AnnotationTarget.PROPERTY) +@Retention(AnnotationRetention.RUNTIME) +annotation class JsonOptional -val JsonPropertyConfig.realMustBePresent get() = mustBePresent.toBool() +/** + * Указывает, что данное свойство обязано присутствовать в структуре + */ +@Target(AnnotationTarget.PROPERTY) +@Retention(AnnotationRetention.RUNTIME) +annotation class JsonRequired + +/** + * Указывает, что для данного класса можно автоматически создать [BuilderAdapter] для всех его свойств, + * которые не указаны как [JsonIgnore] + */ +@Target(AnnotationTarget.CLASS) +@Retention(AnnotationRetention.RUNTIME) +annotation class JsonBuilder /** * Указывает, что для данного класса можно автоматически создать [FactoryAdapter] * - * В подавляющем большинстве случаев это работает исключительно с data классами + * Чаще всего, это используется только для data классов * - * С технической точки зрения, у класса определяются все свойства и происходит поиск *главного* конструктора, а - * затем аргументы конструктора отражаются на свойства класса - * - * @see JsonIgnoreProperty - * @see JsonPropertyConfig + * @see JsonIgnore */ @Target(AnnotationTarget.CLASS) @Retention(AnnotationRetention.RUNTIME) @@ -88,11 +63,6 @@ annotation class JsonFactory( */ val storesJson: Boolean = false, - /** - * @see FactoryAdapter.Builder.logMisses - */ - val logMisses: Boolean = true, - /** * @see FactoryAdapter.Builder.inputAsList * @see FactoryAdapter.Builder.inputAsMap 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 bb10ca5b..ceeebe76 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 @@ -1,7 +1,6 @@ package ru.dbotthepony.kstarbound.io.json.builder import com.google.common.collect.ImmutableMap -import com.google.common.collect.ImmutableSet import com.github.benmanes.caffeine.cache.Interner import com.google.gson.Gson import com.google.gson.JsonObject @@ -14,14 +13,11 @@ import com.google.gson.reflect.TypeToken import com.google.gson.stream.JsonReader import com.google.gson.stream.JsonToken import com.google.gson.stream.JsonWriter -import it.unimi.dsi.fastutil.objects.ObjectArraySet import it.unimi.dsi.fastutil.objects.ObjectLinkedOpenHashSet import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet import it.unimi.dsi.fastutil.objects.Reference2ObjectOpenHashMap import org.apache.logging.log4j.LogManager -import ru.dbotthepony.kstarbound.defs.FreezableDefintionBuilder import ru.dbotthepony.kstarbound.defs.util.flattenJsonElement -import ru.dbotthepony.kstarbound.io.json.builder.BuilderAdapter.Builder import ru.dbotthepony.kstarbound.util.INotNullDelegate import kotlin.properties.Delegates import kotlin.reflect.KClass @@ -43,21 +39,6 @@ class BuilderAdapter private constructor( */ val properties: ImmutableMap>, - /** - * Ключи, которые необходимо игнорировать при чтении JSON - */ - val ignoreKeys: ImmutableSet, - - /** - * @see Builder.extraPropertiesAreFatal - */ - val extraPropertiesAreFatal: Boolean, - - /** - * @see Builder.logMisses - */ - val logMisses: Boolean, - val stringInterner: Interner = Interner { it }, ) : TypeAdapter() { private val loggedMisses = ObjectOpenHashSet() @@ -92,21 +73,11 @@ class BuilderAdapter private constructor( } // загружаем указатели на стек - val logMisses = logMisses - val extraPropertiesAreFatal = extraPropertiesAreFatal - val loggedMisses = loggedMisses - val ignoreKeys = ignoreKeys reader.beginObject() while (reader.hasNext()) { val name = reader.nextName() - - if (ignoreKeys.contains(name)) { - reader.skipValue() - continue - } - val property = properties[name] if (property != null) { @@ -131,11 +102,7 @@ class BuilderAdapter private constructor( throw JsonSyntaxException("Reading property ${property.name} of ${instance::class.qualifiedName} near ${reader.path}", err) } } else { - if (extraPropertiesAreFatal) { - throw JsonSyntaxException("$name is not a valid property of ${instance::class.qualifiedName}") - } - - if (logMisses && loggedMisses.add(name)) { + if (loggedMisses.add(name)) { LOGGER.warn("${instance::class.qualifiedName} has no property for storing $name") } @@ -186,25 +153,18 @@ class BuilderAdapter private constructor( } } - if (instance is FreezableDefintionBuilder) { - instance.freeze() - } - return instance } class Builder(val factory: () -> T, vararg fields: KMutableProperty1) : TypeAdapterFactory { private val properties = ArrayList>() - private val ignoreKeys = ObjectArraySet() - var extraPropertiesAreFatal = false - var logMisses: Boolean? = null private val factoryReturnType by lazy { factory.invoke()::class.java } + var stringInterner: Interner = Interner { it } override fun create(gson: Gson, type: TypeToken): TypeAdapter? { - if (type.rawType == factoryReturnType) { + if (type.rawType == factoryReturnType) return build(gson) as TypeAdapter - } return null } @@ -219,102 +179,18 @@ class BuilderAdapter private constructor( factory = factory, properties = map.build(), stringInterner = stringInterner, - 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(), - stringInterner = stringInterner, - ignoreKeys = ImmutableSet.copyOf(ignoreKeys), - extraPropertiesAreFatal = extraPropertiesAreFatal, - logMisses = logMisses ?: properties.none { it.isFlat }, - ) - } - - /** - * Являются ли "лишние" ключи в JSON структуре ошибкой. - * - * Если "лишние" ключи являются ошибкой и известны некоторые лишние ключи, которые не нужны, - * то [extraPropertiesAreFatal] можно скомбинировать с [ignoreKey]. - */ - fun extraPropertiesAreFatal(flag: Boolean = true): Builder { - check(properties.none { it.isFlat } || !flag) { "Can't have both flattened properties and extraPropertiesAreFatal" } - extraPropertiesAreFatal = flag - return this - } - - /** - * Логировать ли несуществующие свойства у класса когда они попадаются в исходной JSON структуре - */ - fun logMisses(flag: Boolean = true): Builder { - logMisses = flag - return this - } - init { - for (field in fields) - auto(field) + for (field in fields) add(field) } - /** - * Добавляет указанное свойство в будущий адаптер с указанным [adapter] - * - * Если указан [isFlat] как true, то данное свойство будет обработано как на одном уровне с данным объектом. - * Пример: - * ```json - * { - * "prop_belong_to_a_1": ..., - * "prop_belong_to_a_2": ..., - * "prop_belong_to_b_1": ..., - * } - * ``` - * - * В данном случае, можно указать `b` как плоский класс внутри `a`. - * - * Данный подход позволяет избавиться от постоянного наследования и реализации одного и того же интерфейса во множестве других классов. - * Флаг [extraPropertiesAreFatal] не поддерживается с данными свойствами. - * Если [logMisses] не указан явно, то он будет выставлен на false. - */ - fun add(property: KMutableProperty1, adapter: TypeAdapter, isFlat: Boolean = false, mustBePresent: Boolean? = null): Builder { + fun add(property: KMutableProperty1, isFlat: Boolean = false, mustBePresent: Boolean? = null): Builder { if (properties.any { it.property == property }) { throw IllegalArgumentException("Property $property is defined twice") } - 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, isFlat: Boolean = false, mustBePresent: Boolean? = null): Builder { - if (properties.any { it.property == property }) { - throw IllegalArgumentException("Property $property is defined twice") - } - - ignoreKeys.remove(property.name) properties.add(ResolvableMutableProperty( property = property, mustBePresent = mustBePresent, @@ -323,14 +199,35 @@ class BuilderAdapter private constructor( return this } + } - fun ignoreKey(name: String): Builder { - if (properties.any { it.property.name == name }) { - throw IllegalArgumentException("Can not ignore key $name because we have property with this name!") + class Factory(val stringInterner: Interner = Interner { it }) : TypeAdapterFactory { + override fun create(gson: Gson, type: TypeToken): TypeAdapter? { + val raw = type.rawType + + if (raw.isAnnotationPresent(JsonBuilder::class.java)) { + val kclass = raw.kotlin + val builder = Builder(kclass.constructors.first { it.parameters.isEmpty() } as () -> T) + + builder.stringInterner = stringInterner + + val declarations = LinkedHashMap>() + collectDecl(kclass, declarations) + + for (decl in declarations.values) { + if (decl.annotations.none { it is JsonIgnore }) { + builder.add( + decl as KMutableProperty1, + isFlat = decl.annotations.any { it.annotationClass == JsonFlat::class }, + mustBePresent = if (decl.annotations.any { it.annotationClass == JsonRequired::class }) true else if (decl.annotations.any { it.annotationClass == JsonOptional::class }) false else null + ) + } + } + + return builder.build(gson) } - ignoreKeys.add(name) - return this + return null } } @@ -373,47 +270,4 @@ class BuilderAdapter private constructor( return list } } - - class Factory(val stringInterner: Interner = Interner { it }) : TypeAdapterFactory { - override fun create(gson: Gson, type: TypeToken): TypeAdapter? { - val raw = type.rawType - - if (raw.isAnnotationPresent(JsonBuilder::class.java)) { - val first = raw.getAnnotationsByType(JsonBuilder::class.java) - require(first.size == 1) { "Multiple JsonBuilder defined: ${first.joinToString(", ")}" } - - val bconfig = first[0] as JsonBuilder - val kclass = raw.kotlin - val builder = Builder(kclass.constructors.first { it.parameters.isEmpty() && !it.returnType.isMarkedNullable } as () -> T) - - builder.logMisses = bconfig.realLogMisses - builder.extraPropertiesAreFatal = bconfig.extraPropertiesAreFatal - builder.stringInterner = stringInterner - - for (name in bconfig.ignoreKeys) { - builder.ignoreKey(name) - } - - val declarations = LinkedHashMap>() - collectDecl(kclass, declarations) - - for (decl in declarations.values) { - if (decl.annotations.none { it is JsonIgnoreProperty }) { - val config = decl.annotations.firstOrNull { it is JsonPropertyConfig } - - if (config == null) { - builder.auto(decl as KMutableProperty1) - } else { - config as JsonPropertyConfig - builder.auto(decl as KMutableProperty1, isFlat = config.isFlat, mustBePresent = config.realMustBePresent) - } - } - } - - return builder.build(gson) - } - - return null - } - } } 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 bfd57aaf..3188e8f9 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 @@ -5,6 +5,7 @@ import com.google.common.collect.ImmutableMap import com.github.benmanes.caffeine.cache.Interner import com.google.gson.Gson import com.google.gson.JsonArray +import com.google.gson.JsonNull import com.google.gson.JsonObject import com.google.gson.JsonParseException import com.google.gson.JsonSyntaxException @@ -16,14 +17,16 @@ import com.google.gson.reflect.TypeToken import com.google.gson.stream.JsonReader import com.google.gson.stream.JsonToken import com.google.gson.stream.JsonWriter +import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap import it.unimi.dsi.fastutil.objects.Object2IntArrayMap +import it.unimi.dsi.fastutil.objects.Object2ObjectArrayMap import it.unimi.dsi.fastutil.objects.ObjectArraySet import org.apache.logging.log4j.LogManager import ru.dbotthepony.kstarbound.defs.util.enrollList import ru.dbotthepony.kstarbound.defs.util.enrollMap import ru.dbotthepony.kstarbound.defs.util.flattenJsonElement import ru.dbotthepony.kstarbound.io.json.consumeNull -import ru.dbotthepony.kstarbound.io.json.ifString +import ru.dbotthepony.kstarbound.io.json.value import java.lang.reflect.Constructor import kotlin.jvm.internal.DefaultConstructorMarker import kotlin.properties.Delegates @@ -37,28 +40,38 @@ import kotlin.reflect.full.primaryConstructor * [TypeAdapter] для классов, которые имеют все свои свойства в главном конструкторе. */ class FactoryAdapter private constructor( - val bound: KClass, + val clazz: KClass, val types: ImmutableList>, + aliases: Map, val asJsonArray: Boolean, val storesJson: Boolean, - val logMisses: Boolean, val stringInterner: Interner ) : TypeAdapter() { private val name2index = Object2IntArrayMap() private val loggedMisses = ObjectArraySet() init { + if (asJsonArray && types.any { it.isFlat }) { + throw IllegalArgumentException("Can't have both flat properties and input be as json array") + } + name2index.defaultReturnValue(-1) for ((i, pair) in types.withIndex()) { name2index[pair.property.name] = i + + aliases.entries.forEach { + if (it.value == pair.property.name) { + name2index[it.key] = i + } + } } } /** * Обычный конструктор класса (без флагов "значения по умолчанию") */ - private val regularFactory: KFunction = bound.constructors.firstOrNull first@{ + private val regularFactory: KFunction = clazz.constructors.firstOrNull first@{ var requiredSize = types.size if (storesJson) @@ -98,7 +111,7 @@ class FactoryAdapter private constructor( } return@first false - } ?: throw NoSuchElementException("Unable to determine constructor for ${bound.qualifiedName} matching (${types.joinToString(", ")})") + } ?: throw NoSuchElementException("Unable to determine constructor for ${clazz.qualifiedName} matching (${types.joinToString(", ")})") /** * Синтетический конструктор класса, который создаётся Kotlin'ном, для создания классов со значениями по умолчанию @@ -117,19 +130,15 @@ class FactoryAdapter private constructor( typelist.add(DefaultConstructorMarker::class.java) - bound.java.getDeclaredConstructor(*typelist.toTypedArray()) + clazz.java.getDeclaredConstructor(*typelist.toTypedArray()) } catch(_: NoSuchMethodException) { null } - private val syntheticPrimitives: Array? + private val syntheticPrimitives = Int2ObjectOpenHashMap() init { - if (syntheticFactory == null) { - syntheticPrimitives = null - } else { - syntheticPrimitives = arrayOfNulls(syntheticFactory.parameters.size) - + if (syntheticFactory != null) { for ((i, param) in syntheticFactory.parameters.withIndex()) { val type = param.parameterizedType as? Class<*> ?: continue @@ -158,10 +167,24 @@ class FactoryAdapter private constructor( out.beginObject() - for ((field, adapter) in types) { - out.name(field.name) - @Suppress("unchecked_cast") - (adapter as TypeAdapter).write(out, (field as KProperty1).get(value)) + for (type in types) { + if (type.isFlat) { + val (field, adapter) = type + val result = (adapter as TypeAdapter).toJsonTree((field as KProperty1).get(value)) + + if (result != null && result != JsonNull.INSTANCE) { + if (result !is JsonObject) { + throw JsonSyntaxException("Expected JsonObject from adapter of ${type.name}, but got ${result::class.qualifiedName}") + } + + out.value(result) + } + } else { + val (field, adapter) = type + out.name(field.name) + @Suppress("unchecked_cast") + (adapter as TypeAdapter).write(out, (field as KProperty1).get(value)) + } } out.endObject() @@ -173,7 +196,7 @@ class FactoryAdapter private constructor( // таблица присутствия значений (если значение true то на i было значение внутри json) val presentValues = BooleanArray(types.size + (if (storesJson) 1 else 0)) - val readValues = arrayOfNulls(types.size + (if (storesJson) 1 else 0)) + var readValues = arrayOfNulls(types.size + (if (storesJson) 1 else 0)) if (storesJson) presentValues[presentValues.size - 1] = true @@ -183,28 +206,26 @@ class FactoryAdapter private constructor( // Если нам необходимо читать объект как набор данных массива, то давай if (asJsonArray) { - val iterator = types.iterator() - var fieldId = 0 - if (storesJson) { val readArray = TypeAdapters.JSON_ELEMENT.read(reader) - if (readArray !is JsonArray) { + if (readArray !is JsonArray) throw JsonParseException("Expected JSON element to be an Array, ${readArray::class.qualifiedName} given") - } reader = JsonTreeReader(readArray) - readValues[readValues.size - 1] = enrollList(flattenJsonElement(readArray) as List, stringInterner::intern) + readValues[readValues.size - 1] = enrollList(flattenJsonElement(readArray, stringInterner) as List, stringInterner) } reader.beginArray() + val iterator = types.iterator() + var fieldId = 0 while (reader.peek() != JsonToken.END_ARRAY) { if (!iterator.hasNext()) { val name = fieldId.toString() if (!storesJson && loggedMisses.add(name)) { - LOGGER.warn("${bound.qualifiedName} has no property for storing $name") + LOGGER.warn("${clazz.qualifiedName} has no property for storing $name") } reader.skipValue() @@ -213,14 +234,13 @@ class FactoryAdapter private constructor( continue } - val tuple = iterator.next() - val (field, adapter) = tuple + val (field, adapter) = iterator.next() try { readValues[fieldId] = adapter.read(reader) presentValues[fieldId] = true } catch(err: Throwable) { - throw JsonSyntaxException("Reading field \"${field.name}\" near ${reader.path} for ${bound.qualifiedName}", err) + throw JsonSyntaxException("Reading field \"${field.name}\" near ${reader.path} for ${clazz.qualifiedName}", err) } fieldId++ @@ -228,18 +248,18 @@ class FactoryAdapter private constructor( // иначе - читаем как json object } else { var json: JsonObject by Delegates.notNull() - val hasFlatValues = types.any { it.isFlat } - if (storesJson || hasFlatValues) { + if (storesJson || types.any { it.isFlat }) { val readMap = TypeAdapters.JSON_ELEMENT.read(reader) - if (readMap !is JsonObject) { + if (readMap !is JsonObject) throw JsonParseException("Expected JSON element to be a Map, ${readMap::class.qualifiedName} given") - } json = readMap reader = JsonTreeReader(readMap) - readValues[readValues.size - 1] = enrollMap(flattenJsonElement(readMap) as Map, stringInterner::intern) + + if (storesJson) + readValues[readValues.size - 1] = enrollMap(flattenJsonElement(readMap, stringInterner) as Map, stringInterner) } reader.beginObject() @@ -249,8 +269,8 @@ class FactoryAdapter private constructor( val fieldId = name2index.getInt(name) if (fieldId == -1) { - if (!storesJson && !hasFlatValues && logMisses && loggedMisses.add(name)) { - LOGGER.warn("${bound.qualifiedName} has no property for storing $name") + if (!storesJson && loggedMisses.add(name)) { + LOGGER.warn("${clazz.qualifiedName} has no property for storing $name") } reader.skipValue() @@ -268,7 +288,7 @@ class FactoryAdapter private constructor( readValues[fieldId] = adapter.read(reader) presentValues[fieldId] = true } catch(err: Throwable) { - throw JsonSyntaxException("Reading field \"${field.name}\" near ${reader.path} for ${bound.qualifiedName}", err) + throw JsonSyntaxException("Reading field \"${field.name}\" near ${reader.path} for ${clazz.qualifiedName}", err) } } } @@ -283,7 +303,7 @@ class FactoryAdapter private constructor( readValues[i] = read } } catch(err: Throwable) { - throw JsonSyntaxException("Reading flat field \"${property.property.name}\" near ${reader.path} for ${bound.qualifiedName}", err) + throw JsonSyntaxException("Reading flat field \"${property.property.name}\" for ${clazz.qualifiedName}", err) } } } @@ -303,11 +323,9 @@ class FactoryAdapter private constructor( if (readValues[i] == null) { if (!tuple.isMarkedNullable) { - throw JsonSyntaxException("Field ${field.name} of ${bound.qualifiedName} does not accept nulls") - } - - if (!regularFactory.parameters[i].isOptional && !presentValues[i]) { - throw JsonSyntaxException("Field ${field.name} of ${bound.qualifiedName} must be defined (even just as null)") + throw JsonSyntaxException("Field ${field.name} of ${clazz.qualifiedName} does not accept nulls") + } else if (!regularFactory.parameters[i].isOptional && !presentValues[i]) { + throw JsonSyntaxException("Field ${field.name} of ${clazz.qualifiedName} must be defined") } } } @@ -316,86 +334,59 @@ class FactoryAdapter private constructor( return regularFactory.call(*readValues as Array) // иначе - в бой вступает синтетический конструктор, с флагами "значения по умолчанию" } else { - // количество bitflag'ов значений по умолчанию - val ints = if (presentValues.size % 31 == 0) presentValues.size / 31 else presentValues.size / 31 + 1 - // максимум считанных значений + bitflag аргументы значений по умолчанию + null типа DefaultConstructorMarker - val copied = readValues.copyOf(readValues.size + ints + 1) - var intIndex = readValues.size - var target = 0 - var targetMove = 0 + var argumentFlagCount = presentValues.size / 31 + 1 /* DefaultConstructorMarker */ + if (presentValues.size % 31 != 0) argumentFlagCount++ + readValues = readValues.copyOf(readValues.size + argumentFlagCount) - for (bool in presentValues) { - if (!bool) { - target = target.or(1.shl(targetMove)) - } + var flagIndex = readValues.size - argumentFlagCount + var flags = 0 + var flagBit = 0 - targetMove++ + for (isPresent in presentValues) { + if (!isPresent) flags = flags.or(1.shl(flagBit)) + flagBit++ - if (targetMove >= 32) { - copied[intIndex++] = target - target = 0 - targetMove = 0 + if (flagBit >= 32) { + readValues[flagIndex++] = flags + flags = 0 + flagBit = 0 } } - if (targetMove != 0) { - copied[intIndex] = target + if (flagBit != 0) { + readValues[flagIndex] = flags } - val syntheticPrimitives = syntheticPrimitives!! - - for ((i, tuple) in types.withIndex()) { - if (copied[i] != null) { - continue - } - - val (field) = tuple + for ((i, field) in types.withIndex()) { + if (readValues[i] != null) continue val param = regularFactory.parameters[i] - if (!param.isOptional && !presentValues[i]) { - throw JsonSyntaxException("Field ${field.name} of ${bound.qualifiedName} is missing") - } - - if (tuple.isMarkedNullable) { - continue - } - if (param.isOptional && !presentValues[i]) { - copied[i] = syntheticPrimitives[i] - continue + readValues[i] = syntheticPrimitives[i] + } else if (!param.isOptional) { + if (!presentValues[i]) throw JsonSyntaxException("Field ${field.name} of ${clazz.qualifiedName} is missing") + if (!param.type.isMarkedNullable) throw JsonSyntaxException("Field ${field.name} of ${clazz.qualifiedName} does not accept nulls") } - - throw JsonSyntaxException("Field \"${field.name}\" of ${bound.qualifiedName} does not accept nulls near ${reader.path}") } - return syntheticFactory.newInstance(*copied) + return syntheticFactory.newInstance(*readValues) } } /** * Позволяет построить класс [FactoryAdapter] на основе заданных параметров */ - class Builder(val clazz: KClass) : TypeAdapterFactory { - constructor(clazz: KClass, vararg fields: KProperty1) : this(clazz) { - for (field in fields) { - auto(field) - } - } - + class Builder(val clazz: KClass, vararg fields: KProperty1) : TypeAdapterFactory { + private var asList = false private var storesJson = false - private var logMisses = true private val types = ArrayList>() - private var stringTransformer: ((String) -> T)? = null + private val aliases = Object2ObjectArrayMap() var stringInterner: Interner = Interner { it } - fun stringInterner(interner: Interner): Builder { - this.stringInterner = interner - return this - } - - fun ifString(transformer: (String) -> T): Builder { - stringTransformer = transformer - return this + init { + for (field in fields) { + add(field) + } } override fun create(gson: Gson, type: TypeToken): TypeAdapter? { @@ -413,44 +404,13 @@ class FactoryAdapter private constructor( check(!asList || types.none { it.isFlat }) { "Can't have both flat properties and json data array layout" } return FactoryAdapter( - bound = clazz, + clazz = clazz, types = ImmutableList.copyOf(types.map { it.resolve(gson) }), asJsonArray = asList, storesJson = storesJson, - logMisses = logMisses, stringInterner = stringInterner, - ).let { - if (stringTransformer != null) - it.ifString(stringTransformer!!) - else - it - } - } - - /** - * Собирает этот [FactoryAdapter] без GSON объекта - * - * Не рекомендуется использовать, лучше всего использовать как [TypeAdapterFactory] - * - * Несмотря на @Deprecated, данный вариант метода удалён не будет - */ - @Deprecated("Используйте как TypeAdapterFactory") - fun build(): TypeAdapter { - 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.map { it.resolve(null) }), - asJsonArray = asList, - storesJson = storesJson, - logMisses = logMisses, - stringInterner = stringInterner, - ).let { - if (stringTransformer != null) - it.ifString(stringTransformer!!) - else - it - } + aliases = aliases + ) } /** @@ -466,34 +426,15 @@ class FactoryAdapter private constructor( return this } - /** - * Логировать ли json значения которые нет в списке свойств - */ - fun logMisses(flag: Boolean = true): Builder { - logMisses = flag - return this - } - - /** - * Добавляет свойство с определённым [adapter] - */ - fun add(field: KProperty1, adapter: TypeAdapter, isFlat: Boolean = false): Builder { - types.add(ResolvedProperty(field, adapter, isFlat = isFlat)) - return this - } - - /** - * Автоматически определяет необходимый адаптер типа к свойству при сборке данного адаптера внутри Gson - * - * Можно указать [transform] для изменения определённого адаптера - */ - @Suppress("unchecked_cast") - fun auto(field: KProperty1, isFlat: Boolean = false, transform: (TypeAdapter) -> TypeAdapter = { it }): Builder { + fun add(field: KProperty1, isFlat: Boolean = false, transform: (TypeAdapter) -> TypeAdapter = { it }): Builder { types.add(ResolvableProperty(field, isFlat = isFlat, transform = transform)) return this } - private var asList = false + fun alias(alias: String, canonical: String): Builder { + aliases[alias] = canonical + return this + } /** * При выставлении данного флага в качестве исходной структуры будет приниматься Json объект: @@ -542,7 +483,6 @@ class FactoryAdapter private constructor( } builder.storesJson(config.storesJson) - builder.logMisses(config.logMisses) builder.stringInterner = stringInterner if (properties.isEmpty()) { @@ -551,20 +491,20 @@ class FactoryAdapter private constructor( val foundConstructor = kclass.primaryConstructor ?: throw NoSuchElementException("Can't determine primary constructor for ${kclass.qualifiedName}") - if (!config.storesJson) { - for (argument in foundConstructor.parameters) { - val property = properties.first { it.name == argument.name && it.returnType.isSupertypeOf(argument.type) } - val config = property.annotations.firstOrNull { it.annotationClass == JsonPropertyConfig::class } as JsonPropertyConfig? - builder.auto(property, isFlat = config?.isFlat ?: false) - } - } else { - val params = foundConstructor.parameters + val params = foundConstructor.parameters + val lastIndex = if (config.storesJson) params.size - 1 else params.size - for (i in 0 until params.size - 1) { - val argument = params[i] - val property = properties.first { it.name == argument.name && it.returnType.isSupertypeOf(argument.type) } - val config = property.annotations.firstOrNull { it.annotationClass == JsonPropertyConfig::class } as JsonPropertyConfig? - builder.auto(property, isFlat = config?.isFlat ?: false) + for (i in 0 until lastIndex) { + val argument = params[i] + val property = properties.first { it.name == argument.name && it.returnType.isSupertypeOf(argument.type) } + builder.add(property, isFlat = property.annotations.any { it.annotationClass == JsonFlat::class }) + + property.annotations.firstOrNull { it.annotationClass == JsonAlias::class }?.let { + it as JsonAlias + + for (name in it.aliases) { + builder.alias(name, property.name) + } } }